{"thread":{"id":"59560","subject":"[PATCH 00/14] Introduce new `git replay` command","startedAt":"2023-04-07T07:25:07Z","lastAt":"2023-11-25T00:02:35Z","messageCount":208,"participants":["Christian Couder","Phillip Wood","Derrick Stolee","Felipe Contreras","Elijah Newren","Junio C Hamano","Toon Claes","Johannes Schindelin","Dragan Simic","Linus Arver"],"isPatch":true,"patchVersion":1,"patchTotal":14},"messages":[{"id":"474976","messageId":"20230407072415.1360068-1-christian.couder@gmail.com","threadId":"59560","inReplyTo":null,"subject":"[PATCH 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:01Z","receivedAt":"2023-04-07T07:25:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"# Intro\n\n`git replay` has initially been developed entirely by Elijah Newren\nmostly between June and July 2022 at:\n\nhttps://github.com/newren/git/commits/replay\n\nI took over a few months ago to polish and upstream it as GitLab is\ninterested in replacing libgit2, and for that purpose needs a command\nto do server side (so without using a worktree) rebases, cherry-picks\nand reverts.\n\nI reduced the number of commits and features in this first patch\nseries, compared to what Elijah already developed. Especially I\nstopped short of replaying merge commits and replaying\ninteractively. These and other features might be upstreamed in the\nfuture after this patch series has graduated.\n\nThanks to Elijah, Patrick Steinhardt and Dscho for early reviews and\ndiscussions.\n\nBased on ae73b2c8f1 (The seventh batch, 2023-04-04)\n\n# Quick Overview (from Elijah)\n\n`git replay`, at a basic level, can perhaps be thought of as a\n\"default-to-dry-run rebase\" -- meaning no updates to the working tree,\nor to the index, or to any references.  However, it differs from\nrebase in that it:\n\n  * Works for branches that aren't checked out\n  * Works in a bare repository\n  * Can replay multiple branches simultaneously (with or without common\n    history in the range being replayed)\n  * Preserves relative topology by default (merges are replayed too)\n  * Focuses on performance\n  * Has several altered defaults as a result of the above\n\nI sometimes think of `git replay` as \"fast-replay\", a patch-based\nanalogue to the snapshot-based fast-export & fast-import tools.\n\n# Reasons for diverging from cherry-pick & rebase (from Elijah)\n\nThere are multiple reasons to diverge from the defaults in cherry-pick and\nrebase.\n\n* Server side needs\n\n  * Both cherry-pick and rebase, via the sequencer, are heavily tied\n    to updating the working tree, index, some refs, and a lot of\n    control files with every commit replayed, and invoke a mess of\n    hooks[1] that might be hard to avoid for backward compatibility\n    reasons (at least, that's been brought up a few times on the\n    list).\n\n  * cherry-pick and rebase both fork various subprocesses\n    unnecessarily, but somewhat intrinsically in part to ensure the\n    same hooks are called that old scripted implementations would\n    have called.\n\n  * \"Dry run\" behavior, where there are no updates to worktree, index,\n    or even refs might be important.\n\n  * Should not assume users only want to operate on HEAD (see next\n    section)\n\n* Decapitate HEAD-centric assumptions\n\n  * cherry-pick forces commits to be played on top of HEAD; inflexible.\n\n  * rebase assumes the range of commits to be replayed is\n    upstream..HEAD by default, though it allows one to replay\n    upstream..otherbranch -- but it still forcibly and needlessly\n    checks out otherbranch before starting to replay things.\n\n  * Assuming HEAD is involved severely limits replaying multiple\n    (possibly divergent) branches.\n\n  * Once you stop assuming HEAD has a certain meaning, there's not\n    much reason to have two separate commands anymore (except for the\n    funny extra not-necessarily-compatible options both have gained\n    over time).\n\n  * (Micro issue: Assuming HEAD is involved also makes it harder for\n    new users to learn what rebase means and does; it makes command\n    lines hard to parse.  Not sure I want to harp on this too much, as\n    I have a suspicion I might be creating a tool for experts with\n    complicated use cases, but it's a minor quibble.)\n\n* Performance\n\n  * jj is slaughtering us on rebase speed[2].  I would like us to become\n    competitive.  (I dropped a few comments in the link at [2] about why\n    git is currently so bad.)\n\n  * From [3], there was a simple 4-patch series in linux.git that took\n    53 seconds to rebase.  Switching to ort dropped it to 16 seconds.\n    While that sounds great, only 11 *milliseconds* were needed to do\n    the actual merges.  That means almost *all* the time (>99%) was\n    overhead!  Big offenders:\n\n    * --reapply-cherry-picks should be the default\n\n    * can_fast_forward() should be ripped out, and perhaps other extraneous\n      revision walks\n\n    * avoid updating working tree, index, refs, reflogs, and control\n      structures except when needed (e.g. hitting a conflict, or operation\n      finished)\n\n  * Other performance ideas:\n\n    * single-file control structures instead of directory of files\n\n    * avoid forking subprocesses unless explicitly requested (e.g.\n      --exec, --strategy, --run-hooks).  For example, definitely do not\n      invoke `git commit` or `git merge`.\n\n    * Sanitize hooks:\n\n      * dispense with all per-commit hooks for sure (pre-commit,\n        post-commit, post-checkout).\n\n      * pre-rebase also seems to assume exactly 1 ref is written, and\n        invoking it repeatedly would be stupid.  Plus, it's specific\n        to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n        rebase probably broke the pre-rebase assumptions already...)\n\n      * post-rewrite hook might make sense, but fast-import got\n        exempted, and I think of replay like a patch-based analogue\n        to the snapshot-based fast-import.\n\n    * When not running server side, resolve conflicts in a sparse-cone\n      sparse-index worktree to reduce number of files written to a\n      working tree.  (See below as well)\n\n    * [High risk of possible premature optimization] Avoid large\n      numbers of newly created loose objects, when replaying large\n      numbers of commits.  Two possibilities: (1) Consider using\n      tmp-objdir and pack objects from the tmp-objdir at end of\n      exercise, (2) Lift code from git-fast-import to immediately\n      stuff new objects into a pack?\n\n* Multiple branches and non-checked out branches\n\n  * The ability to operate on non-checked out branches also implies\n    that we should generally be able to replay when in a dirty working\n    tree (exception being when we expect to update HEAD and any of the\n    dirty files is one that needs to be updated by the replay).\n\n  * Also, if we are operating locally on a non-checked out branch and\n    hit a conflict, we should have a way to resolve the conflict without\n    messing with the user's work on their current branch.\n\n    * Idea: new worktree with sparse cone + sparse index checkout,\n      containing only files in the root directory, and whatever is\n      necessary to get the conflicts\n\n    * Companion to above idea: control structures should be written to\n      $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n      replay sessions, and so we know which worktrees are associated\n      with which replay operations.\n\n  - [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/\n  - [2] https://github.com/martinvonz/jj/discussions/49\n  - [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/\n\n# Important limitations\n\n* The code die()s if there are any conflict. No resumability. No nice\n  output. No interactivity.\n\n* No replaying merges, nor root commits. Only regular commits.\n\n* Signed commits are not properly handled. It's not clear what to do\n  to such commits when replaying on the server side.\n\n# Commit overview\n\n* 1/14 replay: introduce new builtin\n\n     This creates a minimal `git replay` command by moving the code\n     from the `fast-rebase` test helper from `t/helper/` into\n     `builtin/` and doing some renames and a few other needed changes.\n\n* - 2/14 replay: start using parse_options API\n  - 3/14 replay: die() instead of failing assert()\n  - 4/14 replay: introduce pick_regular_commit()\n  - 5/14 replay: don't simplify history\n  - 6/14 replay: add an important FIXME comment about gpg signing\n  - 7/14 replay: remove progress and info output\n  - 8/14 replay: remove HEAD related sanity check\n\n     These slowly change the command to make it behave more like a\n     regular commands and to start cleaning up its output. \n\n* 9/14 replay: very coarse worktree updating\n\n     Make it handle conflicts in a very coarse way. This might not\n     work on bare repos, but it allows existing tests to pass and it's\n     nice to help cli users a bit when they get conflicts.\n\n* 10/14 replay: make it a minimal server side command\n\n     After the cleaning up in previous ommits, it's now time to\n     radically change the way it works by stopping it to do ref\n     updates, to update the index and worktree, to consider HEAD as\n     special. Instead just make it output commands that should be\n     passed to `git update-ref --stdin`.\n\n* - 11/14 replay: use standard revision ranges\n  - 12/14 replay: introduce guess_new_base()\n  - 13/14 replay: add different modes\n  - 14/14 replay: stop assuming replayed branches do not diverge\n\n      These finish the clean up and add new interesting features at\n      the same time, as well as related documentation and tests.\n\n# Note about tests and documentation\n\nNote that the `fast-rebase` test helper was used before this series in\n\nt6429-merge-sequence-rename-caching.sh\n\nSo when `git replay` is created from `fast-rebase` in patch 1/14, this\ntest script is also converted to use `git replay`. This ensures that\n`git replay` doesn't break too badly during the first 10 patches in\nthis patch series.\n\nTests and documentation are introduced specifically for `git replay`\nonly in 11/14 and later patches as it doesn't make much sense to\ndocument and test behavior that we know is going to change soon. So\nit's only when the command is crystalizing towards its final form that\nwe start documenting and testing it.\n\n# Possibly controversial issues \n\n* bare or not bare: this series works towards a command with the end\n  goal of it being usable and used on bare repos, contrary to existing\n  commands like `git rebase` and `git cherry-pick`, but the tests\n  currently don't check that, and in case of conflicts it won't\n  currently work on bare repos. One reason for that is that existing\n  tests in t6429 should continue to work, and one of these tests\n  requires some output in case of conflict. And it's nice for users to\n  get some help in case of conflict. It's also nice for users if\n  commands that should work on both bare and non bare repos work well\n  on non bare repos first as they are less likely to use them on bare\n  repos. So let's have a command that works well on non-bare repos\n  first, even if its end goal is to work fine on bare repos too. We\n  plan to improve things for bare repos soon after this first patch\n  series graduates.\n\n* exit status: a successful, non-conflicted replay exits with code\n  0. When the replay has conflicts, the exit status is 1. If the\n  replay is not able to complete (or start) due to some kind of error,\n  the exit status is something other than 0 or 1. It has been\n  suggested in an internal review that conflicts might want to get a\n  more specific error code as an error code of 1 might be quite easy\n  to return by accident. It doesn't seem to me from their docs (which\n  might want to be improved, I didn't look at the code) that other\n  commands like `git merge` and `git rebase` exit with a special error\n  code in case of conflict.\n\n* to guess or not to guess: commit 12/14 introduces the\n  guess_new_base() function which tries to find a base to rebase onto\n  when the --onto option is not provided, making this option actually\n  optional instead of mandatory. Given that it's an heuristic and the\n  command end goal is to be used on server side, we might want to\n  introduce this as an iterative improvement later. I still think it's\n  interesting to have it in for now though, as it shows that --onto\n  and --advance (which is introduced in the following commit) should\n  indeed be options. If --onto was always mandatory in the series,\n  people could argue that it shouldn't be an option and its argument\n  should always be the first (unconditional) argument of the command.\n\n* make worktree and index changes optional: commit 10/14 stops\n  updating the index and worktree, but it might be better especially\n  for cli users to make that optional. The issue is that this would\n  make the command more complex while we are developing a number of\n  important features. It seems to me that this should rather be done\n  in an iterative improvement after the important features have\n  landed.\n\n* when and where to add tests and docs: although t6429 has tests that\n  are changed to use the new command instead of the fast-rebase\n  test-tool command as soon as the former is introduced, there is no\n  specific test script and no doc for the new command until commit\n  11/14 when standard revision ranges are used. This is done to avoid\n  churn in tests and docs while the final form of the command hasn't\n  crystalized enough. Adding tests and doc at this point makes this\n  commit quite big and possibly more difficult to review than if they\n  were in separate commits though. On the other hand when tests and\n  docs are added in specific commits some reviewers say it would be\n  better to introduce them when the related changes are made.\n\n* --advance and --contained: these two advanced options might not\n  belong to this first series and could perhaps be added in a followup\n  series in separate commits. On the other hand the code for\n  --contained seems involved with the code of --advance and it's nice\n  to see soon that git replay can indeed do cherry-picking and rebase\n  many refs at once, and this way fullfil these parts of its promise.\n\n* replaying diverging branches: 14/14 the last patch in the series,\n  which allow replaying diverging branches, can be seen as a\n  fundamental fix or alternatively as adding an interesting\n  feature. So it's debatable if it should be in its own patch along\n  with its own tests as in this series, or if it should be merged into\n  a previous patch and which one.\n\n* only 2 patches: this patch series can be seen from a high level\n  point of view as 1) introducing the new `git replay` command, and 2)\n  using `git replay` to replace, and get rid of, the fast-rebase\n  test-tool command. The fact that not much of the original\n  fast-rebase code and interface is left would agree with that point\n  of view. On the other hand, fast-rebase can also be seen as a first\n  iteration towards `git replay`. So it can also make sense to see how\n  `git replay` evolved from it.\n\n\nElijah Newren (14):\n  replay: introduce new builtin\n  replay: start using parse_options API\n  replay: die() instead of failing assert()\n  replay: introduce pick_regular_commit()\n  replay: don't simplify history\n  replay: add an important FIXME comment about gpg signing\n  replay: remove progress and info output\n  replay: remove HEAD related sanity check\n  replay: very coarse worktree updating\n  replay: make it a minimal server side command\n  replay: use standard revision ranges\n  replay: introduce guess_new_base()\n  replay: add different modes\n  replay: stop assuming replayed branches do not diverge\n\n .gitignore                               |   1 +\n Documentation/git-replay.txt             | 130 +++++++\n Makefile                                 |   2 +-\n builtin.h                                |   1 +\n builtin/replay.c                         | 419 +++++++++++++++++++++++\n command-list.txt                         |   1 +\n git.c                                    |   1 +\n t/helper/test-fast-rebase.c              | 233 -------------\n t/helper/test-tool.c                     |   1 -\n t/helper/test-tool.h                     |   1 -\n t/t3650-replay-basics.sh                 | 160 +++++++++\n t/t6429-merge-sequence-rename-caching.sh |  43 ++-\n 12 files changed, 739 insertions(+), 254 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100644 builtin/replay.c\n delete mode 100644 t/helper/test-fast-rebase.c\n create mode 100755 t/t3650-replay-basics.sh\n\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474977","messageId":"20230407072415.1360068-3-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 02/14] replay: start using parse_options API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:03Z","receivedAt":"2023-04-07T07:25:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of manually parsing arguments, let's start using the parse_options\nAPI. This way this new builtin will look more standard, and in some\nupcoming commits will more easily be able to handle more command line\noptions.\n\nNote that we plan to later use standard revision ranges instead of\nhardcoded \"<oldbase> <branch>\" arguments. When we will use standard\nrevision ranges, it will be easier to check if there are no spurious\narguments if we keep ARGV[0], so let's call parse_options() with\nPARSE_OPT_KEEP_ARGV0 even if we don't need ARGV[0] right now to avoid\nsome useless code churn.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 44 ++++++++++++++++++++++++++++++++------------\n 1 file changed, 32 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 154a976ca6..ca931dee41 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -11,6 +11,7 @@\n #include \"hex.h\"\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n+#include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n #include \"sequencer.h\"\n@@ -82,6 +83,7 @@ static struct commit *create_commit(struct tree *tree,\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n+\tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n \tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n@@ -95,16 +97,32 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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+\tconst char * const replay_usage[] = {\n+\t\tN_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tNULL\n+\t};\n+\tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"onto\", &onto_name,\n+\t\t\t   N_(\"revision\"),\n+\t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n+\n+\tif (!onto_name) {\n+\t\terror(_(\"option --onto is mandatory\"));\n+\t\tusage_with_options(replay_usage, replay_options);\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+\tif (argc != 3) {\n+\t\terror(_(\"bad number of arguments\"));\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n \n-\tonto = peel_committish(argv[2]);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\tonto = peel_committish(onto_name);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \t/* Sanity check */\n \tif (get_oid(\"HEAD\", &head))\n@@ -116,6 +134,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tBUG(\"Could not read index\");\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n+\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\n@@ -124,7 +143,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n@@ -187,8 +207,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &last_picked_commit->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -203,8 +223,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &head,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t}\n \tif (write_locked_index(&the_index, &lock,\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474978","messageId":"20230407072415.1360068-2-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 01/14] replay: introduce new builtin","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:02Z","receivedAt":"2023-04-07T07:25:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor now, this is just a rename from `t/helper/test-fast-rebase.c` into\n`builtin/replay.c` with minimal changes to make it build appropriately.\n\nSubsequent commits will flesh out its capabilities and make it a more\nstandard regular builtin.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n .gitignore                                    |  1 +\n Makefile                                      |  2 +-\n builtin.h                                     |  1 +\n .../test-fast-rebase.c => builtin/replay.c    | 23 ++++------------\n command-list.txt                              |  1 +\n git.c                                         |  1 +\n t/helper/test-tool.c                          |  1 -\n t/helper/test-tool.h                          |  1 -\n t/t6429-merge-sequence-rename-caching.sh      | 27 +++++++------------\n 9 files changed, 19 insertions(+), 39 deletions(-)\n rename t/helper/test-fast-rebase.c => builtin/replay.c (89%)\n\ndiff --git a/.gitignore b/.gitignore\nindex e875c59054..b5f025a296 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -135,6 +135,7 @@\n /git-remote-ext\n /git-repack\n /git-replace\n+/git-replay\n /git-request-pull\n /git-rerere\n /git-reset\ndiff --git a/Makefile b/Makefile\nindex 60ab1a8b4f..44546f74dc 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -799,7 +799,6 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o\n TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n TEST_BUILTINS_OBJS += test-env-helper.o\n TEST_BUILTINS_OBJS += test-example-decorate.o\n-TEST_BUILTINS_OBJS += test-fast-rebase.o\n TEST_BUILTINS_OBJS += test-fsmonitor-client.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n TEST_BUILTINS_OBJS += test-genzeros.o\n@@ -1286,6 +1285,7 @@ BUILTIN_OBJS += builtin/remote-fd.o\n BUILTIN_OBJS += builtin/remote.o\n BUILTIN_OBJS += builtin/repack.o\n BUILTIN_OBJS += builtin/replace.o\n+BUILTIN_OBJS += builtin/replay.o\n BUILTIN_OBJS += builtin/rerere.o\n BUILTIN_OBJS += builtin/reset.o\n BUILTIN_OBJS += builtin/rev-list.o\ndiff --git a/builtin.h b/builtin.h\nindex 46cc789789..6ad9efd6cd 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -204,6 +204,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix);\n int cmd_remote_ext(int argc, const char **argv, const char *prefix);\n int cmd_remote_fd(int argc, const char **argv, const char *prefix);\n int cmd_repack(int argc, const char **argv, const char *prefix);\n+int cmd_replay(int argc, const char **argv, const char *prefix);\n int cmd_rerere(int argc, const char **argv, const char *prefix);\n int cmd_reset(int argc, const char **argv, const char *prefix);\n int cmd_restore(int argc, const char **argv, const char *prefix);\ndiff --git a/t/helper/test-fast-rebase.c b/builtin/replay.c\nsimilarity index 89%\nrename from t/helper/test-fast-rebase.c\nrename to builtin/replay.c\nindex b1edb92a03..154a976ca6 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/builtin/replay.c\n@@ -1,18 +1,11 @@\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+ * \"git replay\" builtin command\n  */\n \n #define USE_THE_INDEX_VARIABLE\n-#include \"test-tool.h\"\n+#include \"git-compat-util.h\"\n \n+#include \"builtin.h\"\n #include \"cache-tree.h\"\n #include \"commit.h\"\n #include \"hex.h\"\n@@ -86,7 +79,7 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n-int cmd__fast_rebase(int argc, const char **argv)\n+int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n@@ -102,12 +95,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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@@ -128,7 +115,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n \n-\trepo_init_revisions(the_repository, &revs, NULL);\n+\trepo_init_revisions(the_repository, &revs, prefix);\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\ndiff --git a/command-list.txt b/command-list.txt\nindex 54b2a50f5f..d74836ab21 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -160,6 +160,7 @@ git-reflog                              ancillarymanipulators           complete\n git-remote                              ancillarymanipulators           complete\n git-repack                              ancillarymanipulators           complete\n git-replace                             ancillarymanipulators           complete\n+git-replay                              mainporcelain           history\n git-request-pull                        foreignscminterface             complete\n git-rerere                              ancillaryinterrogators\n git-reset                               mainporcelain           history\ndiff --git a/git.c b/git.c\nindex ae2134f29a..e4ce39fd4d 100644\n--- a/git.c\n+++ b/git.c\n@@ -571,6 +571,7 @@ static struct cmd_struct commands[] = {\n \t{ \"remote-fd\", cmd_remote_fd, NO_PARSEOPT },\n \t{ \"repack\", cmd_repack, RUN_SETUP },\n \t{ \"replace\", cmd_replace, RUN_SETUP },\n+\t{ \"replay\", cmd_replay, RUN_SETUP },\n \t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t{ \"reset\", cmd_reset, RUN_SETUP },\n \t{ \"restore\", cmd_restore, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex abe8a785eb..9ca1586de7 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -30,7 +30,6 @@ static struct test_cmd cmds[] = {\n \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n \t{ \"env-helper\", cmd__env_helper },\n \t{ \"example-decorate\", cmd__example_decorate },\n-\t{ \"fast-rebase\", cmd__fast_rebase },\n \t{ \"fsmonitor-client\", cmd__fsmonitor_client },\n \t{ \"genrandom\", cmd__genrandom },\n \t{ \"genzeros\", cmd__genzeros },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ea2672436c..a03bbfc6b2 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -24,7 +24,6 @@ int cmd__dump_untracked_cache(int argc, const char **argv);\n int cmd__dump_reftable(int argc, const char **argv);\n int cmd__env_helper(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__fsmonitor_client(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n int cmd__genzeros(int argc, const char **argv);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex d02fa16614..40505c9054 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,8 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -140,8 +139,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -199,8 +197,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -276,8 +273,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -353,8 +349,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n \n \t\tgrep CONFLICT..rename/rename output &&\n \n@@ -455,8 +450,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -521,8 +515,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -623,8 +616,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -681,8 +673,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474979","messageId":"20230407072415.1360068-4-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 03/14] replay: die() instead of failing assert()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:04Z","receivedAt":"2023-04-07T07:25:20Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIt's not a good idea for regular Git commands to use an assert() to\ncheck for things that could happen but are not supported.\n\nLet's die() with an explanation of the issue instead.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex ca931dee41..16efac11ab 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -170,7 +170,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\n+\t\tif (!commit->parents)\n+\t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n+\t\tif (commit->parents->next)\n+\t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n+\n \t\tbase = commit->parents->item;\n \n \t\tnext_tree = get_commit_tree(commit);\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474980","messageId":"20230407072415.1360068-7-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 06/14] replay: add an important FIXME comment about gpg signing","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:07Z","receivedAt":"2023-04-07T07:25:21Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want to be able to handle signed commits in some way in the future,\nbut we are not ready to do it now. So for the time being let's just add\na FIXME comment to remind us about it.\n\nThese are different ways we could handle them:\n\n  - in case of a cli user and if there was an interactive mode, we could\n    perhaps ask if the user wants to sign again\n  - we could add an option to just fail if there are signed commits\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 59ebbc3b8f..ffd7ce1fde 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -54,7 +54,7 @@ static struct commit *create_commit(struct tree *tree,\n \tstruct object *obj;\n \tstruct commit_list *parents = NULL;\n \tchar *author;\n-\tchar *sign_commit = NULL;\n+\tchar *sign_commit = NULL; /* FIXME: cli users might want to sign again */\n \tstruct commit_extra_header *extra;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *out_enc = get_commit_output_encoding();\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474981","messageId":"20230407072415.1360068-5-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 04/14] replay: introduce pick_regular_commit()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:05Z","receivedAt":"2023-04-07T07:25:23Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's refactor the code to handle a regular commit (a commit that is\nneither a root commit nor a merge commit) into a single function instead\nof keeping it inside cmd_replay().\n\nThis is good for separation of concerns, and this will help further work\nin the future to replay merge commits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 54 ++++++++++++++++++++++++++++++------------------\n 1 file changed, 34 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 16efac11ab..ea12d4c8fe 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -80,6 +80,35 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+static struct commit *pick_regular_commit(struct commit *pickme,\n+\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  struct merge_options *merge_opt,\n+\t\t\t\t\t  struct merge_result *result)\n+{\n+\tstruct commit *base;\n+\tstruct tree *pickme_tree, *base_tree;\n+\n+\tbase = pickme->parents->item;\n+\n+\tpickme_tree = get_commit_tree(pickme);\n+\tbase_tree = get_commit_tree(base);\n+\n+\tmerge_opt->branch2 = short_commit_name(pickme);\n+\tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n+\n+\tmerge_incore_nonrecursive(merge_opt,\n+\t\t\t\t  base_tree,\n+\t\t\t\t  result->tree,\n+\t\t\t\t  pickme_tree,\n+\t\t\t\t  result);\n+\n+\tfree((char*)merge_opt->ancestor);\n+\tmerge_opt->ancestor = NULL;\n+\tif (!result->clean)\n+\t\treturn NULL;\n+\treturn create_commit(result->tree, pickme, last_commit);\n+}\n+\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n@@ -91,7 +120,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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 tree *head_tree;\n \tstruct merge_result result;\n \tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n@@ -166,7 +195,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tresult.tree = head_tree;\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *base;\n+\t\tstruct commit *pick;\n \n \t\tfprintf(stderr, \"Rebasing %s...\\r\",\n \t\t\toid_to_hex(&commit->object.oid));\n@@ -176,26 +205,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \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\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!pick)\n \t\t\tbreak;\n+\t\tlast_commit = pick;\n \t\tlast_picked_commit = commit;\n-\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n \tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474982","messageId":"20230407072415.1360068-6-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 05/14] replay: don't simplify history","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:06Z","receivedAt":"2023-04-07T07:25:25Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's set the rev walking options we need after calling\nsetup_revisions() instead of before. This makes it clearer which options\nwe need.\n\nAlso we don't want history simplification, as we want to deal with all\nthe commits in the affected range.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 15 ++++++---------\n 1 file changed, 6 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex ea12d4c8fe..59ebbc3b8f 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -164,15 +164,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \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-\n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n@@ -180,6 +171,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \n+\t/* requirements/overrides for revs */\n+\trevs.reverse = 1;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\trevs.simplify_history = 0;\n+\n \tstrvec_clear(&rev_walk_args);\n \n \tif (prepare_revision_walk(&revs) < 0) {\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474983","messageId":"20230407072415.1360068-9-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 08/14] replay: remove HEAD related sanity check","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:09Z","receivedAt":"2023-04-07T07:25:26Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want replay to be a command that can be used on the server side on\nany branch, not just the current one, so we are going to stop updating\nHEAD in a future commit.\n\nA \"sanity check\" that makes sure we are replaying the current branch\ndoesn't make sense anymore. Let's remove it.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 8 +-------\n 1 file changed, 1 insertion(+), 7 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 0a23ea70dc..a331887d12 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -114,7 +114,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct strvec rev_walk_args = STRVEC_INIT;\n \tstruct rev_info revs;\n@@ -153,11 +152,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\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 \trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n@@ -232,7 +226,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n \t\t\t       &last_commit->object.oid,\n-\t\t\t       &head,\n+\t\t\t       &onto->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n \t\t\terror(_(\"could not update %s\"), argv[2]);\n \t\t\tdie(\"Failed to update %s\", argv[2]);\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474984","messageId":"20230407072415.1360068-8-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 07/14] replay: remove progress and info output","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:08Z","receivedAt":"2023-04-07T07:25:28Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command will be changed in a follow up commit, so that it\nwill not update refs directly, but instead it will print on stdout a\nlist of commands that can be consumed by `git update-ref --stdin`.\n\nWe don't want this output to be polluted by its current low value\noutput, so let's just remove the latter.\n\nIn the future, when the command gets an option to update refs by\nitself, it will make a lot of sense to display a progress meter, but\nwe are not there yet.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 +------\n 1 file changed, 1 insertion(+), 6 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex ffd7ce1fde..0a23ea70dc 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -186,7 +186,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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.show_rename_progress = 0;\n \tmerge_opt.branch1 = \"HEAD\";\n \thead_tree = get_commit_tree(onto);\n \tresult.tree = head_tree;\n@@ -194,9 +194,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \twhile ((commit = get_revision(&revs))) {\n \t\tstruct commit *pick;\n \n-\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n-\t\t\toid_to_hex(&commit->object.oid));\n-\n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n@@ -215,7 +212,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\texit(128);\n \n \tif (result.clean) {\n-\t\tfprintf(stderr, \"\\nDone.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n \t\t\t    oid_to_hex(&last_commit->object.oid));\n@@ -232,7 +228,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tprime_cache_tree(the_repository, the_repository->index,\n \t\t\t\t result.tree);\n \t} else {\n-\t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474985","messageId":"20230407072415.1360068-10-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 09/14] replay: very coarse worktree updating","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:10Z","receivedAt":"2023-04-07T07:25:30Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn case of conflict, let's just update the worktree and index. And then\nlet's just die() as this command doesn't have ways to handle conflicts\nyet.\n\nNote that we might want to improve this behavior in the case of a bare\nrepository in a future commit.\n\nWe also have to lock the index only after all the commits have been\npicked, so that in case of conflict the index is not already locked.\nNote that this locking of the index will be removed in a following\ncommit as we will not want to modify it anymore.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 35 +++++++++++++++++++++++++++++------\n 1 file changed, 29 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex a331887d12..9c795c05a7 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -152,10 +152,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index(the_repository) < 0)\n-\t\tBUG(\"Could not read index\");\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n@@ -194,12 +190,39 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n \t\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n-\t\tif (!pick)\n-\t\t\tbreak;\n+\t\tif (!pick) {\n+\t\t\t/* TODO: handle conflicts in sparse worktree instead */\n+\t\t\tstruct object_id head;\n+\t\t\tstruct tree *head_tree;\n+\t\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\t\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n+\t\t\tif (repo_read_index(the_repository) < 0)\n+\t\t\t\tBUG(\"Could not read index\");\n+\n+\t\t\tget_oid(\"HEAD\", &head);\n+\t\t\thead_tree = parse_tree_indirect(&head);\n+\t\t\tprintf(\"Switching from %s to %s.\\n\",\n+\t\t\t       oid_to_hex(&head_tree->object.oid),\n+\t\t\t       oid_to_hex(&result.tree->object.oid));\n+\t\t\tmerge_switch_to_result(&merge_opt, head_tree, &result,\n+\t\t\t\t\t       1, 1);\n+\t\t\tif (write_locked_index(&the_index, &lock,\n+\t\t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n+\t\t\t\tdie(_(\"unable to write %s\"), get_index_file());\n+\n+\t\t\tdie(_(\"failure to pick %s; cannot handle conflicts yet\"),\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\t}\n+\n \t\tlast_commit = pick;\n \t\tlast_picked_commit = commit;\n \t}\n \n+\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tBUG(\"Could not read index\");\n+\n \tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n \n \tif (result.clean < 0)\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474986","messageId":"20230407072415.1360068-13-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 12/14] replay: introduce guess_new_base()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:13Z","receivedAt":"2023-04-07T07:25:31Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn many cases the `--onto` option is not necessary as we can guess the\nbranch we would like to replay onto.\n\nSo let's introduce guess_new_base() for that purpose and make `--onto`\noptional.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt |  8 ++++-\n builtin/replay.c             | 61 +++++++++++++++++++++++++++++++-----\n 2 files changed, 61 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 7a83f70343..ce2cafc42e 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n SYNOPSIS\n --------\n [verse]\n-'git replay' --onto <newbase> <revision-range>...\n+'git replay' [--onto <newbase>] <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -20,6 +20,12 @@ references.  However, the output of this command is meant to be used\n as input to `git update-ref --stdin`, which would update the relevant\n branches.\n \n+When the `--onto <newbase>` option is not passed, the commits will be\n+replayed onto a base guessed from the `<revision-range>`.  For example\n+if the `<revision-range>` is `origin/main..mybranch` then `mybranch`\n+was probably based on an old version of `origin/main`, so we will\n+replay it on the newest version of that branch.\n+\n OPTIONS\n -------\n \ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 63513ea6f1..af948af73c 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -75,6 +75,54 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+static struct commit *guess_new_base(struct rev_cmdline_info *info)\n+{\n+\tstruct commit *new_base = NULL;\n+\tint i, bottom_commits = 0;\n+\n+\t/*\n+\t * When the user specifies e.g.\n+\t *   git replay origin/main..mybranch\n+\t *   git replay ^origin/next mybranch1 mybranch2\n+\t * we want to be able to determine where to replay the commits.  In\n+\t * these examples, the branches are probably based on an old version\n+\t * of either origin/main or origin/next, so we want to replay on the\n+\t * newest version of that branch.  In contrast we would want to error\n+\t * out if they ran\n+\t *   git replay ^origin/master ^origin/next mybranch\n+\t *   git replay mybranch~2..mybranch\n+\t * the first of those because there's no unique base to choose, and\n+\t * the second because they'd likely just be replaying commits on top\n+\t * of the same commit and not making any difference.\n+\t */\n+\tfor (i = 0; i < info->nr; i++) {\n+\t\tstruct rev_cmdline_entry *e = info->rev + i;\n+\t\tstruct object_id oid;\n+\t\tchar *fullname = NULL;\n+\n+\t\tif (!(e->flags & BOTTOM))\n+\t\t\tcontinue;\n+\n+\t\t/*\n+\t\t * We need a unique base commit to know where to replay; error\n+\t\t * out if not unique.\n+\t\t *\n+\t\t * Also, we usually don't want to replay commits on the same\n+\t\t * base they started on, so only accept this as the base if\n+\t\t * it uniquely names some ref.\n+\t\t */\n+\t\tif (bottom_commits++ ||\n+\t\t    dwim_ref(e->name, strlen(e->name), &oid, &fullname, 0) != 1)\n+\t\t\tdie(_(\"cannot determine where to replay commits; please specify --onto\"));\n+\n+\t\tfree(fullname);\n+\t\tnew_base = lookup_commit_reference_gently(the_repository,\n+\t\t\t\t\t\t\t  &e->item->oid, 1);\n+\t}\n+\n+\treturn new_base;\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n \t\t\t\t\t  struct commit *last_commit,\n \t\t\t\t\t  struct merge_options *merge_opt,\n@@ -117,7 +165,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n+\t\tN_(\"git replay [--onto <newbase>] <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -130,12 +178,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n \t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n \n-\tif (!onto_name) {\n-\t\terror(_(\"option --onto is mandatory\"));\n-\t\tusage_with_options(replay_usage, replay_options);\n-\t}\n-\n-\tonto = peel_committish(onto_name);\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n@@ -151,6 +193,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n+\tif (onto_name)\n+\t\tonto = peel_committish(onto_name);\n+\telse\n+\t\tonto = guess_new_base(&revs.cmdline);\n+\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474987","messageId":"20230407072415.1360068-11-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 10/14] replay: make it a minimal server side command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:11Z","receivedAt":"2023-04-07T07:25:32Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want this command to be a minimal command that just does server side\npicking of commits, displaying the results on stdout for higher level\nscripts to consume.\n\nSo let's simplify it:\n  * remove the worktree and index reading/writing,\n  * remove the ref (and reflog) updating,\n  * remove the assumptions tying us to HEAD, since (a) this is not a\n    rebase and (b) we want to be able to pick commits in a bare repo,\n    i.e. to/from branches that are not checked out and not the main\n    branch,\n  * remove unneeded includes,\n  * handle rebasing multiple branches by printing on stdout the update\n    ref commands that should be performed.\n\nThe output can be piped into `git update-ref --stdin` for the ref\nupdates to happen.\n\nIn the future to make it easier for users to use this command\ndirectly maybe an option can be added to automatically pipe its output\ninto `git update-ref`.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c                         | 86 ++++++++----------------\n t/t6429-merge-sequence-rename-caching.sh | 32 ++++++---\n 2 files changed, 52 insertions(+), 66 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 9c795c05a7..119cfecfe7 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -6,17 +6,13 @@\n #include \"git-compat-util.h\"\n \n #include \"builtin.h\"\n-#include \"cache-tree.h\"\n-#include \"commit.h\"\n #include \"hex.h\"\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n #include \"parse-options.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@@ -93,6 +89,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tpickme_tree = get_commit_tree(pickme);\n \tbase_tree = get_commit_tree(base);\n \n+\tmerge_opt->branch1 = short_commit_name(last_commit);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -113,15 +110,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct lock_file lock = LOCK_INIT;\n+\tstruct commit *last_commit = NULL;\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 *head_tree;\n \tstruct merge_result result;\n-\tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n@@ -177,20 +171,19 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\tmerge_opt.branch1 = \"HEAD\";\n-\thead_tree = get_commit_tree(onto);\n-\tresult.tree = head_tree;\n+\tresult.tree = get_commit_tree(onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *pick;\n+\t\tconst struct name_decoration *decoration;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n-\t\tif (!pick) {\n+\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\n+\t\tif (!last_commit) {\n \t\t\t/* TODO: handle conflicts in sparse worktree instead */\n \t\t\tstruct object_id head;\n \t\t\tstruct tree *head_tree;\n@@ -215,54 +208,31 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&commit->object.oid));\n \t\t}\n \n-\t\tlast_commit = pick;\n-\t\tlast_picked_commit = commit;\n-\t}\n-\n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index(the_repository) < 0)\n-\t\tBUG(\"Could not read index\");\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-\tif (result.clean) {\n-\t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n-\t\t\t    oid_to_hex(&last_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, branch_name.buf,\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &last_picked_commit->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n-\t\t\tdie(_(\"unable to update HEAD\"));\n-\n-\t\tprime_cache_tree(the_repository, the_repository->index,\n-\t\t\t\t result.tree);\n-\t} else {\n-\t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &onto->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n+\t\tdecoration = get_name_decoration(&commit->object);\n+\t\tif (!decoration)\n+\t\t\tcontinue;\n+\n+\t\twhile (decoration) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\t\tprintf(\"update %s %s %s\\n\",\n+\t\t\t\t       decoration->name,\n+\t\t\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t\t\t       oid_to_hex(&commit->object.oid));\n+\t\t\t}\n+\t\t\tdecoration = decoration->next;\n \t\t}\n \t}\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 \n-\tret = (result.clean == 0);\n+\t/* Cleanup */\n+\tmerge_finalize(&merge_opt, &result);\n+\tret = result.clean;\n+\n cleanup:\n-\tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n-\treturn ret;\n+\n+\t/* Return */\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret ? 0 : 1;\n }\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 40505c9054..bfdf7f30b3 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,7 +71,9 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -139,7 +141,9 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -197,7 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -273,7 +279,9 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -450,7 +458,9 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -515,7 +525,9 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -616,7 +628,9 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -673,7 +687,9 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474988","messageId":"20230407072415.1360068-15-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 14/14] replay: stop assuming replayed branches do not diverge","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:15Z","receivedAt":"2023-04-07T07:25:35Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command is able to replay multiple branches but when some of\nthem are based on other replayed branches, their commit should be\nreplayed onto already replayed commits.\n\nFor this purpose, let's store the replayed commit and its original\ncommit in a key value store, so that we can easily find and reused a\nreplayed commit instead of the original one.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 44 ++++++++++++++++++++++++++--------\n t/t3650-replay-basics.sh | 52 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 86 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 63b3ad518e..71815d5ca3 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -216,20 +216,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n \tstrset_clear(&rinfo.positive_refs);\n }\n \n+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,\n+\t\t\t\t    struct commit *commit,\n+\t\t\t\t    struct commit *fallback)\n+{\n+\tkhint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);\n+\tif (pos == kh_end(replayed_commits))\n+\t\treturn fallback;\n+\treturn kh_value(replayed_commits, pos);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n-\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  kh_oid_map_t *replayed_commits,\n+\t\t\t\t\t  struct commit *onto,\n \t\t\t\t\t  struct merge_options *merge_opt,\n \t\t\t\t\t  struct merge_result *result)\n {\n-\tstruct commit *base;\n+\tstruct commit *base, *replayed_base;\n \tstruct tree *pickme_tree, *base_tree;\n \n \tbase = pickme->parents->item;\n+\treplayed_base = mapped_commit(replayed_commits, base, onto);\n \n+\tresult->tree = get_commit_tree(replayed_base);\n \tpickme_tree = get_commit_tree(pickme);\n \tbase_tree = get_commit_tree(base);\n \n-\tmerge_opt->branch1 = short_commit_name(last_commit);\n+\tmerge_opt->branch1 = short_commit_name(replayed_base);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -243,7 +256,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tmerge_opt->ancestor = NULL;\n \tif (!result->clean)\n \t\treturn NULL;\n-\treturn create_commit(result->tree, pickme, last_commit);\n+\treturn create_commit(result->tree, pickme, replayed_base);\n }\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n@@ -259,6 +272,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n \tstruct strset *update_refs = NULL;\n+\tkh_oid_map_t *replayed_commits;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n@@ -312,18 +326,20 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\n-\tresult.tree = get_commit_tree(onto);\n \tlast_commit = onto;\n+\treplayed_commits = kh_init_oid_map();\n \twhile ((commit = get_revision(&revs))) {\n \t\tconst struct name_decoration *decoration;\n+\t\tkhint_t pos;\n+\t\tint hr;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tlast_commit = pick_regular_commit(commit, replayed_commits, onto,\n+\t\t\t\t\t\t  &merge_opt, &result);\n \n \t\tif (!last_commit) {\n \t\t\t/* TODO: handle conflicts in sparse worktree instead */\n@@ -350,6 +366,13 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&commit->object.oid));\n \t\t}\n \n+\t\t/* Record commit -> last_commit mapping */\n+\t\tpos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);\n+\t\tif (hr == 0)\n+\t\t\tBUG(\"Duplicate rewritten commit: %s\\n\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tkh_value(replayed_commits, pos) = last_commit;\n+\n \t\t/* Update any necessary branches */\n \t\tif (advance_name)\n \t\t\tcontinue;\n@@ -379,13 +402,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \t/* Cleanup */\n \tmerge_finalize(&merge_opt, &result);\n-\tret = result.clean;\n-\n-cleanup:\n+\tkh_destroy_oid_map(replayed_commits);\n \tif (update_refs) {\n \t\tstrset_clear(update_refs);\n \t\tfree(update_refs);\n \t}\n+\tret = result.clean;\n+\n+cleanup:\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 976032ad18..b13bb7502c 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -105,4 +105,56 @@ test_expect_success 'using replay to also rebase a contained branch' '\n \ttest_cmp expect result\n '\n \n+test_expect_success 'using replay to rebase multiple divergent branches' '\n+\tgit replay --onto main ^topic1 topic2 topic4 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines J I M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\tprintf \"update refs/heads/topic4 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic4 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay to rebase multiple divergent branches, including contained ones' '\n+\tgit replay --contained --onto main ^main topic2 topic3 topic4 >result &&\n+\n+\ttest_line_count = 4 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\t>expect &&\n+\tfor i in 2 1 3 4\n+\tdo\n+\t\tprintf \"update refs/heads/topic$i \" >>expect &&\n+\t\tprintf \"%s \" $(grep topic$i result | cut -f 3 -d \" \") >>expect &&\n+\t\tgit rev-parse topic$i >>expect || return 1\n+\tdone &&\n+\n+\ttest_cmp expect result &&\n+\n+\ttest_write_lines F C M L B A >expect1 &&\n+\ttest_write_lines E D C M L B A >expect2 &&\n+\ttest_write_lines H G F C M L B A >expect3 &&\n+\ttest_write_lines J I M L B A >expect4 &&\n+\n+\tfor i in 1 2 3 4\n+\tdo\n+\t\tgit log --format=%s $(grep topic$i result | cut -f 3 -d \" \") >actual &&\n+\t\ttest_cmp expect$i actual || return 1\n+\tdone\n+'\n+\n test_done\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474989","messageId":"20230407072415.1360068-12-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 11/14] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:12Z","receivedAt":"2023-04-07T07:25:36Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of the fixed \"<oldbase> <branch>\" arguments, the replay\ncommand now accepts \"<revision-range>...\" arguments in a similar\nway as many other Git commands. This makes its interface more\nstandard and more flexible.\n\nAlso as the interface of the command is now mostly finalized,\nwe can add some documentation as well as testcases to make sure\nthe command will continue to work as designed in the future.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             | 86 ++++++++++++++++++++++++\n builtin/replay.c                         | 21 ++----\n t/t3650-replay-basics.sh                 | 63 +++++++++++++++++\n t/t6429-merge-sequence-rename-caching.sh | 18 ++---\n 4 files changed, 162 insertions(+), 26 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100755 t/t3650-replay-basics.sh\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nnew file mode 100644\nindex 0000000000..7a83f70343\n--- /dev/null\n+++ b/Documentation/git-replay.txt\n@@ -0,0 +1,86 @@\n+git-replay(1)\n+=============\n+\n+NAME\n+----\n+git-replay - Replay commits on a different base, without touching working tree\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git replay' --onto <newbase> <revision-range>...\n+\n+DESCRIPTION\n+-----------\n+\n+Takes a range of commits, and replays them onto a new location.  Does\n+not touch the working tree or index, and does not update any\n+references.  However, the output of this command is meant to be used\n+as input to `git update-ref --stdin`, which would update the relevant\n+branches.\n+\n+OPTIONS\n+-------\n+\n+--onto <newbase>::\n+\tStarting point at which to create the new commits.  May be any\n+\tvalid commit, and not just an existing branch name.\n++\n+The update-ref commands in the output will update the branch(es)\n+in the revision range to point at the new commits (in other\n+words, this mimics a rebase operation).\n+\n+<revision-range>::\n+\tRange of commits to replay; see \"Specifying Ranges\" in\n+\tlinkgit:git-rev-parse.\n+\n+OUTPUT\n+------\n+\n+When there are no conflicts, the output of this command is usable as\n+input to `git update-ref --stdin`.  It is basically of the form:\n+\n+\tupdate refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+\tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+\tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+\n+where the number of refs updated depend on the arguments passed.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted replay, the exit status is 0.  When\n+the replay has conflicts, the exit status is 1.  If the replay is not\n+able to complete (or start) due to some kind of error, the exit status\n+is something other than 0 or 1.\n+\n+EXAMPLES\n+--------\n+\n+To simply rebase mybranch onto target:\n+\n+------------\n+$ git replay --onto target origin/main..mybranch\n+update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n+------------\n+\n+When calling `git replay`, one does not need to specify a range of\n+commits to replay using the syntax `A..B`; any range expression will\n+do:\n+\n+------------\n+$ git replay --onto origin/main ^base branch1 branch2 branch3\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+------------\n+\n+This will simultaneously rebase branch1, branch2, and branch3 -- all\n+commits they have since base, playing them on top of origin/main.\n+These three branches may have commits on top of base that they have in\n+common, but that does not need to be the case.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 119cfecfe7..63513ea6f1 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -12,7 +12,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"strvec.h\"\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -111,16 +110,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL;\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 merge_result result;\n-\tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -138,20 +135,13 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tif (argc != 3) {\n-\t\terror(_(\"bad number of arguments\"));\n-\t\tusage_with_options(replay_usage, replay_options);\n-\t}\n-\n \tonto = peel_committish(onto_name);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n-\n-\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n-\t\tret = error(_(\"unhandled options\"));\n+\targc = setup_revisions(argc, argv, &revs, NULL);\n+\tif (argc > 1) {\n+\t\tret = error(_(\"unrecognized argument: %s\"), argv[1]);\n \t\tgoto cleanup;\n \t}\n \n@@ -161,8 +151,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n-\tstrvec_clear(&rev_walk_args);\n-\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -228,7 +216,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tret = result.clean;\n \n cleanup:\n-\tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nnew file mode 100755\nindex 0000000000..f55b71763a\n--- /dev/null\n+++ b/t/t3650-replay-basics.sh\n@@ -0,0 +1,63 @@\n+#!/bin/sh\n+\n+test_description='basic git replay tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+GIT_AUTHOR_NAME=author@name\n+GIT_AUTHOR_EMAIL=bogus@email@address\n+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\n+\tgit switch -c topic1 &&\n+\ttest_commit C &&\n+\tgit switch -c topic2 &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit switch topic1 &&\n+\ttest_commit F &&\n+\tgit switch -c topic3 &&\n+\ttest_commit G &&\n+\ttest_commit H &&\n+\tgit switch -c topic4 main &&\n+\ttest_commit I &&\n+\ttest_commit J &&\n+\n+\tgit switch -c next main &&\n+\ttest_commit K &&\n+\tgit merge -m \"Merge topic1\" topic1 &&\n+\tgit merge -m \"Merge topic2\" topic2 &&\n+\tgit merge -m \"Merge topic3\" topic3 &&\n+\t>evil &&\n+\tgit add evil &&\n+\tgit commit --amend &&\n+\tgit merge -m \"Merge topic4\" topic4 &&\n+\n+\tgit switch main &&\n+\ttest_commit L &&\n+\ttest_commit M\n+'\n+\n+test_expect_success 'using replay to rebase two branches, one on top of other' '\n+\tgit replay --onto main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex bfdf7f30b3..8f3c394f0e 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,7 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -201,7 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -279,7 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -357,7 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1..topic >output &&\n \n \t\tgrep CONFLICT..rename/rename output &&\n \n@@ -458,7 +458,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -525,7 +525,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -628,7 +628,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -687,7 +687,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"474990","messageId":"20230407072415.1360068-14-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH 13/14] replay: add different modes","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-04-07T07:24:14Z","receivedAt":"2023-04-07T07:25:43Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere is already a 'rebase' mode with `--onto`. Let's add an 'advance' or\n'cherry-pick' mode with `--advance`. This new mode will make the target\nbranch advance as we replay commits onto it.\n\nWhile at it, let's also add a `--contained` that can be used along with\n`--onto` to rebase all the branches contained in the <revision-range>\nargument.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt |  58 +++++++++--\n builtin/replay.c             | 185 +++++++++++++++++++++++++++++------\n t/t3650-replay-basics.sh     |  45 +++++++++\n 3 files changed, 247 insertions(+), 41 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex ce2cafc42e..d714c72188 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n SYNOPSIS\n --------\n [verse]\n-'git replay' [--onto <newbase>] <revision-range>...\n+'git replay' [--contained] [--onto <newbase> | --advance <branch>] <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -20,11 +20,12 @@ references.  However, the output of this command is meant to be used\n as input to `git update-ref --stdin`, which would update the relevant\n branches.\n \n-When the `--onto <newbase>` option is not passed, the commits will be\n-replayed onto a base guessed from the `<revision-range>`.  For example\n-if the `<revision-range>` is `origin/main..mybranch` then `mybranch`\n-was probably based on an old version of `origin/main`, so we will\n-replay it on the newest version of that branch.\n+When neither the `--onto <newbase>` option nor the\n+`--advance <branch>` option are passed, the commits will be replayed\n+onto a base guessed from the `<revision-range>`.  For example if the\n+`<revision-range>` is `origin/main..mybranch` then `mybranch` was\n+probably based on an old version of `origin/main`, so we will replay\n+it on the newest version of that branch.\n \n OPTIONS\n -------\n@@ -33,9 +34,17 @@ OPTIONS\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n +\n-The update-ref commands in the output will update the branch(es)\n-in the revision range to point at the new commits (in other\n-words, this mimics a rebase operation).\n+When `--onto` is specified, the update-ref command(s) in the output will\n+update the branch(es) in the revision range to point at the new\n+commits (in other words, this mimics a rebase operation).\n+\n+--advance <branch>::\n+\tStarting point at which to create the new commits; must be a\n+\tbranch name.\n++\n+When `--advance` is specified, the update-ref command(s) in the output\n+will update the branch passed as an argument to `--advance` to point at\n+the new commits (in other words, this mimics a cherry-pick operation).\n \n <revision-range>::\n \tRange of commits to replay; see \"Specifying Ranges\" in\n@@ -51,7 +60,10 @@ input to `git update-ref --stdin`.  It is basically of the form:\n \tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n \tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n \n-where the number of refs updated depend on the arguments passed.\n+where the number of refs updated depend on the arguments passed.  When\n+using `--advance`, the number of refs updated is always one, but for\n+`--onto`, it can be one or more (rebasing multiple branches\n+simultaneously is supported).\n \n EXIT STATUS\n -----------\n@@ -71,6 +83,32 @@ $ git replay --onto target origin/main..mybranch\n update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n ------------\n \n+To cherry-pick the commits from mybranch onto target:\n+\n+------------\n+$ git replay --advance target origin/main..mybranch\n+update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH}\n+------------\n+\n+Note that the first two examples replay the exact same commits and on\n+top of the exact same new base, they only differ in that the first\n+provides instructions to make mybranch point at the new commits and\n+the second provides instructions to make target point at them.\n+\n+What if you have a stack of branches, one depending upon another, and\n+you'd really like to rebase the whole set?\n+\n+------------\n+$ git replay --contained --onto origin/main origin/main..tipbranch\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}\n+------------\n+\n+In contrast, trying to do this with rebase would require 3 separate\n+rebases, eacho of which involves a different <ONTO> and <UPSTREAM> and\n+forces you to first check out each branch in turn.\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex af948af73c..63b3ad518e 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -12,6 +12,7 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"strmap.h\"\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -75,10 +76,24 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n-static struct commit *guess_new_base(struct rev_cmdline_info *info)\n+struct ref_info {\n+\tstruct commit *onto;\n+\tstruct strset positive_refs;\n+\tstruct strset negative_refs;\n+\tint positive_refexprs;\n+\tint negative_refexprs;\n+};\n+\n+static void get_ref_information(struct rev_cmdline_info *cmd_info,\n+\t\t\t\tstruct ref_info *ref_info)\n {\n-\tstruct commit *new_base = NULL;\n-\tint i, bottom_commits = 0;\n+\tint i;\n+\n+\tref_info->onto = NULL;\n+\tstrset_init(&ref_info->positive_refs);\n+\tstrset_init(&ref_info->negative_refs);\n+\tref_info->positive_refexprs = 0;\n+\tref_info->negative_refexprs = 0;\n \n \t/*\n \t * When the user specifies e.g.\n@@ -95,32 +110,110 @@ static struct commit *guess_new_base(struct rev_cmdline_info *info)\n \t * the second because they'd likely just be replaying commits on top\n \t * of the same commit and not making any difference.\n \t */\n-\tfor (i = 0; i < info->nr; i++) {\n-\t\tstruct rev_cmdline_entry *e = info->rev + i;\n+\tfor (i = 0; i < cmd_info->nr; i++) {\n+\t\tstruct rev_cmdline_entry *e = cmd_info->rev + i;\n \t\tstruct object_id oid;\n+\t\tconst char *refexpr = e->name;\n \t\tchar *fullname = NULL;\n+\t\tint can_uniquely_dwim = 1;\n+\n+\t\tif (*refexpr == '^')\n+\t\t\trefexpr++;\n+\t\tif (dwim_ref(refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)\n+\t\t\tcan_uniquely_dwim = 0;\n+\n+\t\tif (e->flags & BOTTOM) {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->negative_refs, fullname);\n+\t\t\tif (!ref_info->negative_refexprs)\n+\t\t\t\tref_info->onto = lookup_commit_reference_gently(the_repository,\n+\t\t\t\t\t\t\t\t\t\t&e->item->oid, 1);\n+\t\t\tref_info->negative_refexprs++;\n+\t\t} else {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->positive_refs, fullname);\n+\t\t\tref_info->positive_refexprs++;\n+\t\t}\n \n-\t\tif (!(e->flags & BOTTOM))\n-\t\t\tcontinue;\n+\t\tfree(fullname);\n+\t}\n+}\n \n+static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n+\t\t\t\t  const char *onto_name,\n+\t\t\t\t  const char **advance_name,\n+\t\t\t\t  struct commit **onto,\n+\t\t\t\t  struct strset **update_refs)\n+{\n+\tstruct ref_info rinfo;\n+\n+\tget_ref_information(cmd_info, &rinfo);\n+\tif (!rinfo.positive_refexprs)\n+\t\tdie(_(\"need some commits to replay\"));\n+\tif (onto_name && *advance_name)\n+\t\tdie(_(\"--onto and --advance are incompatible\"));\n+\telse if (onto_name) {\n+\t\t*onto = peel_committish(onto_name);\n+\t\tif (rinfo.positive_refexprs <\n+\t\t    strset_get_size(&rinfo.positive_refs))\n+\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t} else if (*advance_name) {\n+\t\tstruct object_id oid;\n+\t\tchar *fullname = NULL;\n+\n+\t\t*onto = peel_committish(*advance_name);\n+\t\tif (dwim_ref(*advance_name, strlen(*advance_name),\n+\t\t\t     &oid, &fullname, 0) == 1) {\n+\t\t\t*advance_name = fullname;\n+\t\t} else {\n+\t\t\tdie(_(\"argument to --advance must be a reference\"));\n+\t\t}\n+\t\tif (rinfo.positive_refexprs > 1)\n+\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\t} else {\n+\t\tint positive_refs_complete = (\n+\t\t\trinfo.positive_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.positive_refs));\n+\t\tint negative_refs_complete = (\n+\t\t\trinfo.negative_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.negative_refs));\n \t\t/*\n-\t\t * We need a unique base commit to know where to replay; error\n-\t\t * out if not unique.\n-\t\t *\n-\t\t * Also, we usually don't want to replay commits on the same\n-\t\t * base they started on, so only accept this as the base if\n-\t\t * it uniquely names some ref.\n+\t\t * We need either positive_refs_complete or\n+\t\t * negative_refs_complete, but not both.\n \t\t */\n-\t\tif (bottom_commits++ ||\n-\t\t    dwim_ref(e->name, strlen(e->name), &oid, &fullname, 0) != 1)\n-\t\t\tdie(_(\"cannot determine where to replay commits; please specify --onto\"));\n-\n-\t\tfree(fullname);\n-\t\tnew_base = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t  &e->item->oid, 1);\n+\t\tif (rinfo.negative_refexprs > 0 &&\n+\t\t    positive_refs_complete == negative_refs_complete)\n+\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\tif (negative_refs_complete) {\n+\t\t\tstruct hashmap_iter iter;\n+\t\t\tstruct strmap_entry *entry;\n+\n+\t\t\tif (rinfo.negative_refexprs == 0)\n+\t\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t\t\telse if (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\t\telse if (rinfo.positive_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\n+\t\t\t/* Only one entry, but we have to loop to get it */\n+\t\t\tstrset_for_each_entry(&rinfo.negative_refs,\n+\t\t\t\t\t      &iter, entry) {\n+\t\t\t\t*advance_name = entry->key;\n+\t\t\t}\n+\t\t} else { /* positive_refs_complete */\n+\t\t\tif (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine correct base for --onto\"));\n+\t\t\tif (rinfo.negative_refexprs == 1)\n+\t\t\t\t*onto = rinfo.onto;\n+\t\t}\n \t}\n-\n-\treturn new_base;\n+\tif (!*advance_name) {\n+\t\t*update_refs = xcalloc(1, sizeof(**update_refs));\n+\t\t**update_refs = rinfo.positive_refs;\n+\t\tmemset(&rinfo.positive_refs, 0, sizeof(**update_refs));\n+\t}\n+\tstrset_clear(&rinfo.negative_refs);\n+\tstrset_clear(&rinfo.positive_refs);\n }\n \n static struct commit *pick_regular_commit(struct commit *pickme,\n@@ -155,29 +248,41 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n-\tstruct commit *onto;\n+\tconst char *advance_name = NULL;\n+\tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL;\n+\tint contained = 0;\n+\n \tstruct rev_info revs;\n+\tstruct commit *last_commit = NULL;\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n+\tstruct strset *update_refs = NULL;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay [--onto <newbase>] <revision-range>...\"),\n+\t\tN_(\"git replay [--contained] [--onto <newbase> | --advance <branch>] <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"advance\", &advance_name,\n+\t\t\t   N_(\"branch\"),\n+\t\t\t   N_(\"make replay advance given branch\")),\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_BOOL(0, \"contained\", &contained,\n+\t\t\t N_(\"advance all branches contained in revision-range\")),\n \t\tOPT_END()\n \t};\n \n \targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n \t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n \n+\tif (advance_name && contained)\n+\t\tdie(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t    \"--advance\", \"--contained\");\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n@@ -193,10 +298,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n-\tif (onto_name)\n-\t\tonto = peel_committish(onto_name);\n-\telse\n-\t\tonto = guess_new_base(&revs.cmdline);\n+\tdetermine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n+\t\t\t      &onto, &update_refs);\n+\n+\tif (!onto) /* FIXME: Should handle replaying down to root commit */\n+\t\tdie(\"Replaying down to root commit is not supported yet!\");\n \n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n@@ -206,6 +312,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n+\n \tresult.tree = get_commit_tree(onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n@@ -243,12 +350,16 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&commit->object.oid));\n \t\t}\n \n+\t\t/* Update any necessary branches */\n+\t\tif (advance_name)\n+\t\t\tcontinue;\n \t\tdecoration = get_name_decoration(&commit->object);\n \t\tif (!decoration)\n \t\t\tcontinue;\n-\n \t\twhile (decoration) {\n-\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n+\t\t\t    (contained || strset_contains(update_refs,\n+\t\t\t\t\t\t\t  decoration->name))) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\n@@ -258,11 +369,23 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\t/* In --advance mode, advance the target ref */\n+\tif (result.clean == 1 && advance_name) {\n+\t\tprintf(\"update %s %s %s\\n\",\n+\t\t       advance_name,\n+\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t       oid_to_hex(&onto->object.oid));\n+\t}\n+\n \t/* Cleanup */\n \tmerge_finalize(&merge_opt, &result);\n \tret = result.clean;\n \n cleanup:\n+\tif (update_refs) {\n+\t\tstrset_clear(update_refs);\n+\t\tfree(update_refs);\n+\t}\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex f55b71763a..976032ad18 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -60,4 +60,49 @@ test_expect_success 'using replay to rebase two branches, one on top of other' '\n \ttest_cmp expect result\n '\n \n+test_expect_success 'using replay to perform basic cherry-pick' '\n+\t# The differences between this test and the last one are:\n+\t#   --advance vs --onto\n+\t# 2nd field of result is refs/heads/main vs. refs/heads/topic2\n+\t# 4th field of result is hash for main instead of hash for topic2\n+\n+\tgit replay --advance main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/main \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse main >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay to also rebase a contained branch' '\n+\tgit replay --contained --onto main main..topic3 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines H G F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic1 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic1 >>expect &&\n+\tprintf \"update refs/heads/topic3 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic3 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n test_done\n-- \n2.40.0.228.gb2eb5bb98e\n\n"},{"id":"475352","messageId":"25f57067-e4fe-51d5-c42c-6f95940cba27@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"Re: [PATCH 00/14] Introduce new `git replay` command","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-04-14T10:12:55Z","receivedAt":"2023-04-14T10:13:58Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Christian and Elijah\n\nOn 07/04/2023 08:24, Christian Couder wrote:\n> # Intro\n> \n> `git replay` has initially been developed entirely by Elijah Newren\n> mostly between June and July 2022 at:\n> \n> https://github.com/newren/git/commits/replay\n> \n> I took over a few months ago to polish and upstream it as GitLab is\n> interested in replacing libgit2, and for that purpose needs a command\n> to do server side (so without using a worktree) rebases, cherry-picks\n> and reverts.\n> \n> I reduced the number of commits and features in this first patch\n> series, compared to what Elijah already developed. Especially I\n> stopped short of replaying merge commits and replaying\n> interactively. These and other features might be upstreamed in the\n> future after this patch series has graduated.\n> \n> Thanks to Elijah, Patrick Steinhardt and Dscho for early reviews and\n> discussions.\n> \n> Based on ae73b2c8f1 (The seventh batch, 2023-04-04)\n\nThanks to both of you for working on this it looks very interesting. \nI've had a quick read over the patches and I ended up slightly confused \nas to exactly what the aim of this series is. My main confusion is \nwhether \"replay\" is intended to be a plumbing command or a porcelain \ncommand. The use case above suggests plumbing and there are patches that \ntake it in that direction by removing any diagnostic output and stopping \nit update any refs. But then it is marked as porcelain in \ncommand-list.txt and there are patches that do things like \nunconditionally updating the index and worktree when there are conflicts \nthat stop it working in bare repositories. I've left some comments below\n\n> # Quick Overview (from Elijah)\n> \n> `git replay`, at a basic level, can perhaps be thought of as a\n> \"default-to-dry-run rebase\" -- meaning no updates to the working tree,\n> or to the index, or to any references.  However, it differs from\n> rebase in that it:\n> \n>    * Works for branches that aren't checked out\n>    * Works in a bare repository\n>    * Can replay multiple branches simultaneously (with or without common\n>      history in the range being replayed)\n>    * Preserves relative topology by default (merges are replayed too)\n>    * Focuses on performance\n>    * Has several altered defaults as a result of the above\n> \n> I sometimes think of `git replay` as \"fast-replay\", a patch-based\n> analogue to the snapshot-based fast-export & fast-import tools.\n> \n> # Reasons for diverging from cherry-pick & rebase (from Elijah)\n> \n> There are multiple reasons to diverge from the defaults in cherry-pick and\n> rebase.\n> \n> * Server side needs\n> \n>    * Both cherry-pick and rebase, via the sequencer, are heavily tied\n>      to updating the working tree, index, some refs, and a lot of\n>      control files with every commit replayed, and invoke a mess of\n>      hooks[1] that might be hard to avoid for backward compatibility\n>      reasons (at least, that's been brought up a few times on the\n>      list).\n> \n>    * cherry-pick and rebase both fork various subprocesses\n>      unnecessarily, but somewhat intrinsically in part to ensure the\n>      same hooks are called that old scripted implementations would\n>      have called.\n\nTo clarify, since 356ee4659bb (sequencer: try to commit without forking \n'git commit', 2017-11-24) cherry-pick and rebase do not fork \nsubprocesses other than hooks for the cases covered by this patch series \n(i.e. they do not fork \"git commit\" for simple picks).\n\n>    * \"Dry run\" behavior, where there are no updates to worktree, index,\n>      or even refs might be important.\n> \n>    * Should not assume users only want to operate on HEAD (see next\n>      section)\n> \n> * Decapitate HEAD-centric assumptions\n> \n>    * cherry-pick forces commits to be played on top of HEAD; inflexible.\n> \n>    * rebase assumes the range of commits to be replayed is\n>      upstream..HEAD by default, though it allows one to replay\n>      upstream..otherbranch -- but it still forcibly and needlessly\n>      checks out otherbranch before starting to replay things.\n\nI agree it would be nice to be able to restrict the range of commits \nreplayed, especially when replaying merges. The comment about checking \nout other branch is out of date since 767a9c417eb (rebase -i: stop \nchecking out the tip of the branch to rebase, 2020-01-24)\n\n>    * Assuming HEAD is involved severely limits replaying multiple\n>      (possibly divergent) branches.\n\nI'm not sure how true this is anymore, since 89fc0b53fdb (rebase: update \nrefs from 'update-ref' commands, 2022-07-19) the sequencer can update \nmultiple branches. The issue with divergent branch is with command line \narguments and the todo list generation rather than the capabilities of \nthe sequencer.\n\n>    * Once you stop assuming HEAD has a certain meaning, there's not\n>      much reason to have two separate commands anymore (except for the\n>      funny extra not-necessarily-compatible options both have gained\n>      over time).\n\nI agree having a unified command at the plumbing level certainly makes \nsense.\n\n>    * (Micro issue: Assuming HEAD is involved also makes it harder for\n>      new users to learn what rebase means and does; it makes command\n>      lines hard to parse.\n\nThat's an interesting point, I wonder if operating on branches that are \nnot checked out is potentially confusing for new user though.\n\n>  Not sure I want to harp on this too much, as\n>      I have a suspicion I might be creating a tool for experts with\n>      complicated use cases, but it's a minor quibble.)\n> \n> * Performance\n> \n>    * jj is slaughtering us on rebase speed[2].  I would like us to become\n>      competitive.  (I dropped a few comments in the link at [2] about why\n>      git is currently so bad.)\n> \n>    * From [3], there was a simple 4-patch series in linux.git that took\n>      53 seconds to rebase.  Switching to ort dropped it to 16 seconds.\n>      While that sounds great, only 11 *milliseconds* were needed to do\n>      the actual merges.  That means almost *all* the time (>99%) was\n>      overhead!  Big offenders:\n> \n>      * --reapply-cherry-picks should be the default\n\nI agree that can be a performance hit if there are a lot of upstream \ncommits, but it is also a usability feature as it means we don't stop \nand ask the user what to do with the commits that have been upstreamed \nwhich wastes more of their time. I think maybe we want different \ndefaults for the server use case than the user replaying commits or \nperhaps default to dropping commits that become empty.\n\n>      * can_fast_forward() should be ripped out, and perhaps other extraneous\n>        revision walks\n\nWe should look at doing that at least for the merge backend which has \nskip_unnecessary_picks(). I think it is useful to tell the user that the \nbranch was not updated by the rebase though.\n\n>      * avoid updating working tree, index, refs, reflogs, and control\n>        structures except when needed (e.g. hitting a conflict, or operation\n>        finished)\n\nNot writing to disc unless we need to is sensible. Having said that for \ninteractive rebases I do find having HEAD's reflog record all the picks \nuseful to unpick what went wrong if mess something up.\n\n>    * Other performance ideas:\n> \n>      * single-file control structures instead of directory of files\n\nI like the idea as it should make it easier to keep the on disc state \nconsistent, but I'm not sure how much of an issue that is in practice as \nwe only read/write the files once each time git is run. The bigger slow \ndown is writing the author script, commit message, list of rewritten \ncommits, todo list and done files with each pick.\n\n\n>      * avoid forking subprocesses unless explicitly requested (e.g.\n>        --exec, --strategy, --run-hooks).  For example, definitely do not\n>        invoke `git commit` or `git merge`.\n\nGood, that matches what the sequencer does for non-merge commits when \nwe're not editing the commit message.\n\n>      * Sanitize hooks:\n> \n>        * dispense with all per-commit hooks for sure (pre-commit,\n>          post-commit, post-checkout).\n\nI agree we should not be running those (we don't run the pre-commit hook \nanyway). However we had a bug report when cherry-pick stopped running \nthe \"prepare-commit-msg\" hook (see \nhttps://lore.kernel.org/git/CAKdAkRQuj1hfKeckjuR2oP+8C1i+ZR36O-+aRYif4ufaS_zs+w@mail.gmail.com/). \nThat shouldn't matter for the server but we should bear it in mind when \nit comes to other use cases.\n\n>        * pre-rebase also seems to assume exactly 1 ref is written, and\n>          invoking it repeatedly would be stupid.  Plus, it's specific\n>          to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n>          rebase probably broke the pre-rebase assumptions already...)\n\nIf replay is a plumbing command then skipping the pre-rebase hook makes \nsense as scripts can call it themselves if they want to. For a porcelain \ncommand keeping a hook that can prevent it from rewriting commits  that \nare already upstream (which I think is one of the main uses of the \npre-rebase hook) would be good.\n\n>        * post-rewrite hook might make sense, but fast-import got\n>          exempted, and I think of replay like a patch-based analogue\n>          to the snapshot-based fast-import.\n\nIf we don't call the hook it would be good to have a option that outputs \nthat information so scripts can request it if they want. Also we should \nthink about if/when we want to update the notes associated with replayed \ncommits.\n\n>      * When not running server side, resolve conflicts in a sparse-cone\n>        sparse-index worktree to reduce number of files written to a\n>        working tree.  (See below as well)\n>\n>      * [High risk of possible premature optimization] Avoid large\n>        numbers of newly created loose objects, when replaying large\n>        numbers of commits.  Two possibilities: (1) Consider using\n>        tmp-objdir and pack objects from the tmp-objdir at end of\n>        exercise, (2) Lift code from git-fast-import to immediately\n>        stuff new objects into a pack?\n> \n> * Multiple branches and non-checked out branches\n> \n>    * The ability to operate on non-checked out branches also implies\n>      that we should generally be able to replay when in a dirty working\n>      tree (exception being when we expect to update HEAD and any of the\n>      dirty files is one that needs to be updated by the replay).\n> \n>    * Also, if we are operating locally on a non-checked out branch and\n>      hit a conflict, we should have a way to resolve the conflict without\n>      messing with the user's work on their current branch.\n\nThat sounds tricky to do in a user friendly way.\n\n>      * Idea: new worktree with sparse cone + sparse index checkout,\n>        containing only files in the root directory, and whatever is\n>        necessary to get the conflicts\n\nIf the user has not asked for a sparse checkout then this could be \nsurprising. Sometimes I find it helpful to be able to poke about in \nother source files when resolving a conflict. I also often build and \ntest after resolving a conflict which requires more than just the \nconflict to be checked out.\n\n>      * Companion to above idea: control structures should be written to\n>        $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n>        replay sessions, and so we know which worktrees are associated\n>        with which replay operations.\n\n\nWe certainly want some way of making sure we only update a given ref in \none replay session, and have checks to for whether the ref is checked \nout anywhere as we do now for rebase --update-refs. That seems to be \nlacking in the patches adding ref updating in this series.\n\n>    - [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/\n>    - [2] https://github.com/martinvonz/jj/discussions/49\n>    - [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/\n> \n> # Important limitations\n> \n> * The code die()s if there are any conflict. No resumability. No nice\n>    output. No interactivity.\n\nI can see that on a server you might not want any output, but if I run \nit locally it would be nice to have a message saying which paths have \nconflicts. Maybe we could add a --quiet flag for the server rather than \nremoving the existing messages?\n\n> * No replaying merges, nor root commits. Only regular commits.\n\nThat is a reasonable place to start.\n\n> * Signed commits are not properly handled. It's not clear what to do\n>    to such commits when replaying on the server side.\n\nYes on the server where you don't have access to the signing key there \nis not much need for replay to have a signing option.\n\n> # Commit overview\n> \n> * 1/14 replay: introduce new builtin\n> \n>       This creates a minimal `git replay` command by moving the code\n>       from the `fast-rebase` test helper from `t/helper/` into\n>       `builtin/` and doing some renames and a few other needed changes.\n> \n> * - 2/14 replay: start using parse_options API\n>    - 3/14 replay: die() instead of failing assert()\n>    - 4/14 replay: introduce pick_regular_commit()\n>    - 5/14 replay: don't simplify history\n>    - 6/14 replay: add an important FIXME comment about gpg signing\n>    - 7/14 replay: remove progress and info output\n>    - 8/14 replay: remove HEAD related sanity check\n> \n>       These slowly change the command to make it behave more like a\n>       regular commands and to start cleaning up its output.\n> \n> * 9/14 replay: very coarse worktree updating\n> \n>       Make it handle conflicts in a very coarse way. This might not\n>       work on bare repos, but it allows existing tests to pass and it's\n>       nice to help cli users a bit when they get conflicts.\n> \n> * 10/14 replay: make it a minimal server side command\n> \n>       After the cleaning up in previous ommits, it's now time to\n>       radically change the way it works by stopping it to do ref\n>       updates, to update the index and worktree, to consider HEAD as\n>       special. Instead just make it output commands that should be\n>       passed to `git update-ref --stdin`.\n> \n> * - 11/14 replay: use standard revision ranges\n>    - 12/14 replay: introduce guess_new_base()\n>    - 13/14 replay: add different modes\n>    - 14/14 replay: stop assuming replayed branches do not diverge\n> \n>        These finish the clean up and add new interesting features at\n>        the same time, as well as related documentation and tests.\n> \n> # Note about tests and documentation\n> \n> Note that the `fast-rebase` test helper was used before this series in\n> \n> t6429-merge-sequence-rename-caching.sh\n> \n> So when `git replay` is created from `fast-rebase` in patch 1/14, this\n> test script is also converted to use `git replay`. This ensures that\n> `git replay` doesn't break too badly during the first 10 patches in\n> this patch series.\n> \n> Tests and documentation are introduced specifically for `git replay`\n> only in 11/14 and later patches as it doesn't make much sense to\n> document and test behavior that we know is going to change soon. So\n> it's only when the command is crystalizing towards its final form that\n> we start documenting and testing it.\n> \n> # Possibly controversial issues\n> \n> * bare or not bare: this series works towards a command with the end\n>    goal of it being usable and used on bare repos, contrary to existing\n>    commands like `git rebase` and `git cherry-pick`, but the tests\n>    currently don't check that, and in case of conflicts it won't\n>    currently work on bare repos. One reason for that is that existing\n>    tests in t6429 should continue to work, and one of these tests\n>    requires some output in case of conflict. And it's nice for users to\n>    get some help in case of conflict. It's also nice for users if\n>    commands that should work on both bare and non bare repos work well\n>    on non bare repos first as they are less likely to use them on bare\n>    repos. So let's have a command that works well on non-bare repos\n>    first, even if its end goal is to work fine on bare repos too. We\n>    plan to improve things for bare repos soon after this first patch\n>    series graduates.\n> \n> * exit status: a successful, non-conflicted replay exits with code\n>    0. When the replay has conflicts, the exit status is 1. If the\n>    replay is not able to complete (or start) due to some kind of error,\n>    the exit status is something other than 0 or 1. It has been\n>    suggested in an internal review that conflicts might want to get a\n>    more specific error code as an error code of 1 might be quite easy\n>    to return by accident. It doesn't seem to me from their docs (which\n>    might want to be improved, I didn't look at the code) that other\n>    commands like `git merge` and `git rebase` exit with a special error\n>    code in case of conflict.\n\nI don't think we've ever had a special \"conflict\" error code but it \nwould be useful for scripts if replay had one. Does replay return a \ndifferent exit code for \"merge conflicts\" and \"cannot merge because it \nwould overwrite an untracked file\"? Does it have an exit code for \"the \ncommit becomes empty\" or are those patches unconditionally dropped?\n\n> * to guess or not to guess: commit 12/14 introduces the\n>    guess_new_base() function which tries to find a base to rebase onto\n>    when the --onto option is not provided, making this option actually\n>    optional instead of mandatory. Given that it's an heuristic and the\n>    command end goal is to be used on server side, we might want to\n>    introduce this as an iterative improvement later. I still think it's\n>    interesting to have it in for now though, as it shows that --onto\n>    and --advance (which is introduced in the following commit) should\n>    indeed be options. If --onto was always mandatory in the series,\n>    people could argue that it shouldn't be an option and its argument\n>    should always be the first (unconditional) argument of the command.\n\nI think it comes down to \"what's the aim of this series?\" is it focused \non the bare repository server use case or is it trying to add a general \npurpose cli tool.\n\n> * make worktree and index changes optional: commit 10/14 stops\n>    updating the index and worktree, but it might be better especially\n>    for cli users to make that optional. The issue is that this would\n>    make the command more complex while we are developing a number of\n>    important features. It seems to me that this should rather be done\n>    in an iterative improvement after the important features have\n>    landed.\n\nI'm confused by this as patch 9 seems to start updating the index and \nworktree when there are conflicts but patch 10 stops updating the index \nand worktree if the replay is successful.\n\n> * when and where to add tests and docs: although t6429 has tests that\n>    are changed to use the new command instead of the fast-rebase\n>    test-tool command as soon as the former is introduced, there is no\n>    specific test script and no doc for the new command until commit\n>    11/14 when standard revision ranges are used. This is done to avoid\n>    churn in tests and docs while the final form of the command hasn't\n>    crystalized enough. Adding tests and doc at this point makes this\n>    commit quite big and possibly more difficult to review than if they\n>    were in separate commits though. On the other hand when tests and\n>    docs are added in specific commits some reviewers say it would be\n>    better to introduce them when the related changes are made.\n> \n> * --advance and --contained: these two advanced options might not\n>    belong to this first series and could perhaps be added in a followup\n>    series in separate commits. On the other hand the code for\n>    --contained seems involved with the code of --advance and it's nice\n>    to see soon that git replay can indeed do cherry-picking and rebase\n>    many refs at once, and this way fullfil these parts of its promise.\n\nOnce I understood what these options did the names made sense, but I \ncould not tell from the names what they were going to do. For me \n\"--cherry-pick\" and \"--update-refs\" would have been clearer. It might be \nworth splitting this patch so the individual options are added separately.\n\n> * replaying diverging branches: 14/14 the last patch in the series,\n>    which allow replaying diverging branches, can be seen as a\n>    fundamental fix or alternatively as adding an interesting\n>    feature. So it's debatable if it should be in its own patch along\n>    with its own tests as in this series, or if it should be merged into\n>    a previous patch and which one.\n\nIt might make sense to add this in the same patch as you add --contained.\n\n> * only 2 patches: this patch series can be seen from a high level\n>    point of view as 1) introducing the new `git replay` command, and 2)\n>    using `git replay` to replace, and get rid of, the fast-rebase\n>    test-tool command. The fact that not much of the original\n>    fast-rebase code and interface is left would agree with that point\n>    of view. On the other hand, fast-rebase can also be seen as a first\n>    iteration towards `git replay`. So it can also make sense to see how\n>    `git replay` evolved from it.\n\nStarting with fast-rebase means one has to checkout the first patch to \nsee what code we're adding to replay.c and to make sense of the later \npatches that remove code. It would be interesting to compare this series \nto one that started from scratch but I guess that would be quite a bit \nof work.\n\nThanks for working on this, I'm interested to see where it goes\n\nBest Wishes\n\nPhillip\n\n> \n> Elijah Newren (14):\n>    replay: introduce new builtin\n>    replay: start using parse_options API\n>    replay: die() instead of failing assert()\n>    replay: introduce pick_regular_commit()\n>    replay: don't simplify history\n>    replay: add an important FIXME comment about gpg signing\n>    replay: remove progress and info output\n>    replay: remove HEAD related sanity check\n>    replay: very coarse worktree updating\n>    replay: make it a minimal server side command\n>    replay: use standard revision ranges\n>    replay: introduce guess_new_base()\n>    replay: add different modes\n>    replay: stop assuming replayed branches do not diverge\n> \n>   .gitignore                               |   1 +\n>   Documentation/git-replay.txt             | 130 +++++++\n>   Makefile                                 |   2 +-\n>   builtin.h                                |   1 +\n>   builtin/replay.c                         | 419 +++++++++++++++++++++++\n>   command-list.txt                         |   1 +\n>   git.c                                    |   1 +\n>   t/helper/test-fast-rebase.c              | 233 -------------\n>   t/helper/test-tool.c                     |   1 -\n>   t/helper/test-tool.h                     |   1 -\n>   t/t3650-replay-basics.sh                 | 160 +++++++++\n>   t/t6429-merge-sequence-rename-caching.sh |  43 ++-\n>   12 files changed, 739 insertions(+), 254 deletions(-)\n>   create mode 100644 Documentation/git-replay.txt\n>   create mode 100644 builtin/replay.c\n>   delete mode 100644 t/helper/test-fast-rebase.c\n>   create mode 100755 t/t3650-replay-basics.sh\n> \n\n"},{"id":"475380","messageId":"8daf2603-2818-9c9d-7a06-6af2872a045a@github.com","threadId":"59560","inReplyTo":"20230407072415.1360068-12-christian.couder@gmail.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2023-04-14T14:09:25Z","receivedAt":"2023-04-14T14:10:49Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/7/2023 3:24 AM, Christian Couder wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> Instead of the fixed \"<oldbase> <branch>\" arguments, the replay\n> command now accepts \"<revision-range>...\" arguments in a similar\n> way as many other Git commands. This makes its interface more\n> standard and more flexible.\n\nUnfortunately, while doing this, you have broken the --onto\nlogic:\n\n $ git replay --onto HEAD~2 HEAD~1 HEAD\n fatal: replaying down to root commit is not supported yet!\n\nThe rev-walk you are supplying by this line...\n\n> +\targc = setup_revisions(argc, argv, &revs, NULL);\n\nis taking the remaining arguments and using them as tips to\nwalk. We need to be able to recognize that --onto A B C means\nthat A is the new base and our walk is B..C.\n\nI'm not sure if there might be a way to use a callback for\nthe --onto option and pull out the next three options into\n'new-base', 'old-base', 'tip' values or something.\n\nOverall, I don't think being flexible in the CLI is of high\nvalue for this command. Let's be as prescriptive as possible.\n\nSomething like:\n\n\t'git replay [options] <base> <tip>'\n\tThis mode means to rebase <tip> onto <base>,\n\tdetecting the range of commits to rewrite.\n\n\t'git replay [options] <new-base> <old-base> <tip>'\n\tThis mode means to rebase the range <old-base>..<tip>\n\tonto <new-base>.\n\nWe don't even need \"--onto\" for these positional arguments.\n\nThanks,\n-Stolee\n"},{"id":"475381","messageId":"89c78da5-388a-e52b-b20b-e376ac90de14@github.com","threadId":"59560","inReplyTo":"8daf2603-2818-9c9d-7a06-6af2872a045a@github.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2023-04-14T14:23:08Z","receivedAt":"2023-04-14T14:23:38Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/14/2023 10:09 AM, Derrick Stolee wrote:\n> On 4/7/2023 3:24 AM, Christian Couder wrote:\n>> From: Elijah Newren <newren@gmail.com>\n>>\n>> Instead of the fixed \"<oldbase> <branch>\" arguments, the replay\n>> command now accepts \"<revision-range>...\" arguments in a similar\n>> way as many other Git commands. This makes its interface more\n>> standard and more flexible.\n> \n> Unfortunately, while doing this, you have broken the --onto\n> logic:\n> \n>  $ git replay --onto HEAD~2 HEAD~1 HEAD\n>  fatal: replaying down to root commit is not supported yet!\n> \n> The rev-walk you are supplying by this line...\n> \n>> +\targc = setup_revisions(argc, argv, &revs, NULL);\n> \n> is taking the remaining arguments and using them as tips to\n> walk. We need to be able to recognize that --onto A B C means\n> that A is the new base and our walk is B..C.\n\nI'm realizing after hitting \"send\" that this change is\nintentional (based on your test updates). I don't agree with\nit, though.\n\nSending arbitrary command-line arguments to setup_revisions()\ncreates an opportunity for behavior you are not expecting.\n\nFor instance, can users pass multiple ranges? Can users\nsupply --first-parent? What happens if they add an --author\nfilter?\n\n(I was able to get a segfault by rebasing this series with\n--author=stolee because the commit list became empty. Something\nto watch for.)\n \n> Something like:\n> \n> \t'git replay [options] <base> <tip>'\n> \tThis mode means to rebase <tip> onto <base>,\n> \tdetecting the range of commits to rewrite.\n> \n> \t'git replay [options] <new-base> <old-base> <tip>'\n> \tThis mode means to rebase the range <old-base>..<tip>\n> \tonto <new-base>.\n\nFor that reason, I think we should be using explicit argument\nparsing in the builtin and only transform arguments we\nunderstand into the setup_revisions() (by building a strvec).\n\nThanks,\n-Stolee\n"},{"id":"475395","messageId":"64398fb87548d_5d9f72942d@chronos.notmuch","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"Re: [PATCH 00/14] Introduce new `git replay` command","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2023-04-14T17:39:04Z","receivedAt":"2023-04-14T17:39:10Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Christian Couder wrote:\n> # Quick Overview (from Elijah)\n> \n> `git replay`, at a basic level, can perhaps be thought of as a\n> \"default-to-dry-run rebase\" -- meaning no updates to the working tree,\n> or to the index, or to any references.\n\nInteresting, I just ran into this problem trying to cleanup my personal git\nbranches.\n\nSimply checking which branches can be cleanly rebased on top of master takes a\nsignificant amount of time without any tricks, and using `git merge-tree` still\ntakes some time.\n\nBut the biggest offender is checking which patches have not yet been merged\ninto master, which takes 52 seconds on my machine which is by no means old.\n\n> # Reasons for diverging from cherry-pick & rebase (from Elijah)\n> \n> * Server side needs\n\nI personally don't care about the server side, but...\n\n>   * Both cherry-pick and rebase, via the sequencer, are heavily tied\n>     to updating the working tree, index, some refs, and a lot of\n>     control files with every commit replayed, and invoke a mess of\n>     hooks[1] that might be hard to avoid for backward compatibility\n>     reasons (at least, that's been brought up a few times on the\n>     list).\n\nThis is important as an end user as well.\n\nSince day 1 one of the important selling points of git was that operations that\ncould be done in milliseconds did take milliseconds.\n\nIf it can be done faster, why wouldn't I want it to be done faster?\n\n> * Decapitate HEAD-centric assumptions\n\nThat's good, but not particularly important at the moment IMO.\n\n> * Performance\n> \n>   * jj is slaughtering us on rebase speed[2].  I would like us to become\n>     competitive.  (I dropped a few comments in the link at [2] about why\n>     git is currently so bad.)\n\nIndeed.\n\n>   * From [3], there was a simple 4-patch series in linux.git that took\n>     53 seconds to rebase.\n\nI did participate in that discussion, but Uwe Kleine-König never responded back.\n\nIn [1] he clearly noticed the problem was *before* attempting to apply any\npatch. Other people mentioned the fork-point detection, but I don't think that\nwas the issue, my guess was that checking for the possibility of a fast-forward\nwas the issue.\n\nThe code was clearly doing the wrong thing for that case, but I believe it\nshould have been fixed by d42c9ffa0f (rebase: factor out branch_base\ncalculation, 2022-10-17).\n\nIt would be interesting to see if this issue can be reproduced somehow.\n\n>     Switching to ort dropped it to 16 seconds.\n\nNo, it dropped to 16 seconds it for Elijah, not Uwe. Uwe (who had the real\nrepository) noticed a big reduction of around 70%, but the discrepancy of using\n--onto versus not always remained.\n\n>     While that sounds great, only 11 *milliseconds* were needed to do\n>     the actual merges.  That means almost *all* the time (>99%) was\n>     overhead!  Big offenders:\n> \n>     * --reapply-cherry-picks should be the default\n> \n>     * can_fast_forward() should be ripped out, and perhaps other extraneous\n>       revision walks\n\nDoesn't d42c9ffa0f (rebase: factor out branch_base calculation, 2022-10-17)\ndeal with that?\n\n---\n\nI think something like this is defeinitely needed, when I rewrote `git rebase`\nto use `git cherry-pick` I noticed many areas of improvement, and I'm of the\nopinion that `git rebase` should be rewritten from scratch.\n\nBut precisely because git focuses too much on backwards compatibility (and\noften in the wrong areas), I think `git replay` should be thoroughly discussed\nbefore accepting something we could quickly realize can be substantially\nimproved.\n\nCheers.\n\n[1] https://lore.kernel.org/git/20210528214024.vw4huojcklrm6d27@pengutronix.de/\n\n-- \nFelipe Contreras"},{"id":"475434","messageId":"CABPp-BE7yD_FJuGx4KtzO5YF-LXYHFrTWSf71ttNTcyMDpZgeg@mail.gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"Re: [PATCH 00/14] Introduce new `git replay` command","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-15T06:44:51Z","receivedAt":"2023-04-15T06:51:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Apr 7, 2023 at 12:24 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> # Intro\n>\n> `git replay` has initially been developed entirely by Elijah Newren\n> mostly between June and July 2022 at:\n\n(Sidenote: actually, there was a good chunk in Jan & Feb 2022 as well,\nand various design and idea work preceding that over a long time, some\nin the form of the fast-rebase test-tool...)\n\n> https://github.com/newren/git/commits/replay\n>\n> I took over a few months ago to polish and upstream it as GitLab is\n> interested in replacing libgit2, and for that purpose needs a command\n> to do server side (so without using a worktree) rebases, cherry-picks\n> and reverts.\n>\n> I reduced the number of commits and features in this first patch\n> series, compared to what Elijah already developed. Especially I\n> stopped short of replaying merge commits and replaying\n> interactively. These and other features might be upstreamed in the\n> future after this patch series has graduated.\n\n...and also cleaned up my commits which were in a WIP state.  Thanks!  :-)\n\n> Thanks to Elijah, Patrick Steinhardt and Dscho for early reviews and\n> discussions.\n>\n> Based on ae73b2c8f1 (The seventh batch, 2023-04-04)\n>\n> # Quick Overview (from Elijah)\n\nIn particular, this comes from the replay-design-notes.txt file on the\nreplay branch, up until the footnote links.\n\n<snip>\n\nMy replay branch involved a whole bunch of aspirational ideas, work in\nprogress, and some things that worked.  As noted above, you've taken\nthis portion of the cover letter from my replay-design-notes.txt file\non that branch, but that file involved my in-progress thought\nprocesses on all these ideas.  For this series, we should probably\njust focus on the server-side usecases since other aspects just\nweren't complete enough for use.  (I also thought the server-side\naspects of git-replay weren't good enough for use either because I\nthought we'd need some conflict-related handling similar to what\ngit-merge-tree does, but both you and Dscho have said you aren't\nworried about that, and are fine with just a simple non-zero exit\nstatus.)\n\nAnyway, deleting the forward-looking stuff and concentrating on the\nserver-side replaying of commits would mean that we can at least\ndelete the portion of this cover letter starting from here...\n\n>   * Other performance ideas:\n>\n>     * single-file control structures instead of directory of files\n>\n>     * avoid forking subprocesses unless explicitly requested (e.g.\n>       --exec, --strategy, --run-hooks).  For example, definitely do not\n>       invoke `git commit` or `git merge`.\n>\n>     * Sanitize hooks:\n>\n>       * dispense with all per-commit hooks for sure (pre-commit,\n>         post-commit, post-checkout).\n>\n>       * pre-rebase also seems to assume exactly 1 ref is written, and\n>         invoking it repeatedly would be stupid.  Plus, it's specific\n>         to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n>         rebase probably broke the pre-rebase assumptions already...)\n>\n>       * post-rewrite hook might make sense, but fast-import got\n>         exempted, and I think of replay like a patch-based analogue\n>         to the snapshot-based fast-import.\n>\n>     * When not running server side, resolve conflicts in a sparse-cone\n>       sparse-index worktree to reduce number of files written to a\n>       working tree.  (See below as well)\n>\n>     * [High risk of possible premature optimization] Avoid large\n>       numbers of newly created loose objects, when replaying large\n>       numbers of commits.  Two possibilities: (1) Consider using\n>       tmp-objdir and pack objects from the tmp-objdir at end of\n>       exercise, (2) Lift code from git-fast-import to immediately\n>       stuff new objects into a pack?\n>\n> * Multiple branches and non-checked out branches\n>\n>   * The ability to operate on non-checked out branches also implies\n>     that we should generally be able to replay when in a dirty working\n>     tree (exception being when we expect to update HEAD and any of the\n>     dirty files is one that needs to be updated by the replay).\n>\n>   * Also, if we are operating locally on a non-checked out branch and\n>     hit a conflict, we should have a way to resolve the conflict without\n>     messing with the user's work on their current branch.\n>\n>     * Idea: new worktree with sparse cone + sparse index checkout,\n>       containing only files in the root directory, and whatever is\n>       necessary to get the conflicts\n>\n>     * Companion to above idea: control structures should be written to\n>       $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n>       replay sessions, and so we know which worktrees are associated\n>       with which replay operations.\n\n...up to here.  Some of the other stuff could perhaps be trimmed as\nwell, though I suspect at least some of it is useful from the\nperspective of letting others know of additional usecases we'd like to\nsupport (so that design suggestions don't curtail those additional\nfuture usecases).\n\n\n>   - [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/\n>   - [2] https://github.com/martinvonz/jj/discussions/49\n>   - [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/\n\nThis appears to be the end of the part you copied from replay-design-notes.txt\n\n<snip>\n\n> * 9/14 replay: very coarse worktree updating\n>\n>      Make it handle conflicts in a very coarse way. This might not\n>      work on bare repos, but it allows existing tests to pass and it's\n>      nice to help cli users a bit when they get conflicts.\n\nI had the coarse working updating in git-replay mostly because it came\nfrom fast-rebase.  I did also use it to poke and prod early ideas,\neven though I knew that its current implementation might well be\nincompatible with some of the other ideas I had.\n\nI don't think this helps the server-side use though, and since it\npotentially conflicts with some of the other goals, I'd be inclined to\nsay we should just drop this patch (and/or squash the next patch into\nthis one.)\n\n> * 10/14 replay: make it a minimal server side command\n>\n>      After the cleaning up in previous ommits, it's now time to\n>      radically change the way it works by stopping it to do ref\n>      updates, to update the index and worktree, to consider HEAD as\n>      special. Instead just make it output commands that should be\n>      passed to `git update-ref --stdin`.\n\nA squashed 9 & 10 would thus be similar to patch 8 in the sense that\nits purpose was to get rid of something that made sense for\nfast-rebase but which doesn't align with the current goals of this\ncommand.\n\n<snip>\n\n> # Possibly controversial issues\n>\n> * bare or not bare: this series works towards a command with the end\n>   goal of it being usable and used on bare repos, contrary to existing\n>   commands like `git rebase` and `git cherry-pick`, but the tests\n>   currently don't check that, and in case of conflicts it won't\n>   currently work on bare repos. One reason for that is that existing\n>   tests in t6429 should continue to work, and one of these tests\n>   requires some output in case of conflict. And it's nice for users to\n>   get some help in case of conflict. It's also nice for users if\n>   commands that should work on both bare and non bare repos work well\n>   on non bare repos first as they are less likely to use them on bare\n>   repos. So let's have a command that works well on non-bare repos\n>   first, even if its end goal is to work fine on bare repos too. We\n>   plan to improve things for bare repos soon after this first patch\n>   series graduates.\n\nI think there's a lot of work to do for conflict handling, and if we\nwant to submit this for inclusion then we should just exclude conflict\nhandling entirely and adjust the tests accordingly.\n\n> * exit status: a successful, non-conflicted replay exits with code\n>   0. When the replay has conflicts, the exit status is 1. If the\n>   replay is not able to complete (or start) due to some kind of error,\n>   the exit status is something other than 0 or 1. It has been\n>   suggested in an internal review that conflicts might want to get a\n>   more specific error code as an error code of 1 might be quite easy\n>   to return by accident. It doesn't seem to me from their docs (which\n>   might want to be improved, I didn't look at the code) that other\n>   commands like `git merge` and `git rebase` exit with a special error\n>   code in case of conflict.\n\nThe merge backend does not provide any granularity finer than \"had\nconflicts\" for its return code.  This is somewhat cemented by the\ngit-merge API as well, where it mandates that the return code of merge\nbackends be 1 for conflicts:\n\n    /*\n     * The backend exits with 1 when conflicts are\n     * left to be resolved, with 2 when it does not\n     * handle the given merge at all.\n     */\n\nBecause of this, all of git-merge-resolve, git-merge-octopus,\ngit-merge-recursive, and git-merge-ort return 1 for conflicts.\n\nOf course, we do not have to use the return code of the merge backend\nas the exit status for `git replay`.  We could inspect the conflicts,\nsomewhat like git-merge-tree does, except that instead of printing\ninformation about those conflicts, we could also let it guide us to\nchoose a new exit status.  I'm not sure we'd want to do that, but we\ncould.\n\nSince an exit status of 1 is pretty thoroughly baked in elsewhere for\neverything merge related, including `git-merge-tree`, I'd be inclined\nto leave this series as-is and have an exit status of 1 for conflicts.\n\n> * to guess or not to guess: commit 12/14 introduces the\n>   guess_new_base() function which tries to find a base to rebase onto\n>   when the --onto option is not provided, making this option actually\n>   optional instead of mandatory. Given that it's an heuristic and the\n>   command end goal is to be used on server side, we might want to\n>   introduce this as an iterative improvement later. I still think it's\n>   interesting to have it in for now though, as it shows that --onto\n>   and --advance (which is introduced in the following commit) should\n>   indeed be options. If --onto was always mandatory in the series,\n>   people could argue that it shouldn't be an option and its argument\n>   should always be the first (unconditional) argument of the command.\n\nI do not want a positional argument for <onto>; rebase's use of\npositional arguments is an example to avoid, IMO.\n\nI also don't want people thinking this is just rebase.  Rebase is\nperhaps used more than cherry-pick and as such a lot of the\ndocumentation and design goals talk about it more, but I designed this\ncommand very deliberately to be about handling both cherry-pick and\nrebase functionality in the same command.\n\nAll that said, I remember spending a fair amount of time on the\nheuristic, but don't remember if I felt I had gotten it good enough or\nnot.  If folks want to leave it out for a future series, that'd be\nfine, so long as it doesn't lead people to suggestions that'd conflict\nwith the above points.\n\n> * make worktree and index changes optional: commit 10/14 stops\n>   updating the index and worktree, but it might be better especially\n>   for cli users to make that optional. The issue is that this would\n>   make the command more complex while we are developing a number of\n>   important features. It seems to me that this should rather be done\n>   in an iterative improvement after the important features have\n>   landed.\n\nI don't think worktree and index changes should be included in this\nseries, even as an option.  The related code as currently written is\nincompatible with some other ideas expressed in the cover letter.\nGranted, those ideas might change, even dramatically, but I don't want\nus limiting the design space and I don't think the worktree or index\nupdates in this series are ready for users to use yet.  (And there\naren't any patches available, to my knowledge, that make them ready.\nThose still need to be written.)\n\n> * when and where to add tests and docs: although t6429 has tests that\n>   are changed to use the new command instead of the fast-rebase\n>   test-tool command as soon as the former is introduced, there is no\n>   specific test script and no doc for the new command until commit\n>   11/14 when standard revision ranges are used. This is done to avoid\n>   churn in tests and docs while the final form of the command hasn't\n>   crystalized enough. Adding tests and doc at this point makes this\n>   commit quite big and possibly more difficult to review than if they\n>   were in separate commits though. On the other hand when tests and\n>   docs are added in specific commits some reviewers say it would be\n>   better to introduce them when the related changes are made.\n\nI don't have opinions on this one.  Maybe I will after reading through\nthis cleaned up series more carefully.\n\n> * --advance and --contained: these two advanced options might not\n>   belong to this first series and could perhaps be added in a followup\n>   series in separate commits. On the other hand the code for\n>   --contained seems involved with the code of --advance and it's nice\n>   to see soon that git replay can indeed do cherry-picking and rebase\n>   many refs at once, and this way fullfil these parts of its promise.\n\n--advance is the one option I thought I had a chance of getting my\ncompany to use server-side in the near term...\n\nAlso, without --advance, I worry reviewers will make rebase-centric\nassumptions that preclude handling cherry-picks, so I'm really leery\nof deferring it based on that.\n\nI understand that --contained isn't as likely to be useful\nserver-side, but it was the one piece that I really enjoyed using\ncli-side (especially since it pre-dated rebase --update-refs).  But\nyeah, it's utility is somewhat limited until there's some kind of\nconflict resolution mechanism provided.\n\n> * replaying diverging branches: 14/14 the last patch in the series,\n>   which allow replaying diverging branches, can be seen as a\n>   fundamental fix or alternatively as adding an interesting\n>   feature. So it's debatable if it should be in its own patch along\n>   with its own tests as in this series, or if it should be merged into\n>   a previous patch and which one.\n\nI don't have opinions on this either.  Maybe I will after reading\nthrough the series?\n\n> * only 2 patches: this patch series can be seen from a high level\n>   point of view as 1) introducing the new `git replay` command, and 2)\n>   using `git replay` to replace, and get rid of, the fast-rebase\n>   test-tool command. The fact that not much of the original\n>   fast-rebase code and interface is left would agree with that point\n>   of view. On the other hand, fast-rebase can also be seen as a first\n>   iteration towards `git replay`. So it can also make sense to see how\n>   `git replay` evolved from it.\n\nI preferred the angle of starting from fast-rebase, but I don't feel\ntoo strongly about it.  Mostly I just thought it'd make it easier for\nme, and for the few other people who already reviewed fast-rebase (and\nyes, I got review comments on it from a few different people back when\nit was proposed).  If dividing up the patches some other way makes it\neasier for others to reason about, and you want to switch it around,\ngo for it.\n"},{"id":"475444","messageId":"CABPp-BGQWYhLrRd19jbzj3_cT8FnogngLczYNgQgZBWoeYRjwg@mail.gmail.com","threadId":"59560","inReplyTo":"25f57067-e4fe-51d5-c42c-6f95940cba27@gmail.com","subject":"Re: [PATCH 00/14] Introduce new `git replay` command","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-15T17:18:42Z","receivedAt":"2023-04-15T17:19:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Phillip,\n\nOn Fri, Apr 14, 2023 at 3:13 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Christian and Elijah\n>\n> On 07/04/2023 08:24, Christian Couder wrote:\n> > # Intro\n> >\n> > `git replay` has initially been developed entirely by Elijah Newren\n> > mostly between June and July 2022 at:\n> >\n> > https://github.com/newren/git/commits/replay\n> >\n> > I took over a few months ago to polish and upstream it as GitLab is\n> > interested in replacing libgit2, and for that purpose needs a command\n> > to do server side (so without using a worktree) rebases, cherry-picks\n> > and reverts.\n> >\n> > I reduced the number of commits and features in this first patch\n> > series, compared to what Elijah already developed. Especially I\n> > stopped short of replaying merge commits and replaying\n> > interactively. These and other features might be upstreamed in the\n> > future after this patch series has graduated.\n> >\n> > Thanks to Elijah, Patrick Steinhardt and Dscho for early reviews and\n> > discussions.\n> >\n> > Based on ae73b2c8f1 (The seventh batch, 2023-04-04)\n>\n> Thanks to both of you for working on this it looks very interesting.\n> I've had a quick read over the patches and I ended up slightly confused\n> as to exactly what the aim of this series is. My main confusion is\n> whether \"replay\" is intended to be a plumbing command or a porcelain\n> command. The use case above suggests plumbing and there are patches that\n> take it in that direction by removing any diagnostic output and stopping\n> it update any refs. But then it is marked as porcelain in\n> command-list.txt and there are patches that do things like\n> unconditionally updating the index and worktree when there are conflicts\n> that stop it working in bare repositories. I've left some comments below\n\nYeah, that's fair.  It was totally unclear in my work-in-progress\npatches, as I had ideas taking it in both directions.  Then Christian\nand Dscho came along and said they wanted to use it for server-side\nrebasing & cherry-picking.  Christian did some good cleanup of my\npatches, but I think your comment here is just reflecting that there's\nstill a bit more to do.\n\nI'd say for now that we should expunge the worktree & index updating,\nany conflict handling beyond exit status, mark it as plumbing, and\nfocus strictly on server-side use for now.\n\n> > # Quick Overview (from Elijah)\n> >\n> > `git replay`, at a basic level, can perhaps be thought of as a\n> > \"default-to-dry-run rebase\" -- meaning no updates to the working tree,\n> > or to the index, or to any references.  However, it differs from\n> > rebase in that it:\n> >\n> >    * Works for branches that aren't checked out\n> >    * Works in a bare repository\n> >    * Can replay multiple branches simultaneously (with or without common\n> >      history in the range being replayed)\n> >    * Preserves relative topology by default (merges are replayed too)\n> >    * Focuses on performance\n> >    * Has several altered defaults as a result of the above\n> >\n> > I sometimes think of `git replay` as \"fast-replay\", a patch-based\n> > analogue to the snapshot-based fast-export & fast-import tools.\n> >\n> > # Reasons for diverging from cherry-pick & rebase (from Elijah)\n> >\n> > There are multiple reasons to diverge from the defaults in cherry-pick and\n> > rebase.\n> >\n> > * Server side needs\n> >\n> >    * Both cherry-pick and rebase, via the sequencer, are heavily tied\n> >      to updating the working tree, index, some refs, and a lot of\n> >      control files with every commit replayed, and invoke a mess of\n> >      hooks[1] that might be hard to avoid for backward compatibility\n> >      reasons (at least, that's been brought up a few times on the\n> >      list).\n> >\n> >    * cherry-pick and rebase both fork various subprocesses\n> >      unnecessarily, but somewhat intrinsically in part to ensure the\n> >      same hooks are called that old scripted implementations would\n> >      have called.\n>\n> To clarify, since 356ee4659bb (sequencer: try to commit without forking\n> 'git commit', 2017-11-24) cherry-pick and rebase do not fork\n> subprocesses other than hooks for the cases covered by this patch series\n> (i.e. they do not fork \"git commit\" for simple picks).\n\nThanks for the clarification.\n\nLet me also add a clarification about this text you're responding to:\nthe document that Christian copied into this portion of the cover\nletter wasn't talking about this particular series he submitted, but\nabout the broader design goals I was working on.  I don't want a\nrun_git_commit() function within replay.c, even for commit message\nediting, for picking merge commits, or for handling staged changes\nafter resuming from an interactive rebase.  I'd even say I don't want\nit for failure handling, either, though that one is less important.\n\n> >    * \"Dry run\" behavior, where there are no updates to worktree, index,\n> >      or even refs might be important.\n> >\n> >    * Should not assume users only want to operate on HEAD (see next\n> >      section)\n> >\n> > * Decapitate HEAD-centric assumptions\n> >\n> >    * cherry-pick forces commits to be played on top of HEAD; inflexible.\n> >\n> >    * rebase assumes the range of commits to be replayed is\n> >      upstream..HEAD by default, though it allows one to replay\n> >      upstream..otherbranch -- but it still forcibly and needlessly\n> >      checks out otherbranch before starting to replay things.\n>\n> I agree it would be nice to be able to restrict the range of commits\n> replayed, especially when replaying merges. The comment about checking\n> out other branch is out of date since 767a9c417eb (rebase -i: stop\n> checking out the tip of the branch to rebase, 2020-01-24)\n\nGuess how many years I've been gathering my ideas for git-replay.  ;-)\n\nOne thing to note here, though, is the checkout handling still isn't\nfully fixed, and it may be impossible to fix at this point.  In\nparticular, `git rebase --abort` doesn't return people to the original\nbranch when otherbranch is specified, which is problematic in\nconjunction with other options (see e.g.\nhttps://lore.kernel.org/git/xmqqft5icsd9.fsf@gitster.c.googlers.com/).\n\n> >    * Assuming HEAD is involved severely limits replaying multiple\n> >      (possibly divergent) branches.\n>\n> I'm not sure how true this is anymore, since 89fc0b53fdb (rebase: update\n> refs from 'update-ref' commands, 2022-07-19) the sequencer can update\n> multiple branches. The issue with divergent branch is with command line\n> arguments and the todo list generation rather than the capabilities of\n> the sequencer.\n\nYeah, this particular paragraph I suspect is from about 18 months ago.\n(See also where I responded to the update-refs series about this\nhttps://lore.kernel.org/git/CABPp-BEOV53oBoBp4YjiRfksZMmAADanZUUemhxwn7Wor=m-nA@mail.gmail.com/).\n\nHowever, I'd still say the statement is true, it just needs to be\nfocused on the divergent branches.  The use of HEAD (or more generally\nexactly 3 commitish-es), as done in rebase, is going to make it really\nawkward to introduce any kind of command line UI for handling rebasing\nof multiple divergent branches.  I'm not sure it even makes sense.\ngit-replay has done it for over a year, and I think it makes sense\nspecifically because it got rid of the problematic assumptions\nhard-wired into the command line UI.\n\n> >    * Once you stop assuming HEAD has a certain meaning, there's not\n> >      much reason to have two separate commands anymore (except for the\n> >      funny extra not-necessarily-compatible options both have gained\n> >      over time).\n>\n> I agree having a unified command at the plumbing level certainly makes\n> sense.\n\n:-)\n\n> >    * (Micro issue: Assuming HEAD is involved also makes it harder for\n> >      new users to learn what rebase means and does; it makes command\n> >      lines hard to parse.\n>\n> That's an interesting point, I wonder if operating on branches that are\n> not checked out is potentially confusing for new user though.\n\nYes, absolutely.  Especially since I'm very focused on having\noperations be O(changes), not O(size-of-repo).  The amount of work\npeople go through to avoid backporting because checking out other\nbranches is too expensive is impressive.  But designing things for the\nuser that needs scaling to big repos isn't always the friendliest to\nnew users.  And I admittedly am focused on performance above other\naspects, and in some cases that might leave new users behind.\n\n> >  Not sure I want to harp on this too much, as\n> >      I have a suspicion I might be creating a tool for experts with\n> >      complicated use cases, but it's a minor quibble.)\n\nOh, heh.  I guess I knew at the time I wrote this that people might\npoint out ways I was not being newbie friendly.  :-)\n\n> > * Performance\n> >\n> >    * jj is slaughtering us on rebase speed[2].  I would like us to become\n> >      competitive.  (I dropped a few comments in the link at [2] about why\n> >      git is currently so bad.)\n> >\n> >    * From [3], there was a simple 4-patch series in linux.git that took\n> >      53 seconds to rebase.  Switching to ort dropped it to 16 seconds.\n> >      While that sounds great, only 11 *milliseconds* were needed to do\n> >      the actual merges.  That means almost *all* the time (>99%) was\n> >      overhead!  Big offenders:\n> >\n> >      * --reapply-cherry-picks should be the default\n>\n> I agree that can be a performance hit if there are a lot of upstream\n> commits, but it is also a usability feature as it means we don't stop\n> and ask the user what to do with the commits that have been upstreamed\n> which wastes more of their time. I think maybe we want different\n> defaults for the server use case than the user replaying commits or\n> perhaps default to dropping commits that become empty.\n\nOh, sure, there are people who will like different options and\nconfigurations.  Non-performant features can certainly be options, I\nwas mostly harping on the defaults for git-replay.  It should default\nto fast, even if/when it moves beyond server-side only.\n\n> >      * can_fast_forward() should be ripped out, and perhaps other extraneous\n> >        revision walks\n>\n> We should look at doing that at least for the merge backend which has\n> skip_unnecessary_picks(). I think it is useful to tell the user that the\n> branch was not updated by the rebase though.\n\nWhich could be done as a very simple post-operation check: do pre- and\npost- hashes match?  No need for a bunch of logic to check what's\ngoing to happen that'll result in slow startup in large repositories.\nDefault should be fast and scale to large repos.\n\n> >      * avoid updating working tree, index, refs, reflogs, and control\n> >        structures except when needed (e.g. hitting a conflict, or operation\n> >        finished)\n>\n> Not writing to disc unless we need to is sensible. Having said that for\n> interactive rebases I do find having HEAD's reflog record all the picks\n> useful to unpick what went wrong if mess something up.\n\nAdding an option for writing HEAD's reflog at every step along the way\nmight be reasonable.  But I specifically was designing a new command\nwith speed as priority number 1.  Updating some external file with\nevery commit thus should not be the default for this new command.  I\nknow that's incompatible with how git-rebase works, but this isn't\nrebase.\n\nActually, there's a second problem as well.  git-replay shouldn't\nupdate _anything_ (other than adding new un-referenced\nblobs/trees/commits), until it is entirely done.  In fact, even when\nit's done, it doesn't update any refs by default; it simply prints out\nsome \"update-ref\" instructions for `git update-ref --stdin` (though I\nwas thinking of adding a flag that would make git-replay also do the\nref updates, as the very final thing the program did).\n\nAnd there's a third problem.  HEAD isn't being updated in many cases,\nso why should its reflog be?\n\n> >    * Other performance ideas:\n> >\n> >      * single-file control structures instead of directory of files\n>\n> I like the idea as it should make it easier to keep the on disc state\n> consistent, but I'm not sure how much of an issue that is in practice as\n> we only read/write the files once each time git is run. The bigger slow\n> down is writing the author script, commit message, list of rewritten\n> commits, todo list and done files with each pick.\n\nThe author script, commit message, list of rewritten commits, todo\nlist and done files are all control structures that are part of that\ndirectory of files I was complaining about.  As far as I can tell, you\nsaid it's not an issue, but then immediately said the opposite.  What\ndid you take the \"directory of files\" control structures to mean?\n...and is there some other way I could word this that would be better?\n\nAlso, if we speed up the other stuff enough, the writing of the\ncontrol files becomes much more important.  See the example elsewhere\nwhere I mentioned that for a cherry-pick of a single commit that took\na minute under git-rebase, that actually creating the new toplevel\ntree only required like 11 milliseconds.  If we can speed things up\ndramatically enough, then all the stuff that used to just not matter,\nsuddenly starts mattering a lot.  (This happened a lot when I was\nrewriting the merge machinery and optimizing it.)  And, I think that\nwriting one file is going to be faster than writing N files\n(especially on spinny disks, and yes my laptop has a spinny disk).\n\nAlso, single file control structures are easier to make safe against\nan ill-timed Ctrl-C, though I suspect that's so rarely a problem that\nit may not be worth worrying much about.\n\n> >      * avoid forking subprocesses unless explicitly requested (e.g.\n> >        --exec, --strategy, --run-hooks).  For example, definitely do not\n> >        invoke `git commit` or `git merge`.\n>\n> Good, that matches what the sequencer does for non-merge commits when\n> we're not editing the commit message.\n\n...and when not committing staged changes after a stopped interactive\nrebase, and when not trying to ensure identical error messages when an\nissue is hit.  :-)\n\nThis is good; sequencer is always improving.  If it could just stop\ncalling git commit in these four extra cases, we'd be rid of the\ngit-commit forking for good.\n\n> >      * Sanitize hooks:\n> >\n> >        * dispense with all per-commit hooks for sure (pre-commit,\n> >          post-commit, post-checkout).\n>\n> I agree we should not be running those (we don't run the pre-commit hook\n> anyway).\n\n...unless picking a merge commit or editing the commit message or...  :-)\n\n> However we had a bug report when cherry-pick stopped running\n> the \"prepare-commit-msg\" hook (see\n> https://lore.kernel.org/git/CAKdAkRQuj1hfKeckjuR2oP+8C1i+ZR36O-+aRYif4ufaS_zs+w@mail.gmail.com/).\n> That shouldn't matter for the server but we should bear it in mind when\n> it comes to other use cases.\n\ngit-fast-export and git-fast-import do not run hooks, in part for\nspeed.  I don't think git-fast-patch, i.e. git-replay should either,\nat least not by default.  I'm well aware that it's impossible to make\nthat kind of change to git-rebase, but I'm not trying to design\nanother rebase.\n\nAnother thing that might horrify you that I don't think I wrote down\nis that when interactivity is added, I don't want replay to pre-parse\nand validate the script, much like fast-import doesn't pre-parse and\nvalidate its input.  Not validating would be anathema to how rebase\noperates (because rebase muddies things as it goes -- the worktree and\nthe index AND refs AND reflogs, and it's slow, so you don't want to\nstart down that path until you're confident that people didn't make\nsimple mistakes).  As such, this is a change that I'd never even\nconsider suggesting for rebase, but it's exactly the kind of thing I\nwant for replay.\n\n> >        * pre-rebase also seems to assume exactly 1 ref is written, and\n> >          invoking it repeatedly would be stupid.  Plus, it's specific\n> >          to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n> >          rebase probably broke the pre-rebase assumptions already...)\n>\n> If replay is a plumbing command then skipping the pre-rebase hook makes\n> sense as scripts can call it themselves if they want to. For a porcelain\n> command keeping a hook that can prevent it from rewriting commits  that\n> are already upstream (which I think is one of the main uses of the\n> pre-rebase hook) would be good.\n\npre-rebase has a broken design; it already doesn't work correctly with\nStolee's --ref-update option.  So, even if we wanted to support this\nkind of functionality, it certainly wouldn't be that script.\n\nAlso, the name is pre-rebase, not pre-cherry-pick, and doesn't apply\nto both commands.  That means there's a second reason the design is\nbroken.  So, even if we wanted to support this kind of functionality,\nit'd have to be a differently named script with a different design.\n\nBut, perhaps more importantly than either of the above, is that replay\ndoesn't change any refs, so why do we need a hook to prevent it from\nchanging refs?\n\n> >        * post-rewrite hook might make sense, but fast-import got\n> >          exempted, and I think of replay like a patch-based analogue\n> >          to the snapshot-based fast-import.\n>\n> If we don't call the hook it would be good to have a option that outputs\n> that information so scripts can request it if they want.\n\nGood point, although the fact that replay doesn't update refs probably\nmeans I didn't even need to mention these hooks.\n\n> Also we should\n> think about if/when we want to update the notes associated with replayed\n> commits.\n\n_Very_ good point.  There should definitely be an option for that,\nmaybe even a config option.\n\n> >      * When not running server side, resolve conflicts in a sparse-cone\n> >        sparse-index worktree to reduce number of files written to a\n> >        working tree.  (See below as well)\n> >\n> >      * [High risk of possible premature optimization] Avoid large\n> >        numbers of newly created loose objects, when replaying large\n> >        numbers of commits.  Two possibilities: (1) Consider using\n> >        tmp-objdir and pack objects from the tmp-objdir at end of\n> >        exercise, (2) Lift code from git-fast-import to immediately\n> >        stuff new objects into a pack?\n> >\n> > * Multiple branches and non-checked out branches\n> >\n> >    * The ability to operate on non-checked out branches also implies\n> >      that we should generally be able to replay when in a dirty working\n> >      tree (exception being when we expect to update HEAD and any of the\n> >      dirty files is one that needs to be updated by the replay).\n> >\n> >    * Also, if we are operating locally on a non-checked out branch and\n> >      hit a conflict, we should have a way to resolve the conflict without\n> >      messing with the user's work on their current branch.\n\nThis stuff really shouldn't have been in the cover letter for this\nseries, but since it is...\n\n> That sounds tricky to do in a user friendly way.\n\nYep, totally agreed.\n\nBut, I really, really want a command that operates in O(changes)\nrather than O(repo size), and which can operate on non-checked-out\nbranches, and even if the user has a dirty working tree.\n\nThere are other choices too -- e.g. if replay is fast enough, just\nfail if the user has a dirty working tree and tell them to re-issue\nthe command after first cleaning it up, allowing us to use their\nworking tree for conflict resolution.  However, the level of IDE\nthrashing that happens when people check out other branches (and the\nsubsequent loss of productivity they experience after resolving the\nsimple conflict and switching back) means I'm not willing to have the\nonly option be resolving conflicts in the current working tree.  I\nthink something very different than how git-rebase and git-cherry-pick\nbehave currently are needed, at least as an option.\n\n> >      * Idea: new worktree with sparse cone + sparse index checkout,\n> >        containing only files in the root directory, and whatever is\n> >        necessary to get the conflicts\n>\n> If the user has not asked for a sparse checkout then this could be\n> surprising. Sometimes I find it helpful to be able to poke about in\n> other source files when resolving a conflict. I also often build and\n> test after resolving a conflict which requires more than just the\n> conflict to be checked out.\n\nSure, some people may need an option to have conflicts be resolved in\nthe current working tree, or perhaps even in a separate full tree.\nBut both are such performance killers that an O(changes) option that\ndoesn't mess with the current working tree is very much needed for\nsome usecases.\n\nBut again, that's **way** outside the scope of this series.\n\n> >      * Companion to above idea: control structures should be written to\n> >        $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n> >        replay sessions, and so we know which worktrees are associated\n> >        with which replay operations.\n>\n> We certainly want some way of making sure we only update a given ref in\n> one replay session, and have checks to for whether the ref is checked\n> out anywhere as we do now for rebase --update-refs. That seems to be\n> lacking in the patches adding ref updating in this series.\n\nYeah, that was in patch 9.  It should be dropped or have patch 10\nsquashed into it.\n\nAt that point, this series won't contain any ref updating, but will\nonly have instructions that can be passed to `git update-ref --stdin`.\nAt that point, no such checks will be necessary for this series (as it\nall belongs in `git update-ref --stdin` instead).\n\n> > * The code die()s if there are any conflict. No resumability. No nice\n> >    output. No interactivity.\n>\n> I can see that on a server you might not want any output, but if I run\n> it locally it would be nice to have a message saying which paths have\n> conflicts. Maybe we could add a --quiet flag for the server rather than\n> removing the existing messages?\n\nI think the choice of what to do for conflicts becomes really complex,\nreally quick.  Just look at git-merge-tree, which is a much simpler\ncase, and those kinds of questions blocked the series from progressing\nfor like half a year last year.\n\nI don't think we're ready to answer these questions.  In fact, that's\nprecisely why I never submitted this series, even though I've had the\nbits that Christian is pushing in a working state for about 9 months\nnow.\n\nI think we should defer all conflict handling other than exit status\nuntil later (or continue deferring this series until conflict handling\nis ready, but that may be a long time).\n\n> > * Signed commits are not properly handled. It's not clear what to do\n> >    to such commits when replaying on the server side.\n>\n> Yes on the server where you don't have access to the signing key there\n> is not much need for replay to have a signing option.\n\nAnd on the client side, it's the one place where I'm almost certainly\ngoing to be forced to accept forking a subprocess, though rest assured\nthat I'll grumble about it incessantly.  :-)\n\n> > * exit status: a successful, non-conflicted replay exits with code\n> >    0. When the replay has conflicts, the exit status is 1. If the\n> >    replay is not able to complete (or start) due to some kind of error,\n> >    the exit status is something other than 0 or 1. It has been\n> >    suggested in an internal review that conflicts might want to get a\n> >    more specific error code as an error code of 1 might be quite easy\n> >    to return by accident. It doesn't seem to me from their docs (which\n> >    might want to be improved, I didn't look at the code) that other\n> >    commands like `git merge` and `git rebase` exit with a special error\n> >    code in case of conflict.\n>\n> I don't think we've ever had a special \"conflict\" error code but it\n> would be useful for scripts if replay had one. Does replay return a\n> different exit code for \"merge conflicts\" and \"cannot merge because it\n> would overwrite an untracked file\"? Does it have an exit code for \"the\n> commit becomes empty\" or are those patches unconditionally dropped?\n\nI'm not sure what Christian meant here, but as you say there's\ncertainly no different special codes in the merge machinery.\n\n> > * --advance and --contained: these two advanced options might not\n> >    belong to this first series and could perhaps be added in a followup\n> >    series in separate commits. On the other hand the code for\n> >    --contained seems involved with the code of --advance and it's nice\n> >    to see soon that git replay can indeed do cherry-picking and rebase\n> >    many refs at once, and this way fullfil these parts of its promise.\n>\n> Once I understood what these options did the names made sense, but I\n> could not tell from the names what they were going to do. For me\n> \"--cherry-pick\" and \"--update-refs\" would have been clearer. It might be\n> worth splitting this patch so the individual options are added separately.\n\nThe output of `git replay`, with or without these options, is meant to\nbe passed as input to `git update-refs --stdin`.  As such, naming an\noption --update-refs for this command just seems inherently confusing.\n\nI also feel like --update-refs is a name coded more around\nimplementation details than end-user description of behavior (and as\nfar as implementation details are concerned, this options is not tied\nto an interactive backend the way the --update-refs options in rebase\nis).\n\n\nIn contrast, --cherry-pick might be reasonable.  But I worry it'll be\nread backwards by people.  I want:\n\n   git replay --OPTION branch upstream..commit\n\nto make sense and I worry that OPTION==cherry-pick will cause users to\nthink `branch` is being cherry-picked rather than `upstream..commit`.\nWhat this command does is advance `branch` by cherry-picking\n`upstream..commit` onto it.\n\n> > * only 2 patches: this patch series can be seen from a high level\n> >    point of view as 1) introducing the new `git replay` command, and 2)\n> >    using `git replay` to replace, and get rid of, the fast-rebase\n> >    test-tool command. The fact that not much of the original\n> >    fast-rebase code and interface is left would agree with that point\n> >    of view. On the other hand, fast-rebase can also be seen as a first\n> >    iteration towards `git replay`. So it can also make sense to see how\n> >    `git replay` evolved from it.\n>\n> Starting with fast-rebase means one has to checkout the first patch to\n> see what code we're adding to replay.c and to make sense of the later\n> patches that remove code. It would be interesting to compare this series\n> to one that started from scratch but I guess that would be quite a bit\n> of work.\n\nYeah, I was probably wrong to suggest to Christian to use fast-rebase\nas the starting point.  That is how I started my series, and multiple\npeople had reviewed it and it seemed like it'd be useful to leverage\nthat past review, but the reviews of these series so far suggest I may\nhave made the wrong recommendation to Christian.\n"},{"id":"475448","messageId":"CABPp-BH7ZPBtV5Hu_-_yVdqVmiydW7_s_LYAtwbPuYSbRQiuQw@mail.gmail.com","threadId":"59560","inReplyTo":"8daf2603-2818-9c9d-7a06-6af2872a045a@github.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-15T18:30:33Z","receivedAt":"2023-04-15T18:30:57Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Apr 14, 2023 at 7:09 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 4/7/2023 3:24 AM, Christian Couder wrote:\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Instead of the fixed \"<oldbase> <branch>\" arguments, the replay\n> > command now accepts \"<revision-range>...\" arguments in a similar\n> > way as many other Git commands. This makes its interface more\n> > standard and more flexible.\n>\n> Unfortunately, while doing this, you have broken the --onto\n> logic:\n>\n>  $ git replay --onto HEAD~2 HEAD~1 HEAD\n>  fatal: replaying down to root commit is not supported yet!\n\nOne of the things Christian asked/proposed a while ago was instead of\nmodifying fast-rebase into git-replay, just build git-replay from the\nground up.  I argued for using fast-rebase as a starting point, but I\nthink you've perhaps given an example for why I may have been wrong to\ndo so.  It has caused confusion, because fast-rebase used a very rigid\nsyntax and specification (because it was a simple test script designed\nto handle exactly one simple setup) that is completely against what we\nwant here.\n\nIn particular, you are probably thinking of\n    $ git replay --onto HEAD~2 HEAD~1 HEAD\nas meaning replay the commits in the range HEAD~1..HEAD (i.e. just\nHEAD) with the new base of HEAD~2.  That's the inflexible way\nfast-rebase worked.  We are dispensing with that here, though; your\ncommand means replay the commits in the history of either HEAD~1 or\nHEAD (all the way to the root since you had no negative references) on\ntop of HEAD~2.  If you had instead said:\n    $ git replay --onto HEAD~2 HEAD~1..HEAD\nthen I think `git replay` handles it just fine.  Christian did cover\nthis in the commit message, but it's perhaps subtle and easily missed.\n\nAnyway, at no point in this series does `git replay` support rebasing\ncommits back to the root, so the error message is what I'd expect.\nThe problem was we weren't clear enough about a different syntax being\nexpected.\n\n> The rev-walk you are supplying by this line...\n>\n> > +     argc = setup_revisions(argc, argv, &revs, NULL);\n>\n> is taking the remaining arguments and using them as tips to\n> walk. We need to be able to recognize that --onto A B C means\n> that A is the new base and our walk is B..C.\n>\n> I'm not sure if there might be a way to use a callback for\n> the --onto option and pull out the next three options into\n> 'new-base', 'old-base', 'tip' values or something.\n>\n> Overall, I don't think being flexible in the CLI is of high\n> value for this command. Let's be as prescriptive as possible.\n>\n> Something like:\n>\n>         'git replay [options] <base> <tip>'\n>         This mode means to rebase <tip> onto <base>,\n>         detecting the range of commits to rewrite.\n>\n>         'git replay [options] <new-base> <old-base> <tip>'\n>         This mode means to rebase the range <old-base>..<tip>\n>         onto <new-base>.\n>\n> We don't even need \"--onto\" for these positional arguments.\n\nSo, from my view, the problem with this alternative design is that it\ninflexibly hardcodes a linear range of commits, in a way that likely\nprecludes future extension.  In particular it:\n\n  * precludes handling multiple divergent branches, which I think was\na core design requirement\n  * seems problematic for extending this to handle replaying of merges\n(where being able to select what to replay needs more control)\n  * more generally from the above two, this precludes the opportunity\nto specify both multiple positive and negative refs\n  * precludes things like using `--ancestry-path=<commit>` which was\nspecifically designed for use with git-replay[1]\n  * may not work well with --first-parent, which I think should be\nallowed (in fact, --first-parent in some instances is a specialization\nof --ancestry-path=<commit>)\n  * seems to presume that rebasing is the only thing we want to do,\npossibly precluding supporting cherry-pick-like commands\n\n[1] See https://lore.kernel.org/git/CABPp-BHmj+QCBFDrH77iNfEU41V=UDu7nhBYkAbCsbXhshJzzw@mail.gmail.com/,\nlook for \"Here's my usecase\"\n\nAnd on a minor note, it also copies the positional argument UI design\nfrom rebase that I've always considered to be an annoying UI design\nflaw.  (Not sure if others agree with me on that, but it's always\nbothered me.)\n\nSince you followed up on this, I'll add more detail in response to\nyour other email.\n"},{"id":"475449","messageId":"CABPp-BGfG3VeY1gOugzig8PLan1AS66BMWnyFSOsLOy-zqLdXw@mail.gmail.com","threadId":"59560","inReplyTo":"89c78da5-388a-e52b-b20b-e376ac90de14@github.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-15T19:07:10Z","receivedAt":"2023-04-15T19:07:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Apr 14, 2023 at 7:23 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 4/14/2023 10:09 AM, Derrick Stolee wrote:\n> > On 4/7/2023 3:24 AM, Christian Couder wrote:\n> >> From: Elijah Newren <newren@gmail.com>\n> >>\n> >> Instead of the fixed \"<oldbase> <branch>\" arguments, the replay\n> >> command now accepts \"<revision-range>...\" arguments in a similar\n> >> way as many other Git commands. This makes its interface more\n> >> standard and more flexible.\n> >\n> > Unfortunately, while doing this, you have broken the --onto\n> > logic:\n> >\n> >  $ git replay --onto HEAD~2 HEAD~1 HEAD\n> >  fatal: replaying down to root commit is not supported yet!\n> >\n> > The rev-walk you are supplying by this line...\n> >\n> >> +    argc = setup_revisions(argc, argv, &revs, NULL);\n> >\n> > is taking the remaining arguments and using them as tips to\n> > walk. We need to be able to recognize that --onto A B C means\n> > that A is the new base and our walk is B..C.\n>\n> I'm realizing after hitting \"send\" that this change is\n> intentional (based on your test updates). I don't agree with\n> it, though.\n>\n> Sending arbitrary command-line arguments to setup_revisions()\n> creates an opportunity for behavior you are not expecting.\n\nNote that fast-export has always accepted the full range of\nsetup_revisions() flags, even though many options make no sense.\nPerhaps we could tighten up the parsing for fast-export, but limiting\nfast-export to three commits in order to avoid other nonsensical\ncombinations would destroy its utility.\n\nYou may think I've gone on a tangent, but I think it's an important\ncomparison point here.  I think limiting replay to three commits does\nthe same: destroys its intended utility.  replay is a patch-based\nanalogue to fast-export, and we need a wide range of the revision\nranges for it to work.\n\n(There are other interesting comparisons here too.  For example, if\nsomeone specifies commits rather than branches to fast-export, then\nthings break down: new commits may be written but nothing takes\nadvantage of them so they merely become garbage to be gc'ed later.\nLikely not what the user wanted.  The exact same issue existed for\nreplay.  However, in replay's case I tried to add some sanity checking\nfor it -- perhaps that sanity checking would be useful to copy to\nfast-export?)\n\n> For instance, can users pass multiple ranges?\n\nThere's no such thing as multiple ranges for most commands (see\nf302c1e4aa0 (\"revisions(7): clarify that most commands take a single\nrevision range\", 2021-05-18))\n\nHowever, users should absolutely be allowed to specify something like\n\n  $ git replay --onto new-base master..my-topic some-base..other-topic\n\nwhich is one revision range, and I'd expect replay to take commits in\nthe history of either my-topic or other-topic, which are not in the\nhistory of either master or some-base, and replay them.  It'd be\nequivalent, in most cases, to the separate commands:\n\n   $ git replay --onto new-base ^master ^some-base my-topic\n   $ git replay --onto new-base ^master ^some-base other-topic\n\nThe first of the two commands above, for example, would replay the\ncommits in my-topic back to nearer of (master, some-base) and do the\nreplaying on top of new-base.  The second command is similar, but with\nmy-topic replaced by other-topic.\n\nNotice, though, that I said most cases.  There's an important case\nwhere the single command is different from the two commands: when\nmy-topic and other-topic share common commits (which is not also\nshared by master and some-base), we want the replayed common history\nto remain common.  If you do two separate commands, the replayed\ncommon history is no longer common (those commits will instead be\nduplicated with separate committer dates).  So, supporting the single\ncommand form is an intrinsic design consideration for git-replay.\n\n> Can users supply --first-parent?\n\nAbsolutely, and it might be a useful replacement for or supplement to\n--[no-]rebase-cousins.\n\nAlso, we definitely want to allow --ancestry-path and\n--ancestry-path=<commit>, the latter of which was specifically written\nfor use in git-replay.\n\nIn fact, --first-parent sometimes might be equivalent to a particular\nspecialization of --ancestry-path=<commit>, so yeah, I definitely want\nto allow it.\n\n> What happens if they add an --author filter?\n\ngit fast-export accepts full revision ranges, including ones that make\nno sense like this.  We could add patches to exclude such options, to\nboth replay and fast-export.  Or just document that they make no\nsense.\n\nTo answer the question as asked, though, let me first provide a little\nbackground: for a commit A with parent B, git-replay checks whether\nthe parent of the current commit (i.e. for A that'd be B) has been\nreplayed (as B').  If so, it will make the parent of A' be B'.  If\nthere is no B' (e.g. because B was part of \"upstream\" and thus wasn't\nreplayed), it makes the parent of A' be whatever is passed to --onto.\nThis allows git-replay to not only rebase/replay a linear set of\npatches, but a partially or even fully divergent set of branches.\n\nNow, with that background, if someone passes --author, there will be a\nlot of parent commits that aren't replayed, resulting in each new\n\"first\" commit being rebased on top of --onto, and only the final\none(s) being referenced by the updated ref commands.  In other words,\nit'll leave lots of orphaned commits behind and only keep the \"newest\"\ncontiguous ones belonging to the author.  In simpler terms, passing\n--author to git-replay would \"make a really big mess of things\" from\nthe user perspective, but an implementationally well-defined mess.\nThis, again, is similar to passing --author to fast-export -- that\ntool has very different primitives involved causing it to make a\ncompletely different big mess of things (and perhaps even bigger mess\nof things), but the mess is at least implementationally well-defined\nto those that understand the data structures and implementation.  (And\nyes, I thought of this exact case while developing the tool, for what\nit's worth.)\n\nDoes that mean I want to allow people to pass --author to fast-export\nor replay?  I'd be perfectly happy with patches that prohibit it in\nboth tools.  But I'm also not worried about people off-roading and\ntrying it and getting non-sense.  A documentation patch stating that\nusing options that \"disconnect\" history make no sense with either\nreplay or fast-export might be the easiest (and most future-proof)\nsolution.\n\n> [unstated: and what about other similar options?]\n\nI'd really rather not have an allowlist of which revision options\nshould be allowed for use by git-replay.  A denylist might be okay, if\nalso implemented for fast-export, but I'm more of the opinion that we\njust document that both commands only make sense with \"contiguous\"\nhistory.\n\nMost importantly, though, at a minimum I do absolutely want to allow\nseveral negative revisions to be specified, several positive revisions\nto be specified, and special flags like --ancestry-path or\n--first-parent to be specified.  There may well be additional flags,\nboth current and future, that should be allowed too.\n\nIn short, I don't want another limited rebase; I want a more generic tool.\n\n\nHope that helps,\nElijah\n"},{"id":"475478","messageId":"CABPp-BHeQpOwit742hVn5eMdeX3WUPCBg7JNhOKjdJsFQbZOdQ@mail.gmail.com","threadId":"59560","inReplyTo":"CABPp-BGfG3VeY1gOugzig8PLan1AS66BMWnyFSOsLOy-zqLdXw@mail.gmail.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-16T05:28:29Z","receivedAt":"2023-04-16T05:28:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Sat, Apr 15, 2023 at 12:07 PM Elijah Newren <newren@gmail.com> wrote:\n<snip>\n> > What happens if they add an --author filter?\n>\n> git fast-export accepts full revision ranges, including ones that make\n> no sense like this.  We could add patches to exclude such options, to\n> both replay and fast-export.  Or just document that they make no\n> sense.\n>\n> To answer the question as asked, though, let me first provide a little\n> background: for a commit A with parent B, git-replay checks whether\n> the parent of the current commit (i.e. for A that'd be B) has been\n> replayed (as B').  If so, it will make the parent of A' be B'.  If\n> there is no B' (e.g. because B was part of \"upstream\" and thus wasn't\n> replayed), it makes the parent of A' be whatever is passed to --onto.\n> This allows git-replay to not only rebase/replay a linear set of\n> patches, but a partially or even fully divergent set of branches.\n\nA small clarification, which only matters to future work and not this\nparticular series: the above description where ONTO is the fallback\nfor a lack of B', is for first parent only.  For a second (or later)\nparent of A, which we'll refer to as C, if C has not been replayed as\npart of this operation, then we simply use the unmodified C as the\nsecond (or later) parent of A'.\n"},{"id":"475523","messageId":"b9732826-5732-0f87-9527-f49c38514fd7@github.com","threadId":"59560","inReplyTo":"CABPp-BGfG3VeY1gOugzig8PLan1AS66BMWnyFSOsLOy-zqLdXw@mail.gmail.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2023-04-17T14:05:06Z","receivedAt":"2023-04-17T14:05:30Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/15/2023 3:07 PM, Elijah Newren wrote:\n> On Fri, Apr 14, 2023 at 7:23 AM Derrick Stolee <derrickstolee@github.com> wrote:\n\n>> Sending arbitrary command-line arguments to setup_revisions()\n>> creates an opportunity for behavior you are not expecting.\n \n>> [unstated: and what about other similar options?]\n> \n> I'd really rather not have an allowlist of which revision options\n> should be allowed for use by git-replay.  A denylist might be okay, if\n> also implemented for fast-export, but I'm more of the opinion that we\n> just document that both commands only make sense with \"contiguous\"\n> history.\n> \n> Most importantly, though, at a minimum I do absolutely want to allow\n> several negative revisions to be specified, several positive revisions\n> to be specified, and special flags like --ancestry-path or\n> --first-parent to be specified.  There may well be additional flags,\n> both current and future, that should be allowed too.\n> \n> In short, I don't want another limited rebase; I want a more generic tool.\n\nThe primary value of git-replay (to me) is that we can do \"simple\" rebases\nwithout a worktree or user interaction. To me, simple rebases mean apply a\nlinear sequence of commits from a single branch onto another branch (and\nspecifying an \"old base\" is simple enough to include here). It also means\nthat we abort completely if there is a conflict (and handle conflict\nresolution in later changes).\n\nThe sooner we can deliver that, the better we can deliver on its potential\n(and expand its functionality to be more generic).\n\nAnd this is generally my preference, but we shouldn't get functionality\n\"by accident\" but instead do it with full intention and strong testing.\n\nI will always think that certain shortcuts should not be used. This\nincludes passing arguments directly to setup_revisions() and using\npathspec parsing when a path string will work just fine.\n\nIf we have a need for a feature, then we should add it explicitly and\ncarefully. The surface area of setup_revisions() is far too large to have\nconfidence that we are not exposing bugs for users to trip on later.\n\nThanks,\n-Stolee\n"},{"id":"475524","messageId":"xmqqy1mqo6kv.fsf@gitster.g","threadId":"59560","inReplyTo":"CABPp-BGfG3VeY1gOugzig8PLan1AS66BMWnyFSOsLOy-zqLdXw@mail.gmail.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-17T15:45:52Z","receivedAt":"2023-04-17T15:45:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> For instance, can users pass multiple ranges?\n>\n> There's no such thing as multiple ranges for most commands (see\n> f302c1e4aa0 (\"revisions(7): clarify that most commands take a single\n> revision range\", 2021-05-18))\n>\n> However, users should absolutely be allowed to specify something like\n>\n>   $ git replay --onto new-base master..my-topic some-base..other-topic\n>\n> which is one revision range, and I'd expect replay to take commits in\n> the history of either my-topic or other-topic, which are not in the\n> history of either master or some-base, and replay them.\n\nIt is one revision range \"^master ^some-base my-topic other-topic\"\nif we let traditional revision parser to work on them, and in many\ntopologies, it would mean something unexpected for users who thought\nthat the user gave two ranges.  E.g. some-base may be an ancestor of\nmaster, in which case commits in the \"some-base..master\" range is\nnot included in the result.  Or some-base may be able to reach\nmy-topic, in which case no commits from master..my-topic range\nappears in the result, etc.\n\nBut should we be forever limited by the \"A..B is always equivalent\nto ^A B\"?\n\nShouldn't replaying master..my-topic and some-base..other-topic,\nwhen some-base is an ancestor of master, replay two overlapping\nrange, possibly recreating some commits twice (when some-base falls\nin between master..my-topic, for example), if the user is willing to\naccept the consequences?\n\nWe can still keep the \"so called 'range' is just commits that are\nreachable from a positive end and are not reachable from any\nnegative end, and by definition there is no such thing as multiple\nranges\" as an option for advanced users and trigger the semantics\nwhen one negative end is written explicitly with ^A notation, but in\napplications like cherry-pick where it is convenient to work on\nmultiple ranges, we may want to extend our worldview to allow A..B\nC..D (especially when these two are distinct ranges---imagine the\ncase where B is an ancestor of C) to mean \"we have two ranges, and\nwe mean the union of the commits in these two ranges\".\n\nWe'd need to design carefully what should happen for trickier cases\nlike A..B C (does it mean the traditional \"a single range of commits\nthat are reachable from either B or C, but never reachable from A\",\nor does it mean \"the union of commits A..B that are reachable from B\nbut not from A and commits C that are reachable from C all the way\ndown to root\"?), though.\n"},{"id":"475585","messageId":"CABPp-BHCdjOutYqdMO1NbYKNA0BgkXRgwUEKK=MX0kXM-5G_DQ@mail.gmail.com","threadId":"59560","inReplyTo":"89c78da5-388a-e52b-b20b-e376ac90de14@github.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-18T04:58:01Z","receivedAt":"2023-04-18T04:58:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Apr 14, 2023 at 7:23 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> (I was able to get a segfault by rebasing this series with\n> --author=stolee because the commit list became empty. Something\n> to watch for.)\n>\n> > Something like:\n> >\n> >       'git replay [options] <base> <tip>'\n> >       This mode means to rebase <tip> onto <base>,\n> >       detecting the range of commits to rewrite.\n> >\n> >       'git replay [options] <new-base> <old-base> <tip>'\n> >       This mode means to rebase the range <old-base>..<tip>\n> >       onto <new-base>.\n>\n> For that reason, I think we should be using explicit argument\n> parsing in the builtin and only transform arguments we\n> understand into the setup_revisions() (by building a strvec).\n\nSo, it turns out that this suggested solution wouldn't have helped\nprevent the segfault you found.  If someone merely passed <old-base>\n== <tip>, they'd see the same segfault.  In fact, I think you found a\nlatent bug in merge-ort.  In particular, cmd_replay() has\n\n    struct merge_options merge_opt;\n    struct merge_result result;\n\n    init_merge_options(&merge_opt, the_repository);\n    memset(&result, 0, sizeof(result));\n\n    <do N merges, for some value of N>\n\n    merge_finalize(&merge_opt, &result);\n\nThis code segfaults if N is 0, because merge_finalize() doesn't return\nearly when result->priv is NULL.\n\nWe never triggered this before, because this is the very first code\nexample we've had where someone has tried to call merge_finalize()\nwithout first performing a merge.  Anyway, nice find and thanks for\nreporting!\n"},{"id":"475587","messageId":"CABPp-BG3xNmwbtu+tstLr8bT24rr0gG65ZvD1rEeus_V8OYk=Q@mail.gmail.com","threadId":"59560","inReplyTo":"b9732826-5732-0f87-9527-f49c38514fd7@github.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-18T05:54:02Z","receivedAt":"2023-04-18T05:54:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Apr 17, 2023 at 7:05 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 4/15/2023 3:07 PM, Elijah Newren wrote:\n> > On Fri, Apr 14, 2023 at 7:23 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> >> Sending arbitrary command-line arguments to setup_revisions()\n> >> creates an opportunity for behavior you are not expecting.\n>\n> >> [unstated: and what about other similar options?]\n> >\n> > I'd really rather not have an allowlist of which revision options\n> > should be allowed for use by git-replay.  A denylist might be okay, if\n> > also implemented for fast-export, but I'm more of the opinion that we\n> > just document that both commands only make sense with \"contiguous\"\n> > history.\n> >\n> > Most importantly, though, at a minimum I do absolutely want to allow\n> > several negative revisions to be specified, several positive revisions\n> > to be specified, and special flags like --ancestry-path or\n> > --first-parent to be specified.  There may well be additional flags,\n> > both current and future, that should be allowed too.\n> >\n> > In short, I don't want another limited rebase; I want a more generic tool.\n>\n> The primary value of git-replay (to me) is that we can do \"simple\" rebases\n> without a worktree or user interaction. To me, simple rebases mean apply a\n> linear sequence of commits from a single branch onto another branch (and\n> specifying an \"old base\" is simple enough to include here). It also means\n> that we abort completely if there is a conflict (and handle conflict\n> resolution in later changes).\n>\n> The sooner we can deliver that, the better we can deliver on its potential\n> (and expand its functionality to be more generic).\n\nLimiting the initial scope of this tool to a smaller set of features\nis okay (and, in fact, Christian has already done that), but we really\nneed to avoid painting ourselves into a corner if we change the design\nas part of that limiting.  As I stated previously[1], I'm worried this\nis happening.\n\n[1] https://lore.kernel.org/git/CABPp-BH7ZPBtV5Hu_-_yVdqVmiydW7_s_LYAtwbPuYSbRQiuQw@mail.gmail.com/,\nunder \"from my view\"\n\n> And this is generally my preference, but we shouldn't get functionality\n> \"by accident\"\n\nAll of the specific flags and cases you brought up so far were ones I\nalready carefully considered over a year ago, so the \"by accident\"\ncomment seems a little unfair.\n\n> but instead do it with full intention\n\nThe intention is listed in the subject of the commit message of this\npatch.  I've also explicitly stated my desire on this list to make a\ntool which replays based on a general range expression multiple\ntimes[2][3][4][5].  And there are tests for general range expressions\nin this series being reviewed.  I don't understand why you might think\nI didn't intend to use general range expressions.\n\n[2] https://lore.kernel.org/git/CABPp-BEAqP7maTVw82Qr8mn-sxPzXmHnE_mTKf2pg6hVYAJSUw@mail.gmail.com/\n[3] https://lore.kernel.org/git/CABPp-BHmj+QCBFDrH77iNfEU41V=UDu7nhBYkAbCsbXhshJzzw@mail.gmail.com/\n[4] https://lore.kernel.org/git/CABPp-BHARfYcsEM7Daeb7+vYxeB9Awo8=qbrOMXG6BQ0gX1RiA@mail.gmail.com/\n[5] https://lore.kernel.org/git/CABPp-BEOV53oBoBp4YjiRfksZMmAADanZUUemhxwn7Wor=m-nA@mail.gmail.com/\n\n> and strong testing.\n\nThe series as it stands already has some good testing for multiple\ndivergent branches, which already takes it beyond the simplistic cases\nyou were focusing on.\n\nYes, there should obviously be tests for even more cases, and yes I\ndid leave this series in a work-in-progress state nearly a year ago.\nMy Git time has been limited, and so I have tended to only work on\nthings that needed short bursts of attention, like responding to the\nlist, or the cache.h refactoring...\n\nBut I'm kind of at a loss trying to understand this paragraph.  Am I\nmisinterpreting what you are saying?\n\n> I will always think that certain shortcuts should not be used. This\n> includes passing arguments directly to setup_revisions() and using\n> pathspec parsing when a path string will work just fine.\n>\n> If we have a need for a feature, then we should add it explicitly and\n> carefully.  The surface area of setup_revisions() is far too large to have\n> confidence that we are not exposing bugs for users to trip on later.\n\nMy original plan was to just extend rebase rather than create a new\ncommand.  While it'd be an extraordinary amount of work to change\nrebase to work without touching the working tree or index, it didn't\nlook completely insurmountable.  The first thing I found that did look\ncompletely insurmountable was extending it to handle rebasing several\ndivergent branches possibly sharing some common history.  Since\nhandling possibly-divergent branches was one of my primary motivating\ngoals, I'm really rather unwilling to accept the suggestion that we\nshould strip the tool and copy rebase's broken design, since that\nwould prevent the tool from ever being extended to do what I want and\nwhat I *already* implemented.  There may be some kind of middle ground\nwe can find, but the suggestion elsewhere in this thread (to make\ngit-replay take three commits as positional arguments, with one an\nassumed negative ref) is copying that exact inflexible design flaw.\n\nIf you want to move git-replay away from setup_revisions(), at a\nminimum I think we need a proposal that can be extended to the cases I\nhighlighted previously:\n    * allow specifying several negative revisions (or maybe even zero of them)\n    * allow specifying several positive revisions\n    * allow standard basic range syntax, i.e. A..B\n    * allow --first-parent\n    * allow --ancestry-path[=<commit>]\nI think it should also be able to eventually support\n    * --branches[=<pattern>]\n    * --not\n    * --tags[=<pattern>]\n    * --remotes[=<pattern>]\n    * --glob=<pattern>\n    * --exclude=<glob-pattern>\n    * --all\nThere may well be others I missed in glancing over the list.\n\nThat's a long list of things to parse, which setup_revisions() happens\nto handle nicely.  You are right that setup_revisions() also has lots\nof options for generating non-contiguous history that may make little\nor no sense for replay (and makes no sense for fast-export either).\nSo, I am willing to consider other solutions if anyone has one, but\nonly alternative solutions which can eventually handle the above\nrequirements.  In particular, \"three commits as positional arguments\"\n(with one implicitly being considered a negative ref) conflicts with\nthe first three bullet points above.\n"},{"id":"475588","messageId":"CABPp-BEXfFWZ-0u4N1gU4L8o89FdJgBj_MaufzP7yGyi4GCSow@mail.gmail.com","threadId":"59560","inReplyTo":"xmqqy1mqo6kv.fsf@gitster.g","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-18T05:58:11Z","receivedAt":"2023-04-18T05:58:32Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Apr 17, 2023 at 8:45 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> >> For instance, can users pass multiple ranges?\n> >\n> > There's no such thing as multiple ranges for most commands (see\n> > f302c1e4aa0 (\"revisions(7): clarify that most commands take a single\n> > revision range\", 2021-05-18))\n> >\n> > However, users should absolutely be allowed to specify something like\n> >\n> >   $ git replay --onto new-base master..my-topic some-base..other-topic\n> >\n> > which is one revision range, and I'd expect replay to take commits in\n> > the history of either my-topic or other-topic, which are not in the\n> > history of either master or some-base, and replay them.\n>\n> It is one revision range \"^master ^some-base my-topic other-topic\"\n> if we let traditional revision parser to work on them, and in many\n> topologies, it would mean something unexpected for users who thought\n> that the user gave two ranges.  E.g. some-base may be an ancestor of\n> master, in which case commits in the \"some-base..master\" range is\n> not included in the result.  Or some-base may be able to reach\n> my-topic, in which case no commits from master..my-topic range\n> appears in the result, etc.\n>\n> But should we be forever limited by the \"A..B is always equivalent\n> to ^A B\"?\n>\n> Shouldn't replaying master..my-topic and some-base..other-topic,\n> when some-base is an ancestor of master, replay two overlapping\n> range, possibly recreating some commits twice (when some-base falls\n> in between master..my-topic, for example), if the user is willing to\n> accept the consequences?\n>\n> We can still keep the \"so called 'range' is just commits that are\n> reachable from a positive end and are not reachable from any\n> negative end, and by definition there is no such thing as multiple\n> ranges\" as an option for advanced users and trigger the semantics\n> when one negative end is written explicitly with ^A notation, but in\n> applications like cherry-pick where it is convenient to work on\n> multiple ranges, we may want to extend our worldview to allow A..B\n> C..D (especially when these two are distinct ranges---imagine the\n> case where B is an ancestor of C) to mean \"we have two ranges, and\n> we mean the union of the commits in these two ranges\".\n\nOoh, interesting.\n\nThat could also be of use in rebase-like operations (i.e. it'd serve\nas a way for the user to rebase while dropping the commits between B\nand C).\n\nOne thing to be careful about is that I think we would also need\nparent rewriting or some other hint that B was an ancestor of C for\nthis to correctly work.  Otherwise, this construction would just be\nmistaken for separate branches that are both being replayed.  That may\neven matter for the cherry-pick case, since cherry-picking merge\ncommits is part of the intended scope, preventing us from just\nassuming a fully linear range of commits to handle.\n\n> We'd need to design carefully what should happen for trickier cases\n> like A..B C (does it mean the traditional \"a single range of commits\n> that are reachable from either B or C, but never reachable from A\",\n> or does it mean \"the union of commits A..B that are reachable from B\n> but not from A and commits C that are reachable from C all the way\n> down to root\"?), though.\n\nYes, and in things like \"^D A..B C\", we'd have to not only worry about\nwhether A limits C, but also whether ^D limits A..B.\n\nThanks for the interesting food for thought.\n"},{"id":"475613","messageId":"bbdd3697-bc10-f311-dbef-99917603ce4f@github.com","threadId":"59560","inReplyTo":"CABPp-BG3xNmwbtu+tstLr8bT24rr0gG65ZvD1rEeus_V8OYk=Q@mail.gmail.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2023-04-18T13:10:06Z","receivedAt":"2023-04-18T13:10:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/18/2023 1:54 AM, Elijah Newren wrote:\n> On Mon, Apr 17, 2023 at 7:05 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>>\n>> On 4/15/2023 3:07 PM, Elijah Newren wrote:\n\n>>> In short, I don't want another limited rebase; I want a more generic tool.\n>>\n>> The primary value of git-replay (to me) is that we can do \"simple\" rebases\n>> without a worktree or user interaction. To me, simple rebases mean apply a\n>> linear sequence of commits from a single branch onto another branch (and\n>> specifying an \"old base\" is simple enough to include here). It also means\n>> that we abort completely if there is a conflict (and handle conflict\n>> resolution in later changes).\n>>\n>> The sooner we can deliver that, the better we can deliver on its potential\n>> (and expand its functionality to be more generic).\n> \n> Limiting the initial scope of this tool to a smaller set of features\n> is okay (and, in fact, Christian has already done that), but we really\n> need to avoid painting ourselves into a corner if we change the design\n> as part of that limiting.  As I stated previously[1], I'm worried this\n> is happening.\n> \n> [1] https://lore.kernel.org/git/CABPp-BH7ZPBtV5Hu_-_yVdqVmiydW7_s_LYAtwbPuYSbRQiuQw@mail.gmail.com/,\n> under \"from my view\"\n> \n>> And this is generally my preference, but we shouldn't get functionality\n>> \"by accident\"\n> \n> All of the specific flags and cases you brought up so far were ones I\n> already carefully considered over a year ago, so the \"by accident\"\n> comment seems a little unfair.\n> \n>> but instead do it with full intention\n> \n> The intention is listed in the subject of the commit message of this\n> patch.  I've also explicitly stated my desire on this list to make a\n> tool which replays based on a general range expression multiple\n> times[2][3][4][5].  And there are tests for general range expressions\n> in this series being reviewed.  I don't understand why you might think\n> I didn't intend to use general range expressions.\n\nIt's one thing to describe commit ranges, and another to include every\npossible rev-list option.\n\n> If you want to move git-replay away from setup_revisions(), at a\n> minimum I think we need a proposal that can be extended to the cases I\n> highlighted previously:\n>     * allow specifying several negative revisions (or maybe even zero of them)\n>     * allow specifying several positive revisions\n>     * allow standard basic range syntax, i.e. A..B\n\nI think supporting these descriptive ranges is nice, but doesn't _need_\nto be in v1 of the tool. If we need to bake them into the CLI from v1\nin order to ensure compatibility, then I understand that.\n\n>     * allow --first-parent\n>     * allow --ancestry-path[=<commit>]\n> I think it should also be able to eventually support\n>     * --branches[=<pattern>]\n>     * --not\n>     * --tags[=<pattern>]\n>     * --remotes[=<pattern>]\n>     * --glob=<pattern>\n>     * --exclude=<glob-pattern>\n>     * --all\n\nHowever, I think very few of these should be generally supported, and\nif there are reasons to include some then they should be motivated by\na specific use case and tested directly.\n\nAs it is, these tags do something in this v1, but we can't really be\nsure that they work because we're not testing them. That is my\nprimary complaint. And testing each individually isn't enough,\nbecause they can combine in all sorts of ways.\n\n> That's a long list of things to parse, which setup_revisions() happens\n> to handle nicely.  You are right that setup_revisions() also has lots\n> of options for generating non-contiguous history that may make little\n> or no sense for replay (and makes no sense for fast-export either).\n> So, I am willing to consider other solutions if anyone has one, but\n> only alternative solutions which can eventually handle the above\n> requirements.  In particular, \"three commits as positional arguments\"\n> (with one implicitly being considered a negative ref) conflicts with\n> the first three bullet points above.\n\nThe only way I can see using setup_revisions() safely is if you make\nsure to reject any arguments that start with \"--\" (or perhaps \"-\"\nbecause some of those options may have single-character versions).\nThis could be extended to an allowlist if we find a motivation to\ninclude other options (I see \"--not\" as being a likely candidate)\nin patches that test that interaction.\n\nOr, could we extract the portion of setup_revisions() that parses\njust the revision ranges in to a new setup_revision_ranges() method?\nIt could reject any options that are not directly about revision\nranges. This option makes less sense if we are going the allowlist\napproach.\n\nThanks,\n-Stolee\n"},{"id":"475724","messageId":"CABPp-BH5rLZyjLD91Bn=xThMXHk=q+kGcqPjPDOT0-N4fHfquA@mail.gmail.com","threadId":"59560","inReplyTo":"bbdd3697-bc10-f311-dbef-99917603ce4f@github.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-20T04:53:00Z","receivedAt":"2023-04-20T04:55:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Apr 18, 2023 at 6:10 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 4/18/2023 1:54 AM, Elijah Newren wrote:\n> > On Mon, Apr 17, 2023 at 7:05 AM Derrick Stolee <derrickstolee@github.com> wrote:\n> >>\n> >> On 4/15/2023 3:07 PM, Elijah Newren wrote:\n>\n> >>> In short, I don't want another limited rebase; I want a more generic tool.\n> >>\n> >> The primary value of git-replay (to me) is that we can do \"simple\" rebases\n> >> without a worktree or user interaction. To me, simple rebases mean apply a\n> >> linear sequence of commits from a single branch onto another branch (and\n> >> specifying an \"old base\" is simple enough to include here). It also means\n> >> that we abort completely if there is a conflict (and handle conflict\n> >> resolution in later changes).\n> >>\n> >> The sooner we can deliver that, the better we can deliver on its potential\n> >> (and expand its functionality to be more generic).\n> >\n> > Limiting the initial scope of this tool to a smaller set of features\n> > is okay (and, in fact, Christian has already done that), but we really\n> > need to avoid painting ourselves into a corner if we change the design\n> > as part of that limiting.  As I stated previously[1], I'm worried this\n> > is happening.\n> >\n> > [1] https://lore.kernel.org/git/CABPp-BH7ZPBtV5Hu_-_yVdqVmiydW7_s_LYAtwbPuYSbRQiuQw@mail.gmail.com/,\n> > under \"from my view\"\n> >\n> >> And this is generally my preference, but we shouldn't get functionality\n> >> \"by accident\"\n> >\n> > All of the specific flags and cases you brought up so far were ones I\n> > already carefully considered over a year ago, so the \"by accident\"\n> > comment seems a little unfair.\n> >\n> >> but instead do it with full intention\n> >\n> > The intention is listed in the subject of the commit message of this\n> > patch.  I've also explicitly stated my desire on this list to make a\n> > tool which replays based on a general range expression multiple\n> > times[2][3][4][5].  And there are tests for general range expressions\n> > in this series being reviewed.  I don't understand why you might think\n> > I didn't intend to use general range expressions.\n>\n> It's one thing to describe commit ranges, and another to include every\n> possible rev-list option.\n\nYes, agreed.  So clearly there is room for some middle ground.  :-)\n\n> > If you want to move git-replay away from setup_revisions(), at a\n> > minimum I think we need a proposal that can be extended to the cases I\n> > highlighted previously:\n> >     * allow specifying several negative revisions (or maybe even zero of them)\n> >     * allow specifying several positive revisions\n> >     * allow standard basic range syntax, i.e. A..B\n>\n> I think supporting these descriptive ranges is nice, but doesn't _need_\n> to be in v1 of the tool. If we need to bake them into the CLI from v1\n> in order to ensure compatibility, then I understand that.\n\nWahoo, we're moving towards middle ground.\n\nI personally think baking them into the CLI in v1 is the easiest way\nto ensure compatibility.  They're part of the long term goal anyway,\nand they are already implemented and tested.  (Well, at least the 2nd\nand 3rd items.  We also have tests with a negative revision, but could\nadd ones with more than one.  Having zero negative revisions means\nreplaying a root commit onto something else, which the code does not\nyet support.)\n\n> >     * allow --first-parent\n> >     * allow --ancestry-path[=<commit>]\n> > I think it should also be able to eventually support\n> >     * --branches[=<pattern>]\n> >     * --not\n> >     * --tags[=<pattern>]\n> >     * --remotes[=<pattern>]\n> >     * --glob=<pattern>\n> >     * --exclude=<glob-pattern>\n> >     * --all\n>\n> However, I think very few of these should be generally supported, and\n> if there are reasons to include some then they should be motivated by\n> a specific use case and tested directly.\n\nNone of these need to be in v1.  Some aren't even useful yet without\nother changes that Christian excluded in this initial version.\n\nBut can I take a step back and ask if you are saying few of these\nshould be generally supported _in v1_ (which I'm fine with) or _ever_\n(which puts us at total loggerheads)?  It reads to me like you're\nsaying the latter, but I can't fathom why.  While I totally understand\nthe apprehension with \"every possible rev-list option\", this is far\nfrom that extreme and I don't see why flags selecting contiguous\nrevision ranges should be of any concern.  They all have usecases, and\nI've even pointed out multiple already.  Going slightly out of order:\n\n* --ancestry-path=<commit>:\n\nI don't see why this option is getting pushback at all.  It was\ninvented specifically and almost exclusively for use in git-replay.\nI've pointed that out already and linked to the detailed usecase\nexplanation for using this option in git-replay twice so far in this\nthread, but there's another interesting twist that may be relevant\nhere: to my knowledge, there are still no known usecases for this\noption outside of git-replay; Junio and I at least could not imagine\nany others[6].\n\nGranted, this option is not of much use until we also support\nreplaying of merges, so leaving it out of v1 is no big deal.\n\n[6] See the quoted comment of Junio including \"solution in search of a\nproblem\", found right before I give my usecase in\nhttps://lore.kernel.org/git/CABPp-BHmj+QCBFDrH77iNfEU41V=UDu7nhBYkAbCsbXhshJzzw@mail.gmail.com/\n\n* --first-parent:\n\nThis option was already referenced with a usecase in the link \"[5]\"\nfrom my last email.  I also listed two other usecases in my\nreplay-design-notes file[7].\n\nGranted, this option isn't yet useful since Christian has removed the\npick_merge_commit() function for v1.  But considering that it was part\nof my original design, I've listed multiple usecases, and I have\nalready written code for git-replay specifically designed to be used\nwith both --ancestry-path[=<commit>] and --first-parent (just not\nincluded in this v1), it is also one where I'm struggling to\nunderstand pushback for eventual support.\n\n[7] https://github.com/newren/git/blob/2a621020863c0b867293e020fec0954b43818789/replay-design-notes.txt#L113-L124\n\n* all the others (which can be summarized as shorthands options for\nvarious sets of multiple positive refs or multiple negative refs):\n\nI think that supporting multiple positive and negative revisions\nlikely leads to users wanting these shorthand versions.  For example,\nsay a user has a dozen different branches in their repository\n(possibly intertwined), all based on various topics in main and next.\nThey go on vacation (or work on some other project) for a few weeks,\nduring which time everything in next has graduated to main (and next\nhas not been rewound), and the user wants to replay all of their\nbranches on an updated main.  They could do so via:\n\n   $ git fetch origin\n   $ git checkout --detach HEAD   # Detach HEAD so it doesn't prevent\nany ref updates\n   $ git replay --onto origin/main --branches --not --remotes=origin |\ngit update-ref --stdin\n\nThat's something I would like to have available for my use.  (And,\nagain, N separate rebases/replays does not achieve the same effect\nsince the commits in those branches being replayed may be partially or\nfully contained in some of the other branches being replayed.)\n\n> As it is, these tags do something in this v1, but we can't really be\n> sure that they work because we're not testing them. That is my\n> primary complaint.\n\nLack of testing for some of these options is a totally fair complaint.\n\nI'm fine with these options being left out of v1, so long as there\nisn't a design change that will impose backward compatibility\nconstraints that precludes them from being added in the future.  That\nis my primary complaint.\n\n> And testing each individually isn't enough, because they can combine in all sorts of ways.\n\nThat's kind of the point, though, right?  Letting people select a\nrange of (contiguous) revisions using capabilities they are familiar\nwith and which they have used elsewhere in Git (and which have been\ntested elsewhere in Git).\n\n> > That's a long list of things to parse, which setup_revisions() happens\n> > to handle nicely.  You are right that setup_revisions() also has lots\n> > of options for generating non-contiguous history that may make little\n> > or no sense for replay (and makes no sense for fast-export either).\n> > So, I am willing to consider other solutions if anyone has one, but\n> > only alternative solutions which can eventually handle the above\n> > requirements.  In particular, \"three commits as positional arguments\"\n> > (with one implicitly being considered a negative ref) conflicts with\n> > the first three bullet points above.\n>\n> The only way I can see using setup_revisions() safely is if you make\n> sure to reject any arguments that start with \"--\" (or perhaps \"-\"\n> because some of those options may have single-character versions).\n\nOoh, single dash.  I wonder if people might find doing an interactive\nreplay with e.g. `--keep-base -5 ${OTHER_BRANCH}` handy.\n\nGranted, that's basically the same as\n`${OTHER_BRANCH}~5..${OTHER_BRANCH}` in most cases, and I'm not sure\nif I'd personally use it, but it might be a nice touch to allow folks who\nhave long branch names to get by with less typing.\n\nAnyway, I could see employing this reject-dashed-arguments scheme for\nv1, since v1 doesn't need any of the dashed options, but only if\naccompanied with a code comment that it's a hack not intended to be\nkept in the future.\n\n> This could be extended to an allowlist if we find a motivation to\n> include other options (I see \"--not\" as being a likely candidate)\n> in patches that test that interaction.\n\n\"if we find a motivation\"??\n\nI find this is a bit frustrating.  Several things in the merge-ort\nmachinery were designed with these kinds of capabilities in mind.\ngit-merge-tree, on my end, was designed, written, submitted, iterated,\nand improved (taking 9+ months) almost solely as a feedback gathering\nmechanism for this tool (it has much simpler design and conflict\nhandling needed).  I've been working on the usecases for this tool for\nyears, and have put quite a bit of time into this tool already, with\nspecific usecases in mind driving how the tool is being written.  And\nI'm specifically motivated by things that rebase cannot do.\n\nI've linked to several of those usecases multiple times in this thread\nalready.  Many were also in the cover letter in this series.  (And\nmore are in my replay-design-notes.txt file on my replay branch.)\n\nI feel like you're brushing aside those repeated attempts to point out\nthose usecases as though they don't exist or don't matter, with some\nseparate new motivation needed for these options to be allowed?  And\nperhaps saying those usecases should all be *ruled out* apriori,\nbecause you're concerned the implementation *might* also allow other\nunintended uses?\n\nSomething isn't working here.\n\n> Or, could we extract the portion of setup_revisions() that parses\n> just the revision ranges in to a new setup_revision_ranges() method?\n> It could reject any options that are not directly about revision\n> ranges. This option makes less sense if we are going the allowlist\n> approach.\n\nOoh, that's interesting.  However, would the name lead people to think\nthat e.g. --reflog, --walk-reflogs, --author, --committer, --grep,\n--min-parents, --max-parents, --cherry-pick are relevant?  Should we\nperhaps use a name like setup_contiguous_revision_ranges() so it's\nclear these flags are disallowed, while things like A, ^B, C..D,\n--first-parent, --ancestry-path, --branches, --remotes, are allowed?\n\nIs it worth refactoring setup_revision() into more divisible chunks,\nso that callers can make use of just the relevant parts of it?  We\nhave numerous other tools looking for revision ranges, which currently\naccept full rev-list options and pass along user-supplied command-line\narguments to them.  Beyond rev-list/log/shortlog, these seem to\ninclude at least:\n  * fast-export\n  * blame\n  * cherry-pick\n  * revert\n  * format-patch\n  * bisect (at least the skip subcommand)\n  * stash (at least the show subcommand)\n  * filter-branch (actually, as a horrible shell script, it'll\ncontinue accepting all rev-list options anyway)\n\nSome of these could probably get away with non-contiguous revision\nranges, but most wouldn't find any benefit from:\n  * tree/blob/packing options (e.g. --objects*, --unpacked, --filter*,\n--verify-objects, --in-commit-order)\n  * anything dealing with reflogs (e.g. --reflog, --walk-reflogs)\n  * various forms of history simplification (e.g. --simplify-merges,\n--simplify-by-decoration, --sparse, --show-pulls)\n  * flags related to display (e.g. --pretty, --graph,\n--show-signature, --early-output, --disk-usage, --abbrev-commit,\n--relative-date, --log-size, --left-right, --cherry-mark)\n  * flags related to file contents (e.g. <pathspecs>, --merge,\n--follow, --full-diff, --remove-empty)\n\nHere's a fun and valid (and innocuous) command.  Guess which flags are\nignored and which aren't:\n\n  $ git stash show --graph --relative-date --min-parents=3\n--simplify-merges --cherry --show-pulls --unpacked -v -t -8\n--format=oneline --abbrev=12 --pretty=fuller --show-notes\n--encode-email-headers --always --branches --indexed-objects stash@{0}\n\n\nThat all said, while I like the idea of someone dividing\nsetup_revisions() into divisible chunks so tools can just use the bits\nthat are relevant, and doing so sounds like it'd avoid weird surprises\nfrom use of unintended flags, I'm surprised that the solution to the\n\"setup_revisions() is scary\" problem isn't to provide easy-to-use API\nthat allows selecting the relevant subsets for each command, but to\ninstead expect every one that comes along to do some heavy lifting and\nspecial whitelisting.  We have half a dozen users in the tree already\nfacing these problems, so shouldn't these be fixed and the API made to\nbe easy to use more safely so that this problem is solved more\ngenerally rather than just putting it on each future functionality\nimplementor who comes along to work around it in their particular\nsituation?\n"},{"id":"475734","messageId":"f5dd91a7-ba11-917a-39e2-2737829558cb@github.com","threadId":"59560","inReplyTo":"CABPp-BH5rLZyjLD91Bn=xThMXHk=q+kGcqPjPDOT0-N4fHfquA@mail.gmail.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2023-04-20T13:44:08Z","receivedAt":"2023-04-20T13:44:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/20/2023 12:53 AM, Elijah Newren wrote:\n> On Tue, Apr 18, 2023 at 6:10 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>>\n>> On 4/18/2023 1:54 AM, Elijah Newren wrote:\n>>> The intention is listed in the subject of the commit message of this\n>>> patch.  I've also explicitly stated my desire on this list to make a\n>>> tool which replays based on a general range expression multiple\n>>> times[2][3][4][5].  And there are tests for general range expressions\n>>> in this series being reviewed.  I don't understand why you might think\n>>> I didn't intend to use general range expressions.\n>>\n>> It's one thing to describe commit ranges, and another to include every\n>> possible rev-list option.\n> \n> Yes, agreed.  So clearly there is room for some middle ground.  :-)\n> \n>>> If you want to move git-replay away from setup_revisions(), at a\n>>> minimum I think we need a proposal that can be extended to the cases I\n>>> highlighted previously:\n>>>     * allow specifying several negative revisions (or maybe even zero of them)\n>>>     * allow specifying several positive revisions\n>>>     * allow standard basic range syntax, i.e. A..B\n>>\n>> I think supporting these descriptive ranges is nice, but doesn't _need_\n>> to be in v1 of the tool. If we need to bake them into the CLI from v1\n>> in order to ensure compatibility, then I understand that.\n> \n> Wahoo, we're moving towards middle ground.\n> \n> I personally think baking them into the CLI in v1 is the easiest way\n> to ensure compatibility.  They're part of the long term goal anyway,\n> and they are already implemented and tested.  (Well, at least the 2nd\n> and 3rd items.  We also have tests with a negative revision, but could\n> add ones with more than one.  Having zero negative revisions means\n> replaying a root commit onto something else, which the code does not\n> yet support.)\n> \n>>>     * allow --first-parent\n>>>     * allow --ancestry-path[=<commit>]\n>>> I think it should also be able to eventually support\n>>>     * --branches[=<pattern>]\n>>>     * --not\n>>>     * --tags[=<pattern>]\n>>>     * --remotes[=<pattern>]\n>>>     * --glob=<pattern>\n>>>     * --exclude=<glob-pattern>\n>>>     * --all\n>>\n>> However, I think very few of these should be generally supported, and\n>> if there are reasons to include some then they should be motivated by\n>> a specific use case and tested directly.\n> \n> None of these need to be in v1.  Some aren't even useful yet without\n> other changes that Christian excluded in this initial version.\n> \n> But can I take a step back and ask if you are saying few of these\n> should be generally supported _in v1_ (which I'm fine with) or _ever_\n> (which puts us at total loggerheads)?  It reads to me like you're\n> saying the latter, but I can't fathom why.  While I totally understand\n> the apprehension with \"every possible rev-list option\", this is far\n> from that extreme and I don't see why flags selecting contiguous\n> revision ranges should be of any concern.  They all have usecases, and\n> I've even pointed out multiple already.  Going slightly out of order:\n\n(going more out of order)\n\n> * --first-parent:\n> \n> This option was already referenced with a usecase in the link \"[5]\"\n> from my last email.  I also listed two other usecases in my\n> replay-design-notes file[7].\n\nI agree on this one.\n\n> * --ancestry-path=<commit>:\n> \n> I don't see why this option is getting pushback at all.  It was\n> invented specifically and almost exclusively for use in git-replay.\n\n(Edited out your explanation.)\n\nI'm still not super convinced that this solves a common user problem,\nbut you've documented your use case well (in other places). The\nproblem I see is that the current patch brings it in without having\nany of that context.\n\n> * all the others (which can be summarized as shorthands options for\n> various sets of multiple positive refs or multiple negative refs):\n\nI think you're over-simplifying here, because...\n\n>> Or, could we extract the portion of setup_revisions() that parses\n>> just the revision ranges in to a new setup_revision_ranges() method?\n>> It could reject any options that are not directly about revision\n>> ranges. This option makes less sense if we are going the allowlist\n>> approach.\n> \n> However, would the name lead people to think\n> that e.g. --reflog, --walk-reflogs, --author, --committer, --grep,\n> --min-parents, --max-parents, --cherry-pick are relevant?  Should we\n> perhaps use a name like setup_contiguous_revision_ranges() so it's\n> clear these flags are disallowed, while things like A, ^B, C..D,\n> --first-parent, --ancestry-path, --branches, --remotes, are allowed?\n\nI think one thing that might help bridge the divide here is a\ndifferent split when I think of \"revision range\" and \"rev-list options\".\n\nWe have several categories of rev-list options, and we need to find\nthe right set to care about and the ones that aren't useful for git-replay:\n\n 1. (What I call revision ranges) a collection of starting revisions,\n    each marked as an \"include\" or \"exclude\" (A..B includes B,\n    excludes A). The --not option helps with defining these starting\n    points.\n\n 2. (Walk options) Modifications to how we walk commits, such as\n    --first-parent, --ancestry-path. These are so far the kind of\n    options you have motivated with use cases.\n\n 3. (Ordering options) Modifications to how those commits are ordered,\n    such as --topo-order, --date-order, and --reverse. These seem to\n    be overridden by git-replay (although, --reverse probably causes\n    some confusion right now).\n\n 4. (Filtering options) A post-walk filter on a per-commit basis.\n    This includes --(max|min)-parents, --author, --grep.\n\nnote: at this point I'm not sure into which of these categories we\nshould put time-based options like --since.\n\nMy main objection to the generic rev-list options come from allowing\ncategories (3) and (4), since this is more likely to cause user\nconfusion rather than actually be of any use to the feature.\n\nWhile I was not considering (2) to be included in setup_revision_ranges(),\nI could see it being valid to include both (1) and (2) in those options.\n\nI would like to see tests for options in category (2) to demonstrate\nthese use cases and solidify them in our supported scenarios.\n\n> \"if we find a motivation\"??\n> \n> I find this is a bit frustrating.  Several things in the merge-ort\n> machinery were designed with these kinds of capabilities in mind.\n> git-merge-tree, on my end, was designed, written, submitted, iterated,\n> and improved (taking 9+ months) almost solely as a feedback gathering\n> mechanism for this tool (it has much simpler design and conflict\n> handling needed).  I've been working on the usecases for this tool for\n> years, and have put quite a bit of time into this tool already, with\n> specific usecases in mind driving how the tool is being written.  And\n> I'm specifically motivated by things that rebase cannot do.\n> \n> I've linked to several of those usecases multiple times in this thread\n> already.  Many were also in the cover letter in this series.  (And\n> more are in my replay-design-notes.txt file on my replay branch.)\n> \n> I feel like you're brushing aside those repeated attempts to point out\n> those usecases as though they don't exist or don't matter, with some\n> separate new motivation needed for these options to be allowed?  And\n> perhaps saying those usecases should all be *ruled out* apriori,\n> because you're concerned the implementation *might* also allow other\n> unintended uses?\n> \n> Something isn't working here.\n\nI'm trying to read the patches and make sense of what is written there.\n\nThe current patch especially is far too lightly documented for what\nit is actually implementing.\n\nEven its documentation states this:\n\n+<revision-range>::\n+\tRange of commits to replay; see \"Specifying Ranges\" in\n+\tlinkgit:git-rev-parse.\n\nThis \"Specifying Ranges\" section describes exactly category (1) of\nwhat I was talking about, but really the patch enables everything\nin \"Commit Limiting\" from git-rev-list.\n\nBased on what I see in the patch, I can't help but think that the\nextra options are an accident.\n\nAnd _even with the extra context linked elsewhere_ I will still hold\nthat using something as generic as setup_revisions() isn't a good\npractice for software development. It adds too may things that all\nat once, some of which I don't think match the purpose of git-replay.\nYou've convinced me to expand my understanding of what fits in that\ncategory, but I still think we need to test this more. Tests can\ndemonstrate use cases much better than anything else.\n\n> Is it worth refactoring setup_revision() into more divisible chunks,\n> so that callers can make use of just the relevant parts of it?  We\n> have numerous other tools looking for revision ranges, which currently\n> accept full rev-list options and pass along user-supplied command-line\n> arguments to them.  Beyond rev-list/log/shortlog, these seem to\n> include at least:\n>   * fast-export\n>   * blame\n>   * cherry-pick\n>   * revert\n>   * format-patch\n>   * bisect (at least the skip subcommand)\n>   * stash (at least the show subcommand)\n>   * filter-branch (actually, as a horrible shell script, it'll\n> continue accepting all rev-list options anyway)\n> \n> Some of these could probably get away with non-contiguous revision\n> ranges, but most wouldn't find any benefit from:\n>   * tree/blob/packing options (e.g. --objects*, --unpacked, --filter*,\n> --verify-objects, --in-commit-order)\n>   * anything dealing with reflogs (e.g. --reflog, --walk-reflogs)\n>   * various forms of history simplification (e.g. --simplify-merges,\n> --simplify-by-decoration, --sparse, --show-pulls)\n>   * flags related to display (e.g. --pretty, --graph,\n> --show-signature, --early-output, --disk-usage, --abbrev-commit,\n> --relative-date, --log-size, --left-right, --cherry-mark)\n>   * flags related to file contents (e.g. <pathspecs>, --merge,\n> --follow, --full-diff, --remove-empty)\n> \n> Here's a fun and valid (and innocuous) command.  Guess which flags are\n> ignored and which aren't:\n> \n>   $ git stash show --graph --relative-date --min-parents=3\n> --simplify-merges --cherry --show-pulls --unpacked -v -t -8\n> --format=oneline --abbrev=12 --pretty=fuller --show-notes\n> --encode-email-headers --always --branches --indexed-objects stash@{0}\n\nThis investigation is exactly why I'm concerned about using the\ngeneric setup_revisions(). I've already noticed its use elsewhere\nand been disappointed. But I wasn't around when those were written,\nso you get the short straw and become the reason I bring it up.\n\n> That all said, while I like the idea of someone dividing\n> setup_revisions() into divisible chunks so tools can just use the bits\n> that are relevant, and doing so sounds like it'd avoid weird surprises\n> from use of unintended flags, I'm surprised that the solution to the\n> \"setup_revisions() is scary\" problem isn't to provide easy-to-use API\n> that allows selecting the relevant subsets for each command, but to\n> instead expect every one that comes along to do some heavy lifting and\n> special whitelisting.  We have half a dozen users in the tree already\n> facing these problems, so shouldn't these be fixed and the API made to\n> be easy to use more safely so that this problem is solved more\n> generally rather than just putting it on each future functionality\n> implementor who comes along to work around it in their particular\n> situation?\n\nI think a better API is a great idea! Splitting into multiple methods\nor providing option flags for \"categories\" of options allowed would\nalso work.\n\nBut back to my original suggestion: you can also do something simpler\nfor v1 of git-replay (say, very limited revision parsing such as one A..B\nrange) so your progress here isn't blocked on refactoring the revisions\nAPI.\n\nThanks,\n-Stolee\n"},{"id":"475891","messageId":"CABPp-BFCKrbSZQtRD1MnXrwB91O2YK9ZuGd6BiYQZ2zrpLp+uw@mail.gmail.com","threadId":"59560","inReplyTo":"f5dd91a7-ba11-917a-39e2-2737829558cb@github.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-23T01:18:00Z","receivedAt":"2023-04-23T01:18:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Apr 20, 2023 at 6:44 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 4/20/2023 12:53 AM, Elijah Newren wrote:\n> > On Tue, Apr 18, 2023 at 6:10 AM Derrick Stolee <derrickstolee@github.com> wrote:\n> >>\n> >> On 4/18/2023 1:54 AM, Elijah Newren wrote:\n[...]\n> >>>     * allow --first-parent\n> >>>     * allow --ancestry-path[=<commit>]\n> >>> I think it should also be able to eventually support\n> >>>     * --branches[=<pattern>]\n> >>>     * --not\n> >>>     * --tags[=<pattern>]\n> >>>     * --remotes[=<pattern>]\n> >>>     * --glob=<pattern>\n> >>>     * --exclude=<glob-pattern>\n> >>>     * --all\n> >>\n> >> However, I think very few of these should be generally supported, and\n> >> if there are reasons to include some then they should be motivated by\n> >> a specific use case and tested directly.\n> >\n> > None of these need to be in v1.  Some aren't even useful yet without\n> > other changes that Christian excluded in this initial version.\n> >\n> > But can I take a step back and ask if you are saying few of these\n> > should be generally supported _in v1_ (which I'm fine with) or _ever_\n> > (which puts us at total loggerheads)?  It reads to me like you're\n> > saying the latter, but I can't fathom why.  While I totally understand\n> > the apprehension with \"every possible rev-list option\", this is far\n> > from that extreme and I don't see why flags selecting contiguous\n> > revision ranges should be of any concern.  They all have usecases, and\n> > I've even pointed out multiple already.  Going slightly out of order:\n>\n> (going more out of order)\n>\n> > * --first-parent:\n> >\n> > This option was already referenced with a usecase in the link \"[5]\"\n> > from my last email.  I also listed two other usecases in my\n> > replay-design-notes file[7].\n>\n> I agree on this one.\n\n:-)\n\n> > * --ancestry-path=<commit>:\n> >\n> > I don't see why this option is getting pushback at all.  It was\n> > invented specifically and almost exclusively for use in git-replay.\n>\n> (Edited out your explanation.)\n>\n> I'm still not super convinced that this solves a common user problem,\n\nWhy does it have to be common?  I said I wanted to be able to do\nthings that I cannot do with existing tooling, which usually implies\nless common usecases.\n\n\n> > * all the others (which can be summarized as shorthand options for\n> > various sets of multiple positive refs or multiple negative refs):\n>\n> I think you're over-simplifying here, because...\n\nI don't think so.  Was the context lost?  I suspect it was, because\nright after this you started talking about completely unrelated\noptions (though in a very useful manner, so it's all good in the end.)\n\nSince I was responding to \"few of these should be generally\nsupported\", and the first two of those had been discussed above, \"all\nthe others\" here refers to exactly these 7 flags:\n    * --branches[=<pattern>]\n    * --not\n    * --tags[=<pattern>]\n    * --remotes[=<pattern>]\n    * --glob=<pattern>\n    * --exclude=<glob-pattern>\n    * --all\n\nAll I did was read our own documentation for these options and\ncategorize exactly what they share in common.  That yields my\nstatement that these \"can be summarized as shorthand options for\nvarious sets of multiple positive refs or multiple negative refs\".\n\n> >> Or, could we extract the portion of setup_revisions() that parses\n> >> just the revision ranges in to a new setup_revision_ranges() method?\n> >> It could reject any options that are not directly about revision\n> >> ranges. This option makes less sense if we are going the allowlist\n> >> approach.\n> >\n> > However, would the name lead people to think\n> > that e.g. --reflog, --walk-reflogs, --author, --committer, --grep,\n> > --min-parents, --max-parents, --cherry-pick are relevant?  Should we\n> > perhaps use a name like setup_contiguous_revision_ranges() so it's\n> > clear these flags are disallowed, while things like A, ^B, C..D,\n> > --first-parent, --ancestry-path, --branches, --remotes, are allowed?\n>\n> I think one thing that might help bridge the divide here is a\n> different split when I think of \"revision range\" and \"rev-list options\".\n>\n> We have several categories of rev-list options, and we need to find\n> the right set to care about and the ones that aren't useful for git-replay:\n>\n>  1. (What I call revision ranges) a collection of starting revisions,\n>     each marked as an \"include\" or \"exclude\" (A..B includes B,\n>     excludes A). The --not option helps with defining these starting\n>     points.\n\nThere are also shorthands for building up these collections of\nstarting revisions, so that instead of saying e.g. `maint main next\nseen mytopic`, one can just use `--branches`.  Thus, options like\n--branches[=<pattern>], --tags[=<pattern>], --exclude=<pattern>, etc.\nalso belong in this category.\n\n>  2. (Walk options) Modifications to how we walk commits, such as\n>     --first-parent, --ancestry-path. These are so far the kind of\n>     options you have motivated with use cases.\n\nI think I also motivated category 1 with use cases.  Since you\nacquiesced on including that category first, I think you may have\nmeant this last sentence to refer to both of these categories?\n\n>  3. (Ordering options) Modifications to how those commits are ordered,\n>     such as --topo-order, --date-order, and --reverse. These seem to\n>     be overridden by git-replay (although, --reverse probably causes\n>     some confusion right now).\n\nYep, intentionally overridden.\n\nCould you elaborate on what you mean by --reverse causing confusion?\n\n>  4. (Filtering options) A post-walk filter on a per-commit basis.\n>     This includes --(max|min)-parents, --author, --grep.\n\nI think pathspecs are an additional example that is worth specifically\ncalling out as belonging in category 4.\n\nThis is a reasonable splitting of categories, if a bit incomplete.\n(However, other categories I can think of matter less to git-replay\nthan these four.)\n\n> note: at this point I'm not sure into which of these categories we\n> should put time-based options like --since.\n\nFair enough, there were also some options I was unsure of.  And there\nare some that look like a can of worms.  (Which I'd rather not get\ninto, but I'll obliquely note that I would place `--since` and\n`--until` in separate categories.)\n\n> My main objection to the generic rev-list options come from allowing\n> categories (3) and (4), since this is more likely to cause user\n> confusion rather than actually be of any use to the feature.\n\nThat is a reasonable objection; I wish what you had stated earlier\nlooked more like this.\n\nI might disagree with you on the severity of the issue, or the\nrelative necessity/importance of cleaning up the widely-used existing\ncode as a pre-requisite to using it in another place, but it's a\ntotally fair review comment to bring up.  Further, I agree with you\nthat the options in these two categories would not be of any use to\ngit-replay.\n\n> While I was not considering (2) to be included in setup_revision_ranges(),\n> I could see it being valid to include both (1) and (2) in those options.\n\nAwesome, I think this provides me with what I need.  Note that\ncategories (1) and (2) include every single option in my previous \"at\na minimum\" list:\n\n    * several negative revisions (or maybe even zero of them)\n    * several positive revisions\n    * standard basic range syntax, i.e. A..B\n    * --first-parent\n    * --ancestry-path[=<commit>]\n    * --branches[=<pattern>]\n    * --not\n    * --tags[=<pattern>]\n    * --remotes[=<pattern>]\n    * --glob=<pattern>\n    * --exclude=<glob-pattern>\n    * --all\n\nThere may also be additional options that fit within categories (1) and (2).\n\n> I would like to see tests for options in category (2) to demonstrate\n> these use cases and solidify them in our supported scenarios.\n\nYup, totally fair.  Probably not as part of this initial patchset,\nthough, since Christian and Dscho are only interested in a small\nsubset of usecases and are the ones pushing these patches early.\n(Whereas I'm focused on answering questions, and making sure that\nwhatever happens doesn't make my goals for the tool impossible.)\n\n> > \"if we find a motivation\"??\n> >\n> > I find this is a bit frustrating.  Several things in the merge-ort\n> > machinery were designed with these kinds of capabilities in mind.\n> > git-merge-tree, on my end, was designed, written, submitted, iterated,\n> > and improved (taking 9+ months) almost solely as a feedback gathering\n> > mechanism for this tool (it has much simpler design and conflict\n> > handling needed).  I've been working on the usecases for this tool for\n> > years, and have put quite a bit of time into this tool already, with\n> > specific usecases in mind driving how the tool is being written.  And\n> > I'm specifically motivated by things that rebase cannot do.\n> >\n> > I've linked to several of those usecases multiple times in this thread\n> > already.  Many were also in the cover letter in this series.  (And\n> > more are in my replay-design-notes.txt file on my replay branch.)\n> >\n> > I feel like you're brushing aside those repeated attempts to point out\n> > those usecases as though they don't exist or don't matter, with some\n> > separate new motivation needed for these options to be allowed?  And\n> > perhaps saying those usecases should all be *ruled out* apriori,\n> > because you're concerned the implementation *might* also allow other\n> > unintended uses?\n> >\n> > Something isn't working here.\n>\n> I'm trying to read the patches and make sense of what is written there.\n>\n> The current patch especially is far too lightly documented for what\n> it is actually implementing.\n>\n> Even its documentation states this:\n>\n> +<revision-range>::\n> +       Range of commits to replay; see \"Specifying Ranges\" in\n> +       linkgit:git-rev-parse.\n>\n> This \"Specifying Ranges\" section describes exactly category (1) of\n> what I was talking about, but really the patch enables everything\n> in \"Commit Limiting\" from git-rev-list.\n>\n> Based on what I see in the patch, I can't help but think that the\n> extra options are an accident.\n\nThis all would be a fine review comment, if you had said that.\nInstead, you stated that setup_revisions() shouldn't be used (which\nmight have been fine as an initial comment), and when I pointed out\nexact options and flags that were needed at a minimum you still said\nthey shouldn't generally be supported, and after pointing out\nusecases, multiple times, you responded with \"if we can find a\nmotivation\".\n\nThere are two reasons I found that problematic:\n\nSometimes it makes sense to say that a usecase shouldn't be supported.\nBut, when there's months of effort so far put into implementing those\nusecases, I would expect strong statements about ruling out usecases\nto be accompanied with really good explanations of why those exact\ncases are deemed \"bad\" and the best possible alternative solutions\nthat should instead be implemented to solve something close to the\nproposer's stated desires.  I didn't see that.\n\nSimilarly, there are times when a reviewer should say that code should\nnot be implemented a certain way.  But when the person responds with\nsome alternatives and their _minimum_ needs trying to find some common\nground, and has put months of efforts into this wider goal, I would\nexpect that ruling out certain methods and their follow up\nalternatives to be accompanied with alternative solutions of your own\nthat can solve the proposer's goals; not merely attempting to veto\nsuggestions immediately with seemingly no path forward.\n\nIt was an unfortunate set of examples that seemed far below your\ntypically excellent reviews.  In contrast, this latest email of yours\nis another high quality response in line with your emails prior to this thread.\n\n> And _even with the extra context linked elsewhere_ I will still hold\n> that using something as generic as setup_revisions() isn't a good\n> practice for software development. It adds too may things that all\n> at once, some of which I don't think match the purpose of git-replay.\n> You've convinced me to expand my understanding of what fits in that\n> category, but I still think we need to test this more. Tests can\n> demonstrate use cases much better than anything else.\n\nYaay on the convincing bit.  :-)\n\nThe rest is all fair, but I'd like to point out that there are a few\nproblems here:\n\n  * Tests should generally be passing before submitting upstream, so\nall the code to implement them needs to be sent too\n  * Submitted patch series have to be digestible sizes; not everything\ncan be submitted at once\n  * Christian and Dscho wanted some of what I had implemented despite\nother parts not being ready\n\nImportantly, the first two issues in particular mean that when the\nfirst series comes:\n\n  * If you insist on (or even just suggest) certain changes that\nhappen to break capabilities in the pipeline, especially in a fashion\nthat cannot backward-compatibly be fixed, then I think the only path\nforward (other than dropping your suggestions) is to engage in\ndiscussions about those usecases & design *without* the testcases and\ncode being available yet.\n\n> > Is it worth refactoring setup_revision() into more divisible chunks,\n> > so that callers can make use of just the relevant parts of it?  We\n> > have numerous other tools looking for revision ranges, which currently\n> > accept full rev-list options and pass along user-supplied command-line\n> > arguments to them.  Beyond rev-list/log/shortlog, these seem to\n> > include at least:\n> >   * fast-export\n> >   * blame\n> >   * cherry-pick\n> >   * revert\n> >   * format-patch\n> >   * bisect (at least the skip subcommand)\n> >   * stash (at least the show subcommand)\n> >   * filter-branch (actually, as a horrible shell script, it'll\n> > continue accepting all rev-list options anyway)\n> >\n> > Some of these could probably get away with non-contiguous revision\n> > ranges, but most wouldn't find any benefit from:\n> >   * tree/blob/packing options (e.g. --objects*, --unpacked, --filter*,\n> > --verify-objects, --in-commit-order)\n> >   * anything dealing with reflogs (e.g. --reflog, --walk-reflogs)\n> >   * various forms of history simplification (e.g. --simplify-merges,\n> > --simplify-by-decoration, --sparse, --show-pulls)\n> >   * flags related to display (e.g. --pretty, --graph,\n> > --show-signature, --early-output, --disk-usage, --abbrev-commit,\n> > --relative-date, --log-size, --left-right, --cherry-mark)\n> >   * flags related to file contents (e.g. <pathspecs>, --merge,\n> > --follow, --full-diff, --remove-empty)\n> >\n> > Here's a fun and valid (and innocuous) command.  Guess which flags are\n> > ignored and which aren't:\n> >\n> >   $ git stash show --graph --relative-date --min-parents=3\n> > --simplify-merges --cherry --show-pulls --unpacked -v -t -8\n> > --format=oneline --abbrev=12 --pretty=fuller --show-notes\n> > --encode-email-headers --always --branches --indexed-objects stash@{0}\n>\n> This investigation is exactly why I'm concerned about using the\n> generic setup_revisions(). I've already noticed its use elsewhere\n> and been disappointed. But I wasn't around when those were written,\n> so you get the short straw and become the reason I bring it up.\n>\n> > That all said, while I like the idea of someone dividing\n> > setup_revisions() into divisible chunks so tools can just use the bits\n> > that are relevant, and doing so sounds like it'd avoid weird surprises\n> > from use of unintended flags, I'm surprised that the solution to the\n> > \"setup_revisions() is scary\" problem isn't to provide easy-to-use API\n> > that allows selecting the relevant subsets for each command, but to\n> > instead expect every one that comes along to do some heavy lifting and\n> > special whitelisting.  We have half a dozen users in the tree already\n> > facing these problems, so shouldn't these be fixed and the API made to\n> > be easy to use more safely so that this problem is solved more\n> > generally rather than just putting it on each future functionality\n> > implementor who comes along to work around it in their particular\n> > situation?\n>\n> I think a better API is a great idea! Splitting into multiple methods\n> or providing option flags for \"categories\" of options allowed would\n> also work.\n>\n> But back to my original suggestion: you can also do something simpler\n> for v1 of git-replay\n\nThat's workable.\n\n> (say, very limited revision parsing such as one A..B range)\n\nI'm not fine with that, though.  We already have tests of multiple\npositive refs, so there's no reason to exclude those.  In fact,\nkeeping them in is especially important as a means of avoiding having\nother reviewers make suggestions to copy rebase's broken design\n(namely, being limited to a couple commits passed as positional\narguments with one implicitly being an exclude).\n\n> so your progress here isn't blocked on refactoring the revisions\n> API.\n\nIs that a positive or a negative?  That question may surprise you, so\nlet me explain.\n\nI have a conflict of interest of sorts.  When Christian and Dscho\n(separately) approached me earlier this year and pointed out that\ngit-replay had the functionality they wanted, I was happy that others\nliked it, and gave some pointers here and there, but I was _very_\nworried that upstreaming it would result in something getting\nbackward-incompatibly locked into place that prevents the pieces that\nare  still in progress from getting implemented.  And I was concerned\nthat my plans to create a tool people could experiment with, and that\nwe could get usability feedback on (as I suggested at the Git\ncontributors summit last year) might be in jeopardy as pieces of its\nfunctionality get locked into place before it's even ready for\nusability testing.  I was silently hoping they'd lose steam on\nrebasing and cleaning up my patches or choose to just delay until I\ncould get real time to work on it with them.  (Since then, the one\nperson who had ever advocated for my Git work at $DAYJOB, the best\nmanager I had ever worked under, became not-a-manager.  I was\nblindsided by that in February.  Also, I have been transferred to a\ndifferent team and am spinning up there.  And so, to my dismay, I'm\nworried my little sliver of Git time at work may evaporate entirely\nrather than return to previous healthier levels.)  Anyway, I've been\nfretting about things getting \"locked-in\" for a few months.\n\nThey didn't lose steam, as I found out when these patches were\nsubmitted.  But, uh, I'm silently torn because I want to help\nChristian and Dscho get what they want, but having them be blocked on\nprogress would reduce my stress.  A lot.\n\nIs there any chance people would be willing to accept a \"NO BACKWARD\nCOMPATIBILITY GUARANTEES\" disclaimer on this command for a (long?)\nwhile, like Victoria suggested at Review club?  That might be an\nalternate solution that would lower my worries.\n"},{"id":"475925","messageId":"58f7918f-6ca3-2158-7d9e-bfcd8eb24b0d@github.com","threadId":"59560","inReplyTo":"CABPp-BFCKrbSZQtRD1MnXrwB91O2YK9ZuGd6BiYQZ2zrpLp+uw@mail.gmail.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2023-04-24T15:23:42Z","receivedAt":"2023-04-24T15:23:50Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 4/22/2023 9:18 PM, Elijah Newren wrote:\n> On Thu, Apr 20, 2023 at 6:44 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>>\n>> On 4/20/2023 12:53 AM, Elijah Newren wrote:\n>>> On Tue, Apr 18, 2023 at 6:10 AM Derrick Stolee <derrickstolee@github.com> wrote:\n\n>>  3. (Ordering options) Modifications to how those commits are ordered,\n>>     such as --topo-order, --date-order, and --reverse. These seem to\n>>     be overridden by git-replay (although, --reverse probably causes\n>>     some confusion right now).\n> \n> Yep, intentionally overridden.\n> \n> Could you elaborate on what you mean by --reverse causing confusion?\n\nIt's very unlikely that a list of patches will successfully apply\nwhen applied in the reverse order. If we allow the argument, then\nsomeone will try it thinking they can flip their commits. Then they\nmight be surprised when there are a bunch of conflicts on every\ncommit.\n\nBasically, I'm not super thrilled about exposing options that are\nunlikely to be valuable to users and instead are more likely to cause\nconfusion due to changes that won't successfully apply.\n\n>>> I feel like you're brushing aside those repeated attempts to point out\n>>> those usecases as though they don't exist or don't matter, with some\n>>> separate new motivation needed for these options to be allowed?  And\n>>> perhaps saying those usecases should all be *ruled out* apriori,\n>>> because you're concerned the implementation *might* also allow other\n>>> unintended uses?\n>>>\n>>> Something isn't working here.\n>>\n>> I'm trying to read the patches and make sense of what is written there.\n>>\n>> The current patch especially is far too lightly documented for what\n>> it is actually implementing.\n>>\n>> Even its documentation states this:\n>>\n>> +<revision-range>::\n>> +       Range of commits to replay; see \"Specifying Ranges\" in\n>> +       linkgit:git-rev-parse.\n>>\n>> This \"Specifying Ranges\" section describes exactly category (1) of\n>> what I was talking about, but really the patch enables everything\n>> in \"Commit Limiting\" from git-rev-list.\n>>\n>> Based on what I see in the patch, I can't help but think that the\n>> extra options are an accident.\n> \n> This all would be a fine review comment, if you had said that.\n> Instead, you stated that setup_revisions() shouldn't be used (which\n> might have been fine as an initial comment), and when I pointed out\n> exact options and flags that were needed at a minimum you still said\n> they shouldn't generally be supported, and after pointing out\n> usecases, multiple times, you responded with \"if we can find a\n> motivation\".\n> \n> There are two reasons I found that problematic:\n> \n> Sometimes it makes sense to say that a usecase shouldn't be supported.\n> But, when there's months of effort so far put into implementing those\n> usecases, I would expect strong statements about ruling out usecases\n> to be accompanied with really good explanations of why those exact\n> cases are deemed \"bad\" and the best possible alternative solutions\n> that should instead be implemented to solve something close to the\n> proposer's stated desires.  I didn't see that.\n> \n> Similarly, there are times when a reviewer should say that code should\n> not be implemented a certain way.  But when the person responds with\n> some alternatives and their _minimum_ needs trying to find some common\n> ground, and has put months of efforts into this wider goal, I would\n> expect that ruling out certain methods and their follow up\n> alternatives to be accompanied with alternative solutions of your own\n> that can solve the proposer's goals; not merely attempting to veto\n> suggestions immediately with seemingly no path forward.\n> \n> It was an unfortunate set of examples that seemed far below your\n> typically excellent reviews.  In contrast, this latest email of yours\n> is another high quality response in line with your emails prior to this thread.\n\nI'm sorry I didn't read the prior work linked in the cover letter.\nSince cover letters and their external links are not recorded in our\ncommit history, I have a habit of ignoring them other than getting\nsome basic context of the series.\n\nI also was less specific about my complaints because I thought that\nsaying \"things like --author\" was enough to point out that\nsetup_revisions() is too generic. Didn't seem like context that was\nrequired until you showed it was required.\n\n> The rest is all fair, but I'd like to point out that there are a few\n> problems here:\n> \n>   * Tests should generally be passing before submitting upstream, so\n> all the code to implement them needs to be sent too\n>   * Submitted patch series have to be digestible sizes; not everything\n> can be submitted at once\n>   * Christian and Dscho wanted some of what I had implemented despite\n> other parts not being ready\n\nOutside of my first response (using positional arguments, recommended\nbefore you provided the extra context I was missing) I have not\nsuggested implementing something that can't be forward compatible. Note\nthat I've used A..B notation in those replies. I'm suggesting that we\nfocus on a critical core of functionality that can be carefully tested\nwhile the rest of the system is being worked out. Then, those more\nadvanced bits can be added carefully with test cases.\n\nWhen we get to that point, there are creative ways to prepare tests in\nadvance of a code change such that they make reviewable patches, instead\nof creating test debt to be left for later.\n\n> Importantly, the first two issues in particular mean that when the\n> first series comes:\n> \n>   * If you insist on (or even just suggest) certain changes that\n> happen to break capabilities in the pipeline, especially in a fashion\n> that cannot backward-compatibly be fixed, then I think the only path\n> forward (other than dropping your suggestions) is to engage in\n> discussions about those usecases & design *without* the testcases and\n> code being available yet.\nThis sounds a lot like your arguments are focused on reducing the\namount of rework you'd need to do on your pipeline of work that hasn't\nbeen submitted for review. I find that to be an expected risk of working\nso far ahead of reviewed patches, and am familiar with that pain. I\ndon't find that to be a convincing argument.\n\n>> But back to my original suggestion: you can also do something simpler\n>> for v1 of git-replay\n> \n> That's workable.\n> \n>> (say, very limited revision parsing such as one A..B range)\n> \n> I'm not fine with that, though.  We already have tests of multiple\n> positive refs, so there's no reason to exclude those.  In fact,\n> keeping them in is especially important as a means of avoiding having\n> other reviewers make suggestions to copy rebase's broken design\n> (namely, being limited to a couple commits passed as positional\n> arguments with one implicitly being an exclude).\n\nI'm just making one suggestion about where to limit the parsing. If\nmultiple ranges are doable before refactoring setup_revisions(), then\nby all means go ahead. And whatever parsing is accepted in this v1,\nwe can make sure it works with those future plans.\n\nFocusing on the revisions as described by 'git rev-parse' pointed to\nin the 'git replay' docs in this patch would be a great place to start.\n \n>> so your progress here isn't blocked on refactoring the revisions\n>> API.\n> \n> Is that a positive or a negative?  That question may surprise you, so\n> let me explain.\n> \n> I have a conflict of interest of sorts.  When Christian and Dscho\n> (separately) approached me earlier this year and pointed out that\n> git-replay had the functionality they wanted, I was happy that others\n> liked it, and gave some pointers here and there, but I was _very_\n> worried that upstreaming it would result in something getting\n> backward-incompatibly locked into place that prevents the pieces that\n> are  still in progress from getting implemented.  And I was concerned\n> that my plans to create a tool people could experiment with, and that\n> we could get usability feedback on (as I suggested at the Git\n> contributors summit last year) might be in jeopardy as pieces of its\n> functionality get locked into place before it's even ready for\n> usability testing.  I was silently hoping they'd lose steam on\n> rebasing and cleaning up my patches or choose to just delay until I\n> could get real time to work on it with them.  (Since then, the one\n> person who had ever advocated for my Git work at $DAYJOB, the best\n> manager I had ever worked under, became not-a-manager.  I was\n> blindsided by that in February.  Also, I have been transferred to a\n> different team and am spinning up there.  And so, to my dismay, I'm\n> worried my little sliver of Git time at work may evaporate entirely\n> rather than return to previous healthier levels.)  Anyway, I've been\n> fretting about things getting \"locked-in\" for a few months.\n\nI'm also upset that you have been disrupted like this.\n\n> They didn't lose steam, as I found out when these patches were\n> submitted.  But, uh, I'm silently torn because I want to help\n> Christian and Dscho get what they want, but having them be blocked on\n> progress would reduce my stress.  A lot.\n\nFrom my perspective, git-replay's most important use is being able\nto generate rebases without a worktree or an interactive user. For\nnow, I don't even care if that includes conflict resolution. That's\nenough of a lift that has enough unknowns that adding a complex CLI\nat this early stage seems like a hasty decision to me. I'm voicing\nmy opinion that we should avoid backwards-compatibility issues by\nimplementing only the essentials.\n\nThat said, I also want to make sure that you eventually get the\nsuper-flexible command that you want, but only when the framework\nis ready for that kind of flexibility.\n\n> Is there any chance people would be willing to accept a \"NO BACKWARD\n> COMPATIBILITY GUARANTEES\" disclaimer on this command for a (long?)\n> while, like Victoria suggested at Review club?  That might be an\n> alternate solution that would lower my worries.\n\nI'm not crazy about this idea, especially when it is easy to do\nsomething simpler and avoid the need for it. But I'm just one voice\nand one opinion.\n\nThanks,\n-Stolee\n"},{"id":"476314","messageId":"CABPp-BGRtcBQ_6fkMrTskV9dk71ffycXZ8hEE_RaOrAdza_wLA@mail.gmail.com","threadId":"59560","inReplyTo":"58f7918f-6ca3-2158-7d9e-bfcd8eb24b0d@github.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-04-30T06:45:09Z","receivedAt":"2023-04-30T06:45:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Apr 24, 2023 at 8:23 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> On 4/22/2023 9:18 PM, Elijah Newren wrote:\n> > On Thu, Apr 20, 2023 at 6:44 AM Derrick Stolee <derrickstolee@github.com> wrote:\n> >>\n> >> On 4/20/2023 12:53 AM, Elijah Newren wrote:\n> >>> On Tue, Apr 18, 2023 at 6:10 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> >>  3. (Ordering options) Modifications to how those commits are ordered,\n> >>     such as --topo-order, --date-order, and --reverse. These seem to\n> >>     be overridden by git-replay (although, --reverse probably causes\n> >>     some confusion right now).\n> >\n> > Yep, intentionally overridden.\n> >\n> > Could you elaborate on what you mean by --reverse causing confusion?\n>\n> It's very unlikely that a list of patches will successfully apply\n> when applied in the reverse order. If we allow the argument, then\n> someone will try it thinking they can flip their commits. Then they\n> might be surprised when there are a bunch of conflicts on every\n> commit.\n>\n> Basically, I'm not super thrilled about exposing options that are\n> unlikely to be valuable to users and instead are more likely to cause\n> confusion due to changes that won't successfully apply.\n\nOh, I got thrown by the \"right now\" portion of your comment; I\ncouldn't see how time or future changes would affect anything to make\nit less (or more) confusing for users.\n\nQuick clarification, though: while you correctly point out the type of\nconfusion the user would experience without my overriding, my\noverriding of rev.reverse (after setup_revisions() returns, not before\nit is called) precludes that experience.  The override means none of\nthe above happens, and they would instead just wonder why their option\nis being ignored.\n\n[...]\n> > The rest is all fair, but I'd like to point out that there are a few\n> > problems here:\n> >\n> >   * Tests should generally be passing before submitting upstream, so\n> > all the code to implement them needs to be sent too\n> >   * Submitted patch series have to be digestible sizes; not everything\n> > can be submitted at once\n> >   * Christian and Dscho wanted some of what I had implemented despite\n> > other parts not being ready\n>\n> Outside of my first response (using positional arguments, recommended\n> before you provided the extra context I was missing) I have not\n> suggested implementing something that can't be forward compatible.\n\nTrue, and maybe I was misunderstanding, but I thought you were trying\nto make it forward compatible to only slight deviations while ruling\nout most potential future changes, including ones I was specifically\nhighlighting as minimum eventual requirements.  While it was clear\nthat _some_ of the limitations you were trying to impose were only on\nv1, it sounded like you were also trying to rule out a wide swath of\nthings permanently.  That's where things really concerned me, and why\nI specifically asked which you were trying to do.\n\nTo be honest, I'm still unsure what your original intent was.  But it\nprobably doesn't matter now, as it sounds like you've shifted.  I\n_think_ an accurate summary of where we are now at the end of\nthis thread is:\n  * I am predominantly concerned about flexibility/potential for\ngit-replay in the future (what goes in v1 doesn't matter *if* it\ndoesn't affect the future, but that's a huge \"if\").\n  * You are predominantly concerned about v1 and only including the\nportions of functionality that have associated tests\n\nI think there's enough wiggle room to mostly satisfy both viewpoints.\n\nYou helped with my concern by stating that categories (1) and (2)\nshould be fine for git-replay in the future.  (It at least handles the\nforms of potential lock-in that I already know about, though I'm\nworried there are others.)\n\nI believe we can give you what you want via a tweak to the hack you\npreviously suggested for v1 only: rule out passing any options with a\nleading dash to setup_revisions(), *and* throw an error if\nrevs->prune_data.nr is nonzero after calling setup_revisions().  But\nI'd want us to include a comment pointing out that it's a hack, and\ninclude in that note that in the future we want to allow all options\nin categories (1) and (2) to be passed to setup_revisions() (or passed\nto a suitable refactoring thereof).\n\n[...]\n> >> so your progress here isn't blocked on refactoring the revisions\n> >> API.\n> >\n> > Is that a positive or a negative?  That question may surprise you, so\n> > let me explain.\n> >\n> > I have a conflict of interest of sorts.  When Christian and Dscho\n> > (separately) approached me earlier this year and pointed out that\n> > git-replay had the functionality they wanted, I was happy that others\n> > liked it, and gave some pointers here and there, but I was _very_\n> > worried that upstreaming it would result in something getting\n> > backward-incompatibly locked into place that prevents the pieces that\n> > are  still in progress from getting implemented.  And I was concerned\n> > that my plans to create a tool people could experiment with, and that\n> > we could get usability feedback on (as I suggested at the Git\n> > contributors summit last year) might be in jeopardy as pieces of its\n> > functionality get locked into place before it's even ready for\n> > usability testing.  I was silently hoping they'd lose steam on\n> > rebasing and cleaning up my patches or choose to just delay until I\n> > could get real time to work on it with them.  (Since then, the one\n> > person who had ever advocated for my Git work at $DAYJOB, the best\n> > manager I had ever worked under, became not-a-manager.  I was\n> > blindsided by that in February.  Also, I have been transferred to a\n> > different team and am spinning up there.  And so, to my dismay, I'm\n> > worried my little sliver of Git time at work may evaporate entirely\n> > rather than return to previous healthier levels.)  Anyway, I've been\n> > fretting about things getting \"locked-in\" for a few months.\n>\n> I'm also upset that you have been disrupted like this.\n\nThanks.\n\n> > They didn't lose steam, as I found out when these patches were\n> > submitted.  But, uh, I'm silently torn because I want to help\n> > Christian and Dscho get what they want, but having them be blocked on\n> > progress would reduce my stress.  A lot.\n>\n> From my perspective, git-replay's most important use is being able\n> to generate rebases without a worktree or an interactive user. For\n> now, I don't even care if that includes conflict resolution. That's\n> enough of a lift that has enough unknowns that adding a complex CLI\n> at this early stage seems like a hasty decision to me.  I'm voicing\n> my opinion that we should avoid backwards-compatibility issues by\n> implementing only the essentials.\n\nThat comment is mostly reasonable, but I'd like to nit-pick the last\nsentence: while limiting the series further for now is okay, I don't\nbelieve that only implementing the essentials is any kind of guarantee\nagainst backwards-compatibility issues.  More on that below.\n\n> That said, I also want to make sure that you eventually get the\n> super-flexible command that you want, but only when the framework\n> is ready for that kind of flexibility.\n\nYaay!!!\n\n> > Is there any chance people would be willing to accept a \"NO BACKWARD\n> > COMPATIBILITY GUARANTEES\" disclaimer on this command for a (long?)\n> > while, like Victoria suggested at Review club?  That might be an\n> > alternate solution that would lower my worries.\n>\n> I'm not crazy about this idea, especially when it is easy to do\n> something simpler and avoid the need for it. But I'm just one voice\n> and one opinion.\n\nSorry I wasn't clear.  I wasn't suggesting this as a way of avoiding\nsomething simpler; I was suggesting it as an addition to doing\nsomething simpler, because it's not clear to me that doing something\nsimpler is sufficient.\n\nActually, I think it goes a bit further than that.  Most of my\nobjections to various forms of simplifying are due to the fact that I\nthink that simplifying greatly increases the risks of accidental\nbackward compatibility issues.\n\nPart of the reason why I feel that way is that simplifying has to go\nthrough future rounds of review and people may well respond with, \"oh,\nthat'd be easier if you just did <X> instead of <Y>\" with an implicit\nassumption that there's no difference between them if <simplification\nZ> is being presented as all we need, since <X> and <Y> handle that\nsimplification equally well.  It's kind of hard to guess in advance\nall the forms that <X>, <Y>, and <Z> combined can take, and I also\nhave no idea if I'll have time to respond to upcoming reviews in a\ntimely fashion given current conditions, especially as I'm not the one\nsubmitting the patches, so simplifying is a *big* risk in my book.\nAnd this clearly isn't just theoretical, as this thread started\nprecisely with a simplification suggestion that would have broken\neverything.  But even more importantly, I didn't start git-replay\nuntil I found too many backward compatibility issues in rebase that\njust couldn't be resolved, and yet I know that when folks review\npatches they will make suggestions in line with what they are familiar\nwith, likely meaning rebase-like behavior.  So, \"limiting to the\nessentials\" sounds like \"increase the risk of future problems\" to\nme...unless we include a\nbackward-compatibility-explicitly-not-guaranteed-right-now disclaimer\nfrom the beginning.\n"},{"id":"476891","messageId":"20230509175347.1714141-2-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 01/15] t6429: remove switching aspects of fast-rebase","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:33Z","receivedAt":"2023-05-09T17:54:18Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAt the time t6429 was written, merge-ort was still under development,\ndid not have quite as many tests, and certainly was not widely deployed.\nSince t6429 was exercising some codepaths just a little differently, we\nthought having them also test the \"merge_switch_to_result()\" bits of\nmerge-ort was useful even though they weren't intrinsic to the real\npoint of these tests.\n\nHowever, the value provided by doing extra testing of the\n\"merge_switch_to_result()\" bits has decreased a bit over time, and it's\nactively making it harder to refactor `test-tool fast-rebase` into `git\nreplay`, which we are going to do in following commits.  Dispense with\nthese bits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/helper/test-fast-rebase.c              | 9 +--------\n t/t6429-merge-sequence-rename-caching.sh | 9 +++++++--\n 2 files changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c\nindex d1d63feaa9..403bdf8e75 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/t/helper/test-fast-rebase.c\n@@ -193,7 +193,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n-\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\tmerge_finalize(&merge_opt, &result);\n \n \tif (result.clean < 0)\n \t\texit(128);\n@@ -212,9 +212,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n-\n-\t\tprime_cache_tree(the_repository, the_repository->index,\n-\t\t\t\t result.tree);\n \t} else {\n \t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n@@ -227,10 +224,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t\tdie(\"Failed to update %s\", argv[4]);\n \t\t}\n \t}\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-\n \tret = (result.clean == 0);\n cleanup:\n \tstrbuf_release(&reflog_msg);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex d02fa16614..75d3fd2dba 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -72,6 +72,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \t\tgit switch upstream &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n@@ -200,6 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -277,6 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -356,8 +359,6 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n \t\t#git cherry-pick upstream..topic &&\n \n-\t\tgrep CONFLICT..rename/rename output &&\n-\n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n \t)\n@@ -456,6 +457,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -522,6 +524,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -624,6 +627,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -682,6 +686,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476892","messageId":"20230509175347.1714141-1-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230407072415.1360068-1-christian.couder@gmail.com","subject":"[PATCH v2 00/15] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:32Z","receivedAt":"2023-05-09T17:54:20Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"# Intro\n\n`git replay` has initially been developed entirely by Elijah Newren\nmostly last year (2022) at:\n\nhttps://github.com/newren/git/commits/replay\n\nI took over a few months ago to polish and upstream it as GitLab is\ninterested in replacing libgit2, and for that purpose needs a command\nto do server side (so without using a worktree) rebases, cherry-picks\nand reverts.\n\nI reduced the number of commits and features in this first patch\nseries, compared to what Elijah already developed. Especially I\nstopped short of replaying merge commits and replaying\ninteractively. These and other features might be upstreamed in the\nfuture after this patch series has graduated.\n\nThe focus in this series is to make it a good plumbing command that\ncan already be used server side and that replaces the \"fast-rebase\"\ntest-tool command. So things to make it easier to use on the command\nline, and more advanced features (like replaying merges) are left out.\n\n# Content of this cover letter\n\nThe \"Quick Overview\" and \"Reasons for diverging from cherry-pick &\nrebase\" sections just below are describing the purpose of the new\ncommand in the big scheme of things. They are taken from Elijah's\ndesign notes\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt)\nand describe what we want this command to become and the reasons for\nthat, not what the command is after only this patch series. Also these\ndesign notes were written at least one year ago, so parts of those 2\nsections are not true anymore. I have added Phillip Wood's or Felipe\nContreras' notes (thanks to them) where that's the case, but some now\nflawed parts may have missed.\n\nAfter these two sections, starting with the \"Important limitations\"\nsection, you will find sections describing what is actually in this\npatch series.\n\nMore interesting material is available in Elijah's design notes like\nan \"Intro via examples\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L37-L132),\na discussion about \"Preserving topology, replaying merges\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L264-L341)\nand a \"Current status\" section describing Elijah's work\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L344-L392)\nbefore I started working on upstreaming it.\n\nI have not included this material here though, as the documentation\nadded by this patch series for the `git replay` command already\nincludes an \"EXAMPLES\" section, and other sections of Elijah's design\nnotes might not be interesting for now. Also this cover letter is\nalready pretty long.  But reviewers can refer to the links above if\nthey think it can help.\n\n# Quick Overview (from Elijah's design notes)\n\n`git replay`, at a basic level, can perhaps be thought of as a\n\"default-to-dry-run rebase\" -- meaning no updates to the working tree,\nor to the index, or to any references.  However, it differs from\nrebase in that it:\n\n  * Works for branches that aren't checked out\n\n  * Works in a bare repository\n\n  * Can replay multiple branches simultaneously (with or without common\n    history in the range being replayed)\n\n  * Preserves relative topology by default (merges are replayed too in\n    Elijah's original work, not in this series)\n\n  * Focuses on performance\n\n  * Has several altered defaults as a result of the above\n\nI sometimes think of `git replay` as \"fast-replay\", a patch-based\nanalogue to the snapshot-based fast-export & fast-import tools.\n\n# Reasons for diverging from cherry-pick & rebase (from Elijah's\n  design notes)\n\nThere are multiple reasons to diverge from the defaults in cherry-pick and\nrebase.\n\n* Server side needs\n\n  * Both cherry-pick and rebase, via the sequencer, are heavily tied\n    to updating the working tree, index, some refs, and a lot of\n    control files with every commit replayed, and invoke a mess of\n    hooks[1] that might be hard to avoid for backward compatibility\n    reasons (at least, that's been brought up a few times on the\n    list).\n\n  * cherry-pick and rebase both fork various subprocesses\n    unnecessarily, but somewhat intrinsically in part to ensure the\n    same hooks are called that old scripted implementations would have\n    called.\n\n    Note: since 356ee4659bb (sequencer: try to commit without forking\n    'git commit', 2017-11-24) cherry-pick and rebase do not fork\n    subprocesses other than hooks for the cases covered by this patch\n    series (i.e. they do not fork \"git commit\" for simple picks).\n\n  * \"Dry run\" behavior, where there are no updates to worktree, index,\n    or even refs might be important.\n\n  * Should not assume users only want to operate on HEAD (see next\n    section)\n\n* Decapitate HEAD-centric assumptions\n\n  * cherry-pick forces commits to be played on top of HEAD;\n    inflexible.\n\n  * rebase assumes the range of commits to be replayed is\n    upstream..HEAD by default, though it allows one to replay\n    upstream..otherbranch -- but it still forcibly and needlessly\n    checks out 'otherbranch' before starting to replay things.\n\n    Note: since 767a9c417eb (rebase -i: stop checking out the tip of\n    the branch to rebase, 2020-01-24) it's not true that rebase\n    forcibly and needlessly checks out 'otherbranch'.\n\n  * Assuming HEAD is involved severely limits replaying multiple\n    (possibly divergent) branches.\n\n    Note: since 89fc0b53fdb (rebase: update refs from 'update-ref'\n    commands, 2022-07-19) the sequencer can update multiple\n    branches. The issue with divergent branch is with command line\n    arguments and the todo list generation rather than the\n    capabilities of the sequencer.\n\n  * Once you stop assuming HEAD has a certain meaning, there's not\n    much reason to have two separate commands anymore (except for the\n    funny extra not-necessarily-compatible options both have gained\n    over time).\n\n  * (Micro issue: Assuming HEAD is involved also makes it harder for\n    new users to learn what rebase means and does; it makes command\n    lines hard to parse.  Not sure I want to harp on this too much, as\n    I have a suspicion I might be creating a tool for experts with\n    complicated use cases, but it's a minor quibble.)\n\n* Performance\n\n  * jj is slaughtering us on rebase speed[2].  I would like us to become\n    competitive.  (I dropped a few comments in the link at [2] about why\n    git is currently so bad.)\n\n  * From [3], there was a simple 4-patch series in linux.git that took\n    53 seconds to rebase.  Switching to ort dropped it to 16 seconds.\n    While that sounds great, only 11 *milliseconds* were needed to do\n    the actual merges.  That means almost *all* the time (>99%) was\n    overhead!  Big offenders:\n\n    * --reapply-cherry-picks should be the default\n\n    * can_fast_forward() should be ripped out, and perhaps other extraneous\n      revision walks\n\n      Note: d42c9ffa0f (rebase: factor out branch_base calculation,\n      2022-10-17) might already deal with that (according to Felipe\n      Contreras).\n\n    * avoid updating working tree, index, refs, reflogs, and control\n      structures except when needed (e.g. hitting a conflict, or operation\n      finished)\n\n  * Other performance ideas (mostly for future work, not in this\n    series)\n\n    * single-file control structures instead of directory of files\n      (when doing interactive things which is in Elijah's original\n      work, but not in this series)\n\n    * avoid forking subprocesses unless explicitly requested (e.g.\n      --exec, --strategy, --run-hooks).  For example, definitely do not\n      invoke `git commit` or `git merge`.\n\n    * Sanitize hooks:\n\n      * dispense with all per-commit hooks for sure (pre-commit,\n        post-commit, post-checkout).\n\n      * pre-rebase also seems to assume exactly 1 ref is written, and\n        invoking it repeatedly would be stupid.  Plus, it's specific\n        to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n        rebase probably broke the pre-rebase assumptions already...)\n\n      * post-rewrite hook might make sense, but fast-import got\n        exempted, and I think of replay like a patch-based analogue\n        to the snapshot-based fast-import.\n\n    * When not running server side, resolve conflicts in a sparse-cone\n      sparse-index worktree to reduce number of files written to a\n      working tree.  (See below as well.)\n\n    * [High risk of possible premature optimization] Avoid large\n      numbers of newly created loose objects, when replaying large\n      numbers of commits.  Two possibilities: (1) Consider using\n      tmp-objdir and pack objects from the tmp-objdir at end of\n      exercise, (2) Lift code from git-fast-import to immediately\n      stuff new objects into a pack?\n\n* Multiple branches and non-checked out branches\n\n  * The ability to operate on non-checked out branches also implies\n    that we should generally be able to replay when in a dirty working\n    tree (exception being when we expect to update HEAD and any of the\n    dirty files is one that needs to be updated by the replay).\n\n  * Also, if we are operating locally on a non-checked out branch and\n    hit a conflict, we should have a way to resolve the conflict\n    without messing with the user's work on their current\n    branch. (This is not is this patch series though.)\n\n    * Idea: new worktree with sparse cone + sparse index checkout,\n      containing only files in the root directory, and whatever is\n      necessary to get the conflicts\n\n    * Companion to above idea: control structures should be written to\n      $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n      replay sessions, and so we know which worktrees are associated\n      with which replay operations.\n\n  - [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/\n  - [2] https://github.com/martinvonz/jj/discussions/49\n  - [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/\n\n# Important limitations\n\n* The code exits with code 1 if there are any conflict. No\n  resumability. No nice output. No interactivity. No special exit code\n  depending on the reason.\n\n* When a commit becomes empty as it is replayed, it is still replayed\n  as an empty commit, instead of being dropped.\n\n* No replaying merges, nor root commits. Only regular commits.\n\n* Signed commits are not properly handled. It's not clear what to do\n  to such commits when replaying on the server side.\n\n* Notes associated with replayed commits are not updated nor\n  duplicated. (Thanks to Phillip Wood for noticing.)\n\n# Commit overview\n\n* 1/15 t6429: remove switching aspects of fast-rebase\n\n    New preparatory commit to make it easier to later replace the\n    fast-rebase test-tool by `git replay` without breaking existing\n    tests.\n\n* 2/15 replay: introduce new builtin\n\n     This creates a minimal `git replay` command by moving the code\n     from the `fast-rebase` test helper from `t/helper/` into\n     `builtin/` and doing some renames and a few other needed changes.\n     (No change on this commit since v1.)\n\n* - 3/15 replay: start using parse_options API\n  - 4/15 replay: die() instead of failing assert()\n  - 5/15 replay: introduce pick_regular_commit()\n  - 6/15 replay: don't simplify history\n  - 7/15 replay: add an important FIXME comment about gpg signing\n  - 8/15 replay: remove progress and info output\n  - 9/15 replay: remove HEAD related sanity check\n\n     These slowly change the command to make it behave more like a\n     regular commands and to start cleaning up its output. (No change\n     on these commits since v1.)\n\n* 10/15 replay: make it a minimal server side command\n\n     After the cleaning up in previous commits, it's now time to\n     radically change the way it works by stopping it to do ref\n     updates, to update the index and worktree, to consider HEAD as\n     special. Instead just make it output commands that should be\n     passed to `git update-ref --stdin`. (No change on this commit\n     since v1.)\n\n* 11/15 replay: use standard revision ranges\n\n     Start addind new interesting features and also documentation and\n     tests, as the interface of the command is cristalizing into its\n     final form.\n\n     Since v1, new tests to check that the command works well on bare\n     repo and exits with code 1 in case of conflict have been\n     added. The doc also now says that the new command is\n     experimental.\n\n* 12/15 replay: disallow revision specific options and pathspecs\n\n     New commit to disallow for now revision specific options and\n     pathspecs that are allowed and eaten by setup_revisions(), as\n     it's not clear if all of these extra features are really needed,\n     and anyway they would require tests and doc. So we leave them for\n     future improvements.\n\n* - 13/15 replay: add --advance or 'cherry-pick' mode\n  - 14/15 replay: add --contained to rebase contained branches\n\n     New commits, but extracted from a previous commit (that added both\n     --advance and --contained at the same time) without any code\n     change except that a few test cases for bare repos were added.\n\n* 15/15 replay: stop assuming replayed branches do not diverge\n\n      This adds another interesting feature, as well as related\n      documentation and tests. (Since v1 a few test cases for bare\n      repos were added.)\n\n# Notes about `fast-rebase`, tests and documentation\n\nThe `fast-rebase` test-tool helper was developed by Elijah to\nexperiment with a rebasing tool that would be developed from scratch\nbased on his merge-ort work, could be used to test that merge-ort\nwork, and would not have the speed and interface limitations of `git\nrebase` or `git cherry-pick`.\n\nThis `fast-rebase` helper was used before this series in:\n\nt6429-merge-sequence-rename-caching.sh\n\nSo when `git replay` is created from `fast-rebase` in patch 2/15, the\nt6429 test script is also converted to use `git replay`. This ensures\nthat `git replay` doesn't break too badly during the first 10 patches\nin this patch series.\n\nTests and documentation are introduced specifically for `git replay`\nonly in 11/15 and later patches as it doesn't make much sense to\ndocument and test behavior that we know is going to change soon. So\nit's only when the command is crystalizing towards its final form that\nwe start documenting and testing it.\n\n# Possibly controversial issues \n\n* bare or not bare: this series works towards a plumbing command with\n  the end goal of it being usable and used first on bare repos,\n  contrary to existing commands like `git rebase` and `git\n  cherry-pick`. The tests check that the command works on both bare\n  and non-bare repo though.\n\n* exit status: a successful, non-conflicted replay exits with code\n  0. When the replay has conflicts, the exit status is 1. If the\n  replay is not able to complete (or start) due to some kind of error,\n  the exit status is something other than 0 or 1. There are a few\n  tests checking that. It has been suggested in an internal review\n  that conflicts might want to get a more specific error code as an\n  error code of 1 might be quite easy to return by accident. It\n  doesn't seem to me from their docs (which might want to be improved,\n  I didn't look at the code) that other commands like `git merge` and\n  `git rebase` exit with a special error code in case of conflict.\n\n* make worktree and index changes optional: commit 10/15 stops\n  updating the index and worktree, but it might be better especially\n  for cli users to make that optional. The issue is that this would\n  make the command more complex while we are developing a number of\n  important features so that the command can be used on bare repos. It\n  seems that this should rather be done in an iterative improvement\n  after the important features have landed.\n\n* when and where to add tests and docs: although t6429 has tests that\n  are changed to use the new command instead of the fast-rebase\n  test-tool command as soon as the former is introduced, there is no\n  specific test script and no doc for the new command until commit\n  11/15 when standard revision ranges are used. This is done to avoid\n  churn in tests and docs while the final form of the command hasn't\n  crystalized enough. Adding tests and doc at this point makes this\n  commit quite big and possibly more difficult to review than if they\n  were in separate commits though. On the other hand when tests and\n  docs are added in specific commits some reviewers say it would be\n  better to introduce them when the related changes are made.\n\n* --advance and --contained: these two advanced options might not\n  belong to this first series and could perhaps be added in a followup\n  series in separate commits. On the other hand the code for\n  --contained seems involved with the code of --advance and it's nice\n  to see soon that git replay can indeed do cherry-picking and rebase\n  many refs at once, and this way fullfil these parts of its promise.\n\n* replaying diverging branches: 15/15 the last patch in the series,\n  which allow replaying diverging branches, can be seen as a\n  fundamental fix or alternatively as adding an interesting\n  feature. So it's debatable if it should be in its own patch along\n  with its own tests as in this series, or if it should be merged into\n  a previous patch and which one.\n\n* only 2 patches: this patch series can be seen from a high level\n  point of view as 1) introducing the new `git replay` command, and 2)\n  using `git replay` to replace, and get rid of, the fast-rebase\n  test-tool command. The fact that not much of the original\n  fast-rebase code and interface is left would agree with that point\n  of view. On the other hand, fast-rebase can also be seen as a first\n  iteration towards `git replay`. So it can also make sense to see how\n  `git replay` evolved from it.\n\n# Changes between v1 and v2\n\nThanks to all the reviewers on the mailing list or during a Review\nClub! Especially, thanks to Elijah, Dscho, Phillip Wood, Derrick\nStolee, Patrick Steinhardt, Junio, Felipe Contreras, Calvin Wan and\nall the Review Club participants.\n\nThe Review Club notes (see those on 2023-04-18) are available on:\n\nhttps://docs.google.com/document/d/14L8BAumGTpsXpjDY8VzZ4rRtpAjuGrFSRqn3stCuS_w/\n\nNow the changes between v1 and v2 are:\n\n* The patch series has been rebased on top of current master, so that\n  conflicts with master are resolved. This required replacing calls to\n  functions like get_oid() with repo_get_oid(), as well as changing\n  some `#include \"*.h\"` to use different header files. This\n  unfortunately makes the range-diff between v1 and v2 a bit more\n  difficult to read than we would like.\n\n* Patch 1/15 is a new patch. It has been introduced to rework t6429,\n  so that this test script doesn't break when conflict output is\n  removed later in the series. This allowed us to remove the old 9/14\n  patch (\"replay: very coarse worktree updating\").\n\n* The old 9/14 patch (\"replay: very coarse worktree updating\") was\n  removed. This is to make sure that the new command can be used on\n  bare repos. This enables us to focus first on making sure the new\n  command a good plumbing command that works well on bare repos.\n\n* The old 12/14 patch (\"replay: introduce guess_new_base()\") was\n  removed. This is also to focus first on making sure the new command\n  is a good plumbing command.\n\n* The old 13/14 patch (\"replay: add different modes\") has been split\n  into 2 patches: patch 13/15 (\"replay: add --advance or 'cherry-pick'\n  mode\") and patch 14/15 (\"replay: add --contained to rebase contained\n  branches\").\n\n* Patch 12/15 (\"replay: disallow revision specific options and\n  pathspecs\") is a new patch that disallow using revision specific\n  options that would be allowed and eaten by setup_revisions(). Even\n  if it would be very nice to have some of these options in the\n  future, not all these options make sense for the new command, and it\n  might require improving the setup_revisions() mechanism (as well as\n  adding tests and doc) to cleanly allow those we want, but that is\n  outside the scope of this first patch series.\n\n  While at it, this patch also disallow pathspecs too. It would be\n  nice to have them in the future, but for now it is not necessary and\n  would require work (at least for tests and doc) that can be done\n  later.\n\n* Patches 11/15 to 15/15 include a number of new tests to check that\n  the command works well on bare repos, and in case of 11/15 to check\n  that exit code in case of conflict is 1.\n\n* Patch 11/15, in which the doc for the `git replay` command is\n  introduced, now says that the new command is experimental.\n\n* Note that a segfault that was found by Derrick Stolee in the v1 is\n  fixed by 000c4ceca7 (merge-ort: fix calling merge_finalize() with no\n  intermediate merge, 2023-04-22) merged to master by 3927312601\n  (Merge branch 'en/ort-finalize-after-0-merges-fix', 2023-05-02).\n\n\nChristian Couder (1):\n  replay: disallow revision specific options and pathspecs\n\nElijah Newren (14):\n  t6429: remove switching aspects of fast-rebase\n  replay: introduce new builtin\n  replay: start using parse_options API\n  replay: die() instead of failing assert()\n  replay: introduce pick_regular_commit()\n  replay: don't simplify history\n  replay: add an important FIXME comment about gpg signing\n  replay: remove progress and info output\n  replay: remove HEAD related sanity check\n  replay: make it a minimal server side command\n  replay: use standard revision ranges\n  replay: add --advance or 'cherry-pick' mode\n  replay: add --contained to rebase contained branches\n  replay: stop assuming replayed branches do not diverge\n\n .gitignore                               |   1 +\n Documentation/git-replay.txt             | 125 +++++++\n Makefile                                 |   2 +-\n builtin.h                                |   1 +\n builtin/replay.c                         | 431 +++++++++++++++++++++++\n command-list.txt                         |   1 +\n git.c                                    |   1 +\n t/helper/test-fast-rebase.c              | 240 -------------\n t/helper/test-tool.c                     |   1 -\n t/helper/test-tool.h                     |   1 -\n t/t3650-replay-basics.sh                 | 206 +++++++++++\n t/t6429-merge-sequence-rename-caching.sh |  45 +--\n 12 files changed, 792 insertions(+), 263 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100644 builtin/replay.c\n delete mode 100644 t/helper/test-fast-rebase.c\n create mode 100755 t/t3650-replay-basics.sh\n\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476893","messageId":"20230509175347.1714141-3-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 02/15] replay: introduce new builtin","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:34Z","receivedAt":"2023-05-09T17:54:22Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor now, this is just a rename from `t/helper/test-fast-rebase.c` into\n`builtin/replay.c` with minimal changes to make it build appropriately.\n\nSubsequent commits will flesh out its capabilities and make it a more\nstandard regular builtin.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n .gitignore                                    |  1 +\n Makefile                                      |  2 +-\n builtin.h                                     |  1 +\n .../test-fast-rebase.c => builtin/replay.c    | 25 +++++------------\n command-list.txt                              |  1 +\n git.c                                         |  1 +\n t/helper/test-tool.c                          |  1 -\n t/helper/test-tool.h                          |  1 -\n t/t6429-merge-sequence-rename-caching.sh      | 27 +++++++------------\n 9 files changed, 20 insertions(+), 40 deletions(-)\n rename t/helper/test-fast-rebase.c => builtin/replay.c (89%)\n\ndiff --git a/.gitignore b/.gitignore\nindex e875c59054..b5f025a296 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -135,6 +135,7 @@\n /git-remote-ext\n /git-repack\n /git-replace\n+/git-replay\n /git-request-pull\n /git-rerere\n /git-reset\ndiff --git a/Makefile b/Makefile\nindex e440728c24..a6ab78840f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -799,7 +799,6 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o\n TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n TEST_BUILTINS_OBJS += test-env-helper.o\n TEST_BUILTINS_OBJS += test-example-decorate.o\n-TEST_BUILTINS_OBJS += test-fast-rebase.o\n TEST_BUILTINS_OBJS += test-fsmonitor-client.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n TEST_BUILTINS_OBJS += test-genzeros.o\n@@ -1286,6 +1285,7 @@ BUILTIN_OBJS += builtin/remote-fd.o\n BUILTIN_OBJS += builtin/remote.o\n BUILTIN_OBJS += builtin/repack.o\n BUILTIN_OBJS += builtin/replace.o\n+BUILTIN_OBJS += builtin/replay.o\n BUILTIN_OBJS += builtin/rerere.o\n BUILTIN_OBJS += builtin/reset.o\n BUILTIN_OBJS += builtin/rev-list.o\ndiff --git a/builtin.h b/builtin.h\nindex cb0db67681..c3f0b56915 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -214,6 +214,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix);\n int cmd_remote_ext(int argc, const char **argv, const char *prefix);\n int cmd_remote_fd(int argc, const char **argv, const char *prefix);\n int cmd_repack(int argc, const char **argv, const char *prefix);\n+int cmd_replay(int argc, const char **argv, const char *prefix);\n int cmd_rerere(int argc, const char **argv, const char *prefix);\n int cmd_reset(int argc, const char **argv, const char *prefix);\n int cmd_restore(int argc, const char **argv, const char *prefix);\ndiff --git a/t/helper/test-fast-rebase.c b/builtin/replay.c\nsimilarity index 89%\nrename from t/helper/test-fast-rebase.c\nrename to builtin/replay.c\nindex 403bdf8e75..9637c31da2 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/builtin/replay.c\n@@ -1,18 +1,11 @@\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+ * \"git replay\" builtin command\n  */\n \n #define USE_THE_INDEX_VARIABLE\n-#include \"test-tool.h\"\n-#include \"cache.h\"\n+#include \"git-compat-util.h\"\n+\n+#include \"builtin.h\"\n #include \"cache-tree.h\"\n #include \"commit.h\"\n #include \"environment.h\"\n@@ -93,7 +86,7 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n-int cmd__fast_rebase(int argc, const char **argv)\n+int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n@@ -109,12 +102,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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@@ -135,7 +122,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n \n-\trepo_init_revisions(the_repository, &revs, NULL);\n+\trepo_init_revisions(the_repository, &revs, prefix);\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\ndiff --git a/command-list.txt b/command-list.txt\nindex 54b2a50f5f..d74836ab21 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -160,6 +160,7 @@ git-reflog                              ancillarymanipulators           complete\n git-remote                              ancillarymanipulators           complete\n git-repack                              ancillarymanipulators           complete\n git-replace                             ancillarymanipulators           complete\n+git-replay                              mainporcelain           history\n git-request-pull                        foreignscminterface             complete\n git-rerere                              ancillaryinterrogators\n git-reset                               mainporcelain           history\ndiff --git a/git.c b/git.c\nindex 45899be826..a1252e4cd3 100644\n--- a/git.c\n+++ b/git.c\n@@ -577,6 +577,7 @@ static struct cmd_struct commands[] = {\n \t{ \"remote-fd\", cmd_remote_fd, NO_PARSEOPT },\n \t{ \"repack\", cmd_repack, RUN_SETUP },\n \t{ \"replace\", cmd_replace, RUN_SETUP },\n+\t{ \"replay\", cmd_replay, RUN_SETUP },\n \t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t{ \"reset\", cmd_reset, RUN_SETUP },\n \t{ \"restore\", cmd_restore, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex abe8a785eb..9ca1586de7 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -30,7 +30,6 @@ static struct test_cmd cmds[] = {\n \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n \t{ \"env-helper\", cmd__env_helper },\n \t{ \"example-decorate\", cmd__example_decorate },\n-\t{ \"fast-rebase\", cmd__fast_rebase },\n \t{ \"fsmonitor-client\", cmd__fsmonitor_client },\n \t{ \"genrandom\", cmd__genrandom },\n \t{ \"genzeros\", cmd__genzeros },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ea2672436c..a03bbfc6b2 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -24,7 +24,6 @@ int cmd__dump_untracked_cache(int argc, const char **argv);\n int cmd__dump_reftable(int argc, const char **argv);\n int cmd__env_helper(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__fsmonitor_client(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n int cmd__genzeros(int argc, const char **argv);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 75d3fd2dba..7670b72008 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,9 +71,8 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -141,8 +140,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -200,9 +198,8 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -278,9 +275,8 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -356,8 +352,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,9 +451,8 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -523,9 +517,8 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -626,9 +619,8 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -685,9 +677,8 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476894","messageId":"20230509175347.1714141-5-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 04/15] replay: die() instead of failing assert()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:36Z","receivedAt":"2023-05-09T17:54:25Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIt's not a good idea for regular Git commands to use an assert() to\ncheck for things that could happen but are not supported.\n\nLet's die() with an explanation of the issue instead.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 4644bee3cf..366ea8ef08 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -177,7 +177,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\n+\t\tif (!commit->parents)\n+\t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n+\t\tif (commit->parents->next)\n+\t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n+\n \t\tbase = commit->parents->item;\n \n \t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476895","messageId":"20230509175347.1714141-4-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 03/15] replay: start using parse_options API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:35Z","receivedAt":"2023-05-09T17:54:28Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of manually parsing arguments, let's start using the parse_options\nAPI. This way this new builtin will look more standard, and in some\nupcoming commits will more easily be able to handle more command line\noptions.\n\nNote that we plan to later use standard revision ranges instead of\nhardcoded \"<oldbase> <branch>\" arguments. When we will use standard\nrevision ranges, it will be easier to check if there are no spurious\narguments if we keep ARGV[0], so let's call parse_options() with\nPARSE_OPT_KEEP_ARGV0 even if we don't need ARGV[0] right now to avoid\nsome useless code churn.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 44 ++++++++++++++++++++++++++++++++------------\n 1 file changed, 32 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 9637c31da2..4644bee3cf 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,6 +14,7 @@\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n #include \"object-name.h\"\n+#include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n #include \"sequencer.h\"\n@@ -89,6 +90,7 @@ static struct commit *create_commit(struct tree *tree,\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n+\tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n \tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n@@ -102,16 +104,32 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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+\tconst char * const replay_usage[] = {\n+\t\tN_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tNULL\n+\t};\n+\tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"onto\", &onto_name,\n+\t\t\t   N_(\"revision\"),\n+\t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n+\n+\tif (!onto_name) {\n+\t\terror(_(\"option --onto is mandatory\"));\n+\t\tusage_with_options(replay_usage, replay_options);\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+\tif (argc != 3) {\n+\t\terror(_(\"bad number of arguments\"));\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n \n-\tonto = peel_committish(argv[2]);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\tonto = peel_committish(onto_name);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \t/* Sanity check */\n \tif (repo_get_oid(the_repository, \"HEAD\", &head))\n@@ -123,6 +141,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tBUG(\"Could not read index\");\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n+\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\n@@ -131,7 +150,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n@@ -194,8 +214,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &last_picked_commit->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -207,8 +227,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &head,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t}\n \tret = (result.clean == 0);\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476896","messageId":"20230509175347.1714141-6-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 05/15] replay: introduce pick_regular_commit()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:37Z","receivedAt":"2023-05-09T17:54:41Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's refactor the code to handle a regular commit (a commit that is\nneither a root commit nor a merge commit) into a single function instead\nof keeping it inside cmd_replay().\n\nThis is good for separation of concerns, and this will help further work\nin the future to replay merge commits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 54 ++++++++++++++++++++++++++++++------------------\n 1 file changed, 34 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 366ea8ef08..f087d97353 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -87,6 +87,35 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+static struct commit *pick_regular_commit(struct commit *pickme,\n+\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  struct merge_options *merge_opt,\n+\t\t\t\t\t  struct merge_result *result)\n+{\n+\tstruct commit *base;\n+\tstruct tree *pickme_tree, *base_tree;\n+\n+\tbase = pickme->parents->item;\n+\n+\tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n+\tbase_tree = repo_get_commit_tree(the_repository, base);\n+\n+\tmerge_opt->branch2 = short_commit_name(pickme);\n+\tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n+\n+\tmerge_incore_nonrecursive(merge_opt,\n+\t\t\t\t  base_tree,\n+\t\t\t\t  result->tree,\n+\t\t\t\t  pickme_tree,\n+\t\t\t\t  result);\n+\n+\tfree((char*)merge_opt->ancestor);\n+\tmerge_opt->ancestor = NULL;\n+\tif (!result->clean)\n+\t\treturn NULL;\n+\treturn create_commit(result->tree, pickme, last_commit);\n+}\n+\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n@@ -98,7 +127,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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 tree *head_tree;\n \tstruct merge_result result;\n \tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n@@ -173,7 +202,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tresult.tree = head_tree;\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *base;\n+\t\tstruct commit *pick;\n \n \t\tfprintf(stderr, \"Rebasing %s...\\r\",\n \t\t\toid_to_hex(&commit->object.oid));\n@@ -183,26 +212,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tbase = commit->parents->item;\n-\n-\t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-\t\tbase_tree = repo_get_commit_tree(the_repository, 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\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!pick)\n \t\t\tbreak;\n+\t\tlast_commit = pick;\n \t\tlast_picked_commit = commit;\n-\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476897","messageId":"20230509175347.1714141-7-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 06/15] replay: don't simplify history","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:38Z","receivedAt":"2023-05-09T17:54:42Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's set the rev walking options we need after calling\nsetup_revisions() instead of before. This makes it clearer which options\nwe need.\n\nAlso we don't want history simplification, as we want to deal with all\nthe commits in the affected range.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 15 ++++++---------\n 1 file changed, 6 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex f087d97353..9f6cca2972 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -171,15 +171,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \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-\n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n@@ -187,6 +178,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \n+\t/* requirements/overrides for revs */\n+\trevs.reverse = 1;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\trevs.simplify_history = 0;\n+\n \tstrvec_clear(&rev_walk_args);\n \n \tif (prepare_revision_walk(&revs) < 0) {\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476898","messageId":"20230509175347.1714141-8-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 07/15] replay: add an important FIXME comment about gpg signing","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:39Z","receivedAt":"2023-05-09T17:54:45Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want to be able to handle signed commits in some way in the future,\nbut we are not ready to do it now. So for the time being let's just add\na FIXME comment to remind us about it.\n\nThese are different ways we could handle them:\n\n  - in case of a cli user and if there was an interactive mode, we could\n    perhaps ask if the user wants to sign again\n  - we could add an option to just fail if there are signed commits\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 9f6cca2972..a9d88ac6df 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -60,7 +60,7 @@ static struct commit *create_commit(struct tree *tree,\n \tstruct object *obj;\n \tstruct commit_list *parents = NULL;\n \tchar *author;\n-\tchar *sign_commit = NULL;\n+\tchar *sign_commit = NULL; /* FIXME: cli users might want to sign again */\n \tstruct commit_extra_header *extra;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *out_enc = get_commit_output_encoding();\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476899","messageId":"20230509175347.1714141-9-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 08/15] replay: remove progress and info output","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:40Z","receivedAt":"2023-05-09T17:54:47Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command will be changed in a follow up commit, so that it\nwill not update refs directly, but instead it will print on stdout a\nlist of commands that can be consumed by `git update-ref --stdin`.\n\nWe don't want this output to be polluted by its current low value\noutput, so let's just remove the latter.\n\nIn the future, when the command gets an option to update refs by\nitself, it will make a lot of sense to display a progress meter, but\nwe are not there yet.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 +------\n 1 file changed, 1 insertion(+), 6 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex a9d88ac6df..ccbef6552a 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -193,7 +193,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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.show_rename_progress = 0;\n \tmerge_opt.branch1 = \"HEAD\";\n \thead_tree = repo_get_commit_tree(the_repository, onto);\n \tresult.tree = head_tree;\n@@ -201,9 +201,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \twhile ((commit = get_revision(&revs))) {\n \t\tstruct commit *pick;\n \n-\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n-\t\t\toid_to_hex(&commit->object.oid));\n-\n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n@@ -222,7 +219,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\texit(128);\n \n \tif (result.clean) {\n-\t\tfprintf(stderr, \"\\nDone.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n \t\t\t    oid_to_hex(&last_commit->object.oid));\n@@ -236,7 +232,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t} else {\n-\t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476900","messageId":"20230509175347.1714141-10-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 09/15] replay: remove HEAD related sanity check","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:41Z","receivedAt":"2023-05-09T17:54:56Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want replay to be a command that can be used on the server side on\nany branch, not just the current one, so we are going to stop updating\nHEAD in a future commit.\n\nA \"sanity check\" that makes sure we are replaying the current branch\ndoesn't make sense anymore. Let's remove it.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 8 +-------\n 1 file changed, 1 insertion(+), 7 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex ccbef6552a..6437633724 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -121,7 +121,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct strvec rev_walk_args = STRVEC_INIT;\n \tstruct rev_info revs;\n@@ -160,11 +159,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\t/* Sanity check */\n-\tif (repo_get_oid(the_repository, \"HEAD\", &head))\n-\t\tdie(_(\"Cannot read HEAD\"));\n-\tassert(oideq(&onto->object.oid, &head));\n-\n \trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n@@ -236,7 +230,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n \t\t\t       &last_commit->object.oid,\n-\t\t\t       &head,\n+\t\t\t       &onto->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n \t\t\terror(_(\"could not update %s\"), argv[2]);\n \t\t\tdie(\"Failed to update %s\", argv[2]);\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476901","messageId":"20230509175347.1714141-11-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 10/15] replay: make it a minimal server side command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:42Z","receivedAt":"2023-05-09T17:54:56Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want this command to be a minimal command that just does server side\npicking of commits, displaying the results on stdout for higher level\nscripts to consume.\n\nSo let's simplify it:\n  * remove the worktree and index reading/writing,\n  * remove the ref (and reflog) updating,\n  * remove the assumptions tying us to HEAD, since (a) this is not a\n    rebase and (b) we want to be able to pick commits in a bare repo,\n    i.e. to/from branches that are not checked out and not the main\n    branch,\n  * remove unneeded includes,\n  * handle rebasing multiple branches by printing on stdout the update\n    ref commands that should be performed.\n\nThe output can be piped into `git update-ref --stdin` for the ref\nupdates to happen.\n\nIn the future to make it easier for users to use this command\ndirectly maybe an option can be added to automatically pipe its output\ninto `git update-ref`.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c                         | 79 ++++++++----------------\n t/t6429-merge-sequence-rename-caching.sh | 39 +++++++-----\n 2 files changed, 51 insertions(+), 67 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 6437633724..46f00a2d71 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -6,10 +6,7 @@\n #include \"git-compat-util.h\"\n \n #include \"builtin.h\"\n-#include \"cache-tree.h\"\n-#include \"commit.h\"\n #include \"environment.h\"\n-#include \"gettext.h\"\n #include \"hex.h\"\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n@@ -17,10 +14,7 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"sequencer.h\"\n-#include \"setup.h\"\n #include \"strvec.h\"\n-#include \"tree.h\"\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -100,6 +94,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n+\tmerge_opt->branch1 = short_commit_name(last_commit);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -120,15 +115,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct lock_file lock = LOCK_INIT;\n+\tstruct commit *last_commit = NULL;\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 *head_tree;\n \tstruct merge_result result;\n-\tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n@@ -159,10 +151,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index(the_repository) < 0)\n-\t\tBUG(\"Could not read index\");\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n@@ -188,58 +176,45 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\tmerge_opt.branch1 = \"HEAD\";\n-\thead_tree = repo_get_commit_tree(the_repository, onto);\n-\tresult.tree = head_tree;\n+\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *pick;\n+\t\tconst struct name_decoration *decoration;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n-\t\tif (!pick)\n+\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!last_commit)\n \t\t\tbreak;\n-\t\tlast_commit = pick;\n-\t\tlast_picked_commit = commit;\n+\n+\t\tdecoration = get_name_decoration(&commit->object);\n+\t\tif (!decoration)\n+\t\t\tcontinue;\n+\n+\t\twhile (decoration) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\t\tprintf(\"update %s %s %s\\n\",\n+\t\t\t\t       decoration->name,\n+\t\t\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t\t\t       oid_to_hex(&commit->object.oid));\n+\t\t\t}\n+\t\t\tdecoration = decoration->next;\n+\t\t}\n \t}\n \n+\t/* Cleanup */\n \tmerge_finalize(&merge_opt, &result);\n+\tret = result.clean;\n \n-\tif (result.clean < 0)\n-\t\texit(128);\n-\n-\tif (result.clean) {\n-\t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n-\t\t\t    oid_to_hex(&last_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, branch_name.buf,\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &last_picked_commit->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n-\t\t\tdie(_(\"unable to update HEAD\"));\n-\t} else {\n-\t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &onto->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t}\n-\tret = (result.clean == 0);\n cleanup:\n-\tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n-\treturn ret;\n+\n+\t/* Return */\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret ? 0 : 1;\n }\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 7670b72008..099aefeffc 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,8 +71,9 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -140,7 +141,9 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -198,8 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -275,8 +279,9 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -451,8 +456,9 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -517,8 +523,9 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -619,8 +626,9 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -677,8 +685,9 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476902","messageId":"20230509175347.1714141-12-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 11/15] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:43Z","receivedAt":"2023-05-09T17:55:00Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of the fixed \"<oldbase> <branch>\" arguments, the replay\ncommand now accepts \"<revision-range>...\" arguments in a similar\nway as many other Git commands. This makes its interface more\nstandard and more flexible.\n\nAlso as the interface of the command is now mostly finalized,\nwe can add some documentation as well as testcases to make sure\nthe command will continue to work as designed in the future.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             | 88 ++++++++++++++++++++++++\n builtin/replay.c                         | 21 ++----\n t/t3650-replay-basics.sh                 | 83 ++++++++++++++++++++++\n t/t6429-merge-sequence-rename-caching.sh | 18 ++---\n 4 files changed, 184 insertions(+), 26 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100755 t/t3650-replay-basics.sh\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nnew file mode 100644\nindex 0000000000..394d7b0050\n--- /dev/null\n+++ b/Documentation/git-replay.txt\n@@ -0,0 +1,88 @@\n+git-replay(1)\n+=============\n+\n+NAME\n+----\n+git-replay - Replay commits on a different base, without touching working tree\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git replay' --onto <newbase> <revision-range>...\n+\n+DESCRIPTION\n+-----------\n+\n+Takes a range of commits, and replays them onto a new location.  Does\n+not touch the working tree or index, and does not update any\n+references.  However, the output of this command is meant to be used\n+as input to `git update-ref --stdin`, which would update the relevant\n+branches.\n+\n+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n+\n+OPTIONS\n+-------\n+\n+--onto <newbase>::\n+\tStarting point at which to create the new commits.  May be any\n+\tvalid commit, and not just an existing branch name.\n++\n+The update-ref commands in the output will update the branch(es)\n+in the revision range to point at the new commits (in other\n+words, this mimics a rebase operation).\n+\n+<revision-range>::\n+\tRange of commits to replay; see \"Specifying Ranges\" in\n+\tlinkgit:git-rev-parse.\n+\n+OUTPUT\n+------\n+\n+When there are no conflicts, the output of this command is usable as\n+input to `git update-ref --stdin`.  It is basically of the form:\n+\n+\tupdate refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+\tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+\tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+\n+where the number of refs updated depend on the arguments passed.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted replay, the exit status is 0.  When\n+the replay has conflicts, the exit status is 1.  If the replay is not\n+able to complete (or start) due to some kind of error, the exit status\n+is something other than 0 or 1.\n+\n+EXAMPLES\n+--------\n+\n+To simply rebase mybranch onto target:\n+\n+------------\n+$ git replay --onto target origin/main..mybranch\n+update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n+------------\n+\n+When calling `git replay`, one does not need to specify a range of\n+commits to replay using the syntax `A..B`; any range expression will\n+do:\n+\n+------------\n+$ git replay --onto origin/main ^base branch1 branch2 branch3\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+------------\n+\n+This will simultaneously rebase branch1, branch2, and branch3 -- all\n+commits they have since base, playing them on top of origin/main.\n+These three branches may have commits on top of base that they have in\n+common, but that does not need to be the case.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 46f00a2d71..c855e1a128 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,7 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"strvec.h\"\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -116,16 +115,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL;\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 merge_result result;\n-\tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -143,20 +140,13 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tif (argc != 3) {\n-\t\terror(_(\"bad number of arguments\"));\n-\t\tusage_with_options(replay_usage, replay_options);\n-\t}\n-\n \tonto = peel_committish(onto_name);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n-\n-\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n-\t\tret = error(_(\"unhandled options\"));\n+\targc = setup_revisions(argc, argv, &revs, NULL);\n+\tif (argc > 1) {\n+\t\tret = error(_(\"unrecognized argument: %s\"), argv[1]);\n \t\tgoto cleanup;\n \t}\n \n@@ -166,8 +156,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n-\tstrvec_clear(&rev_walk_args);\n-\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -210,7 +198,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tret = result.clean;\n \n cleanup:\n-\tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nnew file mode 100755\nindex 0000000000..a1da4f9ef9\n--- /dev/null\n+++ b/t/t3650-replay-basics.sh\n@@ -0,0 +1,83 @@\n+#!/bin/sh\n+\n+test_description='basic git replay tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+GIT_AUTHOR_NAME=author@name\n+GIT_AUTHOR_EMAIL=bogus@email@address\n+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\n+\tgit switch -c topic1 &&\n+\ttest_commit C &&\n+\tgit switch -c topic2 &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit switch topic1 &&\n+\ttest_commit F &&\n+\tgit switch -c topic3 &&\n+\ttest_commit G &&\n+\ttest_commit H &&\n+\tgit switch -c topic4 main &&\n+\ttest_commit I &&\n+\ttest_commit J &&\n+\n+\tgit switch -c next main &&\n+\ttest_commit K &&\n+\tgit merge -m \"Merge topic1\" topic1 &&\n+\tgit merge -m \"Merge topic2\" topic2 &&\n+\tgit merge -m \"Merge topic3\" topic3 &&\n+\t>evil &&\n+\tgit add evil &&\n+\tgit commit --amend &&\n+\tgit merge -m \"Merge topic4\" topic4 &&\n+\n+\tgit switch main &&\n+\ttest_commit L &&\n+\ttest_commit M &&\n+\n+\tgit switch -c conflict B &&\n+\ttest_commit C.conflict C.t conflict\n+'\n+\n+test_expect_success 'setup bare' '\n+\tgit clone --bare . bare\n+'\n+\n+test_expect_success 'using replay to rebase two branches, one on top of other' '\n+\tgit replay --onto main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n+\tgit -C bare replay --onto main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n+test_expect_success 'using replay to rebase with a conflict' '\n+\ttest_expect_code 1 git replay --onto topic1 B..conflict\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase with a conflict' '\n+\ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n+'\n+\n+test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 099aefeffc..0f39ed0d08 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,7 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -201,7 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -279,7 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -357,7 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1..topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,7 +456,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -523,7 +523,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -626,7 +626,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -685,7 +685,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476903","messageId":"20230509175347.1714141-13-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 12/15] replay: disallow revision specific options and pathspecs","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:44Z","receivedAt":"2023-05-09T17:55:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"A previous commit changed `git replay` to make it accept standard\nrevision ranges using the setup_revisions() function. While this is good\nthing to make this command more standard and more flexible, it has the\ndownside of enabling many revision related options accepted and eaten by\nsetup_revisions().\n\nSome of these options might make sense, but others, like those\ngenerating non-contiguous history, might not. Anyway those we might want\nto allow should probably be tested and perhaps documented a bit, which\ncould be done in future work.\n\nFor now it is just simpler and safer to just disallow all of them, so\nlet's do that.\n\nOther commands, like `git fast-export`, currently allow all these\nrevision specific options even though some of them might not make sense,\nas these commands also use setup_revisions() but do not check the\noptions that might be passed to this function.\n\nSo a way to fix those commands as well as git replay could be to improve\nor refactor the setup_revisions() mechanism to let callers allow and\ndisallow options in a relevant way for them. Such improvements are\noutside the scope of this work though.\n\nPathspecs, which are also accepted and eaten by setup_revisions(), are\nlikely to result in disconnected history. That could perhaps be useful,\nbut that would need tests and documentation, which can be added in\nfuture work. So, while at it, let's disallow them too.\n\nHelped-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 27 ++++++++++++++++++++++++++-\n t/t3650-replay-basics.sh | 16 ++++++++++++++++\n 2 files changed, 42 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex c855e1a128..7b3c0f2bc4 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -119,7 +119,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n-\tint ret = 0;\n+\tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n \t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n@@ -135,6 +135,20 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n \t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n \n+\t/*\n+\t * TODO: For now, we reject any unknown or invalid option,\n+\t * including revision related ones, like --not,\n+\t * --first-parent, etc that would be allowed and eaten by\n+\t * setup_revisions() below. In the future we should definitely\n+\t * accept those that make sense and add related tests and doc\n+\t * though.\n+\t */\n+\tfor (i = 0; i < argc; i++)\n+\t\tif (argv[i][0] == '-') {\n+\t\t\terror(_(\"invalid option: %s\"), argv[i]);\n+\t\t\tusage_with_options(replay_usage, replay_options);\n+\t\t}\n+\n \tif (!onto_name) {\n \t\terror(_(\"option --onto is mandatory\"));\n \t\tusage_with_options(replay_usage, replay_options);\n@@ -150,6 +164,17 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \n+\t/*\n+\t * TODO: For now, we reject any pathspec. (They are allowed\n+\t * and eaten by setup_revisions() above.) In the future we\n+\t * should definitely accept them and add related tests and doc\n+\t * though.\n+\t */\n+\tif (revs.prune_data.nr) {\n+\t\terror(_(\"invalid pathspec: %s\"), revs.prune_data.items[0].match);\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n+\n \t/* requirements/overrides for revs */\n \trevs.reverse = 1;\n \trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex a1da4f9ef9..de6e40950e 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -80,4 +80,20 @@ test_expect_success 'using replay on bare repo to rebase with a conflict' '\n \ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n '\n \n+test_expect_success 'using replay with (for now) disallowed revision specific option --not' '\n+\ttest_must_fail git replay --onto main topic2 --not topic1\n+'\n+\n+test_expect_success 'using replay on bare repo with (for now) disallowed revision specific option --first-parent' '\n+\ttest_must_fail git -C bare replay --onto main --first-parent topic1..topic2\n+'\n+\n+test_expect_success 'using replay with disallowed pathspec' '\n+\ttest_must_fail git replay --onto main topic1..topic2 A.t\n+'\n+\n+test_expect_success 'using replay on bare repo with disallowed pathspec' '\n+\ttest_must_fail git -C bare replay --onto main topic1..topic2 -- A.t\n+'\n+\n test_done\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476905","messageId":"20230509175347.1714141-14-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 13/15] replay: add --advance or 'cherry-pick' mode","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:45Z","receivedAt":"2023-05-09T17:55:15Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere is already a 'rebase' mode with `--onto`. Let's add an 'advance' or\n'cherry-pick' mode with `--advance`. This new mode will make the target\nbranch advance as we replay commits onto it.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt |  33 ++++++-\n builtin/replay.c             | 185 +++++++++++++++++++++++++++++++++--\n t/t3650-replay-basics.sh     |  26 +++++\n 3 files changed, 230 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 394d7b0050..439b2f92e7 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n SYNOPSIS\n --------\n [verse]\n-'git replay' --onto <newbase> <revision-range>...\n+'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -29,9 +29,17 @@ OPTIONS\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n +\n-The update-ref commands in the output will update the branch(es)\n-in the revision range to point at the new commits (in other\n-words, this mimics a rebase operation).\n+When `--onto` is specified, the update-ref command(s) in the output will\n+update the branch(es) in the revision range to point at the new\n+commits (in other words, this mimics a rebase operation).\n+\n+--advance <branch>::\n+\tStarting point at which to create the new commits; must be a\n+\tbranch name.\n++\n+When `--advance` is specified, the update-ref command(s) in the output\n+will update the branch passed as an argument to `--advance` to point at\n+the new commits (in other words, this mimics a cherry-pick operation).\n \n <revision-range>::\n \tRange of commits to replay; see \"Specifying Ranges\" in\n@@ -47,7 +55,10 @@ input to `git update-ref --stdin`.  It is basically of the form:\n \tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n \tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n \n-where the number of refs updated depend on the arguments passed.\n+where the number of refs updated depend on the arguments passed.  When\n+using `--advance`, the number of refs updated is always one, but for\n+`--onto`, it can be one or more (rebasing multiple branches\n+simultaneously is supported).\n \n EXIT STATUS\n -----------\n@@ -67,6 +78,18 @@ $ git replay --onto target origin/main..mybranch\n update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n ------------\n \n+To cherry-pick the commits from mybranch onto target:\n+\n+------------\n+$ git replay --advance target origin/main..mybranch\n+update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH}\n+------------\n+\n+Note that the first two examples replay the exact same commits and on\n+top of the exact same new base, they only differ in that the first\n+provides instructions to make mybranch point at the new commits and\n+the second provides instructions to make target point at them.\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 7b3c0f2bc4..c146f38f58 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,6 +14,7 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"strmap.h\"\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -80,6 +81,146 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+struct ref_info {\n+\tstruct commit *onto;\n+\tstruct strset positive_refs;\n+\tstruct strset negative_refs;\n+\tint positive_refexprs;\n+\tint negative_refexprs;\n+};\n+\n+static void get_ref_information(struct rev_cmdline_info *cmd_info,\n+\t\t\t\tstruct ref_info *ref_info)\n+{\n+\tint i;\n+\n+\tref_info->onto = NULL;\n+\tstrset_init(&ref_info->positive_refs);\n+\tstrset_init(&ref_info->negative_refs);\n+\tref_info->positive_refexprs = 0;\n+\tref_info->negative_refexprs = 0;\n+\n+\t/*\n+\t * When the user specifies e.g.\n+\t *   git replay origin/main..mybranch\n+\t *   git replay ^origin/next mybranch1 mybranch2\n+\t * we want to be able to determine where to replay the commits.  In\n+\t * these examples, the branches are probably based on an old version\n+\t * of either origin/main or origin/next, so we want to replay on the\n+\t * newest version of that branch.  In contrast we would want to error\n+\t * out if they ran\n+\t *   git replay ^origin/master ^origin/next mybranch\n+\t *   git replay mybranch~2..mybranch\n+\t * the first of those because there's no unique base to choose, and\n+\t * the second because they'd likely just be replaying commits on top\n+\t * of the same commit and not making any difference.\n+\t */\n+\tfor (i = 0; i < cmd_info->nr; i++) {\n+\t\tstruct rev_cmdline_entry *e = cmd_info->rev + i;\n+\t\tstruct object_id oid;\n+\t\tconst char *refexpr = e->name;\n+\t\tchar *fullname = NULL;\n+\t\tint can_uniquely_dwim = 1;\n+\n+\t\tif (*refexpr == '^')\n+\t\t\trefexpr++;\n+\t\tif (repo_dwim_ref(the_repository, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)\n+\t\t\tcan_uniquely_dwim = 0;\n+\n+\t\tif (e->flags & BOTTOM) {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->negative_refs, fullname);\n+\t\t\tif (!ref_info->negative_refexprs)\n+\t\t\t\tref_info->onto = lookup_commit_reference_gently(the_repository,\n+\t\t\t\t\t\t\t\t\t\t&e->item->oid, 1);\n+\t\t\tref_info->negative_refexprs++;\n+\t\t} else {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->positive_refs, fullname);\n+\t\t\tref_info->positive_refexprs++;\n+\t\t}\n+\n+\t\tfree(fullname);\n+\t}\n+}\n+\n+static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n+\t\t\t\t  const char *onto_name,\n+\t\t\t\t  const char **advance_name,\n+\t\t\t\t  struct commit **onto,\n+\t\t\t\t  struct strset **update_refs)\n+{\n+\tstruct ref_info rinfo;\n+\n+\tget_ref_information(cmd_info, &rinfo);\n+\tif (!rinfo.positive_refexprs)\n+\t\tdie(_(\"need some commits to replay\"));\n+\tif (onto_name && *advance_name)\n+\t\tdie(_(\"--onto and --advance are incompatible\"));\n+\telse if (onto_name) {\n+\t\t*onto = peel_committish(onto_name);\n+\t\tif (rinfo.positive_refexprs <\n+\t\t    strset_get_size(&rinfo.positive_refs))\n+\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t} else if (*advance_name) {\n+\t\tstruct object_id oid;\n+\t\tchar *fullname = NULL;\n+\n+\t\t*onto = peel_committish(*advance_name);\n+\t\tif (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name),\n+\t\t\t     &oid, &fullname, 0) == 1) {\n+\t\t\t*advance_name = fullname;\n+\t\t} else {\n+\t\t\tdie(_(\"argument to --advance must be a reference\"));\n+\t\t}\n+\t\tif (rinfo.positive_refexprs > 1)\n+\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\t} else {\n+\t\tint positive_refs_complete = (\n+\t\t\trinfo.positive_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.positive_refs));\n+\t\tint negative_refs_complete = (\n+\t\t\trinfo.negative_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.negative_refs));\n+\t\t/*\n+\t\t * We need either positive_refs_complete or\n+\t\t * negative_refs_complete, but not both.\n+\t\t */\n+\t\tif (rinfo.negative_refexprs > 0 &&\n+\t\t    positive_refs_complete == negative_refs_complete)\n+\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\tif (negative_refs_complete) {\n+\t\t\tstruct hashmap_iter iter;\n+\t\t\tstruct strmap_entry *entry;\n+\n+\t\t\tif (rinfo.negative_refexprs == 0)\n+\t\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t\t\telse if (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\t\telse if (rinfo.positive_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\n+\t\t\t/* Only one entry, but we have to loop to get it */\n+\t\t\tstrset_for_each_entry(&rinfo.negative_refs,\n+\t\t\t\t\t      &iter, entry) {\n+\t\t\t\t*advance_name = entry->key;\n+\t\t\t}\n+\t\t} else { /* positive_refs_complete */\n+\t\t\tif (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine correct base for --onto\"));\n+\t\t\tif (rinfo.negative_refexprs == 1)\n+\t\t\t\t*onto = rinfo.onto;\n+\t\t}\n+\t}\n+\tif (!*advance_name) {\n+\t\t*update_refs = xcalloc(1, sizeof(**update_refs));\n+\t\t**update_refs = rinfo.positive_refs;\n+\t\tmemset(&rinfo.positive_refs, 0, sizeof(**update_refs));\n+\t}\n+\tstrset_clear(&rinfo.negative_refs);\n+\tstrset_clear(&rinfo.positive_refs);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n \t\t\t\t\t  struct commit *last_commit,\n \t\t\t\t\t  struct merge_options *merge_opt,\n@@ -112,20 +253,26 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n-\tstruct commit *onto;\n+\tconst char *advance_name = NULL;\n+\tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL;\n+\n \tstruct rev_info revs;\n+\tstruct commit *last_commit = NULL;\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n+\tstruct strset *update_refs = NULL;\n \tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n+\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"advance\", &advance_name,\n+\t\t\t   N_(\"branch\"),\n+\t\t\t   N_(\"make replay advance given branch\")),\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n@@ -149,13 +296,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tusage_with_options(replay_usage, replay_options);\n \t\t}\n \n-\tif (!onto_name) {\n-\t\terror(_(\"option --onto is mandatory\"));\n+\tif (!onto_name && !advance_name) {\n+\t\terror(_(\"option --onto or --advance is mandatory\"));\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tonto = peel_committish(onto_name);\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \targc = setup_revisions(argc, argv, &revs, NULL);\n@@ -181,6 +326,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n+\tdetermine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n+\t\t\t      &onto, &update_refs);\n+\n+\tif (!onto) /* FIXME: Should handle replaying down to root commit */\n+\t\tdie(\"Replaying down to root commit is not supported yet!\");\n+\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -189,6 +340,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n+\n \tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n@@ -203,12 +355,15 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Update any necessary branches */\n+\t\tif (advance_name)\n+\t\t\tcontinue;\n \t\tdecoration = get_name_decoration(&commit->object);\n \t\tif (!decoration)\n \t\t\tcontinue;\n-\n \t\twhile (decoration) {\n-\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n+\t\t\t    strset_contains(update_refs, decoration->name)) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\n@@ -218,11 +373,23 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\t/* In --advance mode, advance the target ref */\n+\tif (result.clean == 1 && advance_name) {\n+\t\tprintf(\"update %s %s %s\\n\",\n+\t\t       advance_name,\n+\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t       oid_to_hex(&onto->object.oid));\n+\t}\n+\n \t/* Cleanup */\n \tmerge_finalize(&merge_opt, &result);\n \tret = result.clean;\n \n cleanup:\n+\tif (update_refs) {\n+\t\tstrset_clear(update_refs);\n+\t\tfree(update_refs);\n+\t}\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex de6e40950e..bca405c431 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -96,4 +96,30 @@ test_expect_success 'using replay on bare repo with disallowed pathspec' '\n \ttest_must_fail git -C bare replay --onto main topic1..topic2 -- A.t\n '\n \n+test_expect_success 'using replay to perform basic cherry-pick' '\n+\t# The differences between this test and previous ones are:\n+\t#   --advance vs --onto\n+\t# 2nd field of result is refs/heads/main vs. refs/heads/topic2\n+\t# 4th field of result is hash for main instead of hash for topic2\n+\n+\tgit replay --advance main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/main \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse main >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n+\tgit -C bare replay --advance main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n test_done\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476904","messageId":"20230509175347.1714141-15-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 14/15] replay: add --contained to rebase contained branches","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:46Z","receivedAt":"2023-05-09T17:55:16Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's add a `--contained` option that can be used along with\n`--onto` to rebase all the branches contained in the <revision-range>\nargument.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt | 16 +++++++++++++++-\n builtin/replay.c             | 12 ++++++++++--\n t/t3650-replay-basics.sh     | 29 +++++++++++++++++++++++++++++\n 3 files changed, 54 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 439b2f92e7..3e06ab2f5e 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n SYNOPSIS\n --------\n [verse]\n-'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n+'git replay' [--contained] (--onto <newbase> | --advance <branch>) <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -90,6 +90,20 @@ top of the exact same new base, they only differ in that the first\n provides instructions to make mybranch point at the new commits and\n the second provides instructions to make target point at them.\n \n+What if you have a stack of branches, one depending upon another, and\n+you'd really like to rebase the whole set?\n+\n+------------\n+$ git replay --contained --onto origin/main origin/main..tipbranch\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}\n+------------\n+\n+In contrast, trying to do this with rebase would require 3 separate\n+rebases, eacho of which involves a different <ONTO> and <UPSTREAM> and\n+forces you to first check out each branch in turn.\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex c146f38f58..4d24eb95d8 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -256,6 +256,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tconst char *advance_name = NULL;\n \tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n+\tint contained = 0;\n \n \tstruct rev_info revs;\n \tstruct commit *last_commit = NULL;\n@@ -266,7 +267,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n+\t\tN_(\"git replay [--contained] (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -276,6 +277,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_BOOL(0, \"contained\", &contained,\n+\t\t\t N_(\"advance all branches contained in revision-range\")),\n \t\tOPT_END()\n \t};\n \n@@ -301,6 +304,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n+\tif (advance_name && contained)\n+\t\tdie(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t    \"--advance\", \"--contained\");\n+\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \targc = setup_revisions(argc, argv, &revs, NULL);\n@@ -363,7 +370,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tcontinue;\n \t\twhile (decoration) {\n \t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n-\t\t\t    strset_contains(update_refs, decoration->name)) {\n+\t\t\t    (contained || strset_contains(update_refs,\n+\t\t\t\t\t\t\t  decoration->name))) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex bca405c431..3fb4167e69 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -122,4 +122,33 @@ test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to also rebase a contained branch' '\n+\tgit replay --contained --onto main main..topic3 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines H G F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic1 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic1 >>expect &&\n+\tprintf \"update refs/heads/topic3 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic3 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to also rebase a contained branch' '\n+\tgit -C bare replay --contained --onto main main..topic3 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n test_done\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476906","messageId":"20230509175347.1714141-16-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v2 15/15] replay: stop assuming replayed branches do not diverge","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-09T17:53:47Z","receivedAt":"2023-05-09T17:55:19Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command is able to replay multiple branches but when some of\nthem are based on other replayed branches, their commit should be\nreplayed onto already replayed commits.\n\nFor this purpose, let's store the replayed commit and its original\ncommit in a key value store, so that we can easily find and reused a\nreplayed commit instead of the original one.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 44 ++++++++++++++++++++++++++--------\n t/t3650-replay-basics.sh | 52 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 86 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 4d24eb95d8..7699d28f93 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -221,20 +221,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n \tstrset_clear(&rinfo.positive_refs);\n }\n \n+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,\n+\t\t\t\t    struct commit *commit,\n+\t\t\t\t    struct commit *fallback)\n+{\n+\tkhint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);\n+\tif (pos == kh_end(replayed_commits))\n+\t\treturn fallback;\n+\treturn kh_value(replayed_commits, pos);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n-\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  kh_oid_map_t *replayed_commits,\n+\t\t\t\t\t  struct commit *onto,\n \t\t\t\t\t  struct merge_options *merge_opt,\n \t\t\t\t\t  struct merge_result *result)\n {\n-\tstruct commit *base;\n+\tstruct commit *base, *replayed_base;\n \tstruct tree *pickme_tree, *base_tree;\n \n \tbase = pickme->parents->item;\n+\treplayed_base = mapped_commit(replayed_commits, base, onto);\n \n+\tresult->tree = repo_get_commit_tree(the_repository, replayed_base);\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n-\tmerge_opt->branch1 = short_commit_name(last_commit);\n+\tmerge_opt->branch1 = short_commit_name(replayed_base);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -248,7 +261,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tmerge_opt->ancestor = NULL;\n \tif (!result->clean)\n \t\treturn NULL;\n-\treturn create_commit(result->tree, pickme, last_commit);\n+\treturn create_commit(result->tree, pickme, replayed_base);\n }\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n@@ -264,6 +277,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n \tstruct strset *update_refs = NULL;\n+\tkh_oid_map_t *replayed_commits;\n \tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n@@ -347,21 +361,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\n-\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n+\treplayed_commits = kh_init_oid_map();\n \twhile ((commit = get_revision(&revs))) {\n \t\tconst struct name_decoration *decoration;\n+\t\tkhint_t pos;\n+\t\tint hr;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tlast_commit = pick_regular_commit(commit, replayed_commits, onto,\n+\t\t\t\t\t\t  &merge_opt, &result);\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Record commit -> last_commit mapping */\n+\t\tpos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);\n+\t\tif (hr == 0)\n+\t\t\tBUG(\"Duplicate rewritten commit: %s\\n\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tkh_value(replayed_commits, pos) = last_commit;\n+\n \t\t/* Update any necessary branches */\n \t\tif (advance_name)\n \t\t\tcontinue;\n@@ -391,13 +414,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \t/* Cleanup */\n \tmerge_finalize(&merge_opt, &result);\n-\tret = result.clean;\n-\n-cleanup:\n+\tkh_destroy_oid_map(replayed_commits);\n \tif (update_refs) {\n \t\tstrset_clear(update_refs);\n \t\tfree(update_refs);\n \t}\n+\tret = result.clean;\n+\n+cleanup:\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 3fb4167e69..5aafad448f 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -151,4 +151,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to rebase multiple divergent branches' '\n+\tgit replay --onto main ^topic1 topic2 topic4 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines J I M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\tprintf \"update refs/heads/topic4 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic4 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '\n+\tgit -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&\n+\n+\ttest_line_count = 4 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\t>expect &&\n+\tfor i in 2 1 3 4\n+\tdo\n+\t\tprintf \"update refs/heads/topic$i \" >>expect &&\n+\t\tprintf \"%s \" $(grep topic$i result | cut -f 3 -d \" \") >>expect &&\n+\t\tgit -C bare rev-parse topic$i >>expect || return 1\n+\tdone &&\n+\n+\ttest_cmp expect result &&\n+\n+\ttest_write_lines F C M L B A >expect1 &&\n+\ttest_write_lines E D C M L B A >expect2 &&\n+\ttest_write_lines H G F C M L B A >expect3 &&\n+\ttest_write_lines J I M L B A >expect4 &&\n+\n+\tfor i in 1 2 3 4\n+\tdo\n+\t\tgit -C bare log --format=%s $(grep topic$i result | cut -f 3 -d \" \") >actual &&\n+\t\ttest_cmp expect$i actual || return 1\n+\tdone\n+'\n+\n test_done\n-- \n2.40.1.491.gdff9a222ea\n\n"},{"id":"476933","messageId":"xmqq8rdxuokg.fsf@gitster.g","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"Re: [PATCH v2 00/15] Introduce new `git replay` command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-05-09T22:28:47Z","receivedAt":"2023-05-09T22:29:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> # Intro\n>\n> `git replay` has initially been developed entirely by Elijah Newren\n> mostly last year (2022) at:\n>\n> https://github.com/newren/git/commits/replay\n>\n> I took over a few months ago to polish and upstream it as GitLab is\n> interested in replacing libgit2, and for that purpose needs a command\n> to do server side (so without using a worktree) rebases, cherry-picks\n> and reverts.\n\nIt would help to include the following in future updated rounds, as\nElijah is shuffling the header files around agressively and these\npatches do not build when merged into 'seen'.  When these two new\nincludes are added, the series would compile both standalone\n(i.e. applied on top of 'master') and in 'seen' (i.e. with shuffled\nheaders).\n\nThanks.\n\n builtin/replay.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git c/builtin/replay.c w/builtin/replay.c\nindex 7699d28f93..62dd1f7fff 100644\n--- c/builtin/replay.c\n+++ w/builtin/replay.c\n@@ -15,6 +15,8 @@\n #include \"refs.h\"\n #include \"revision.h\"\n #include \"strmap.h\"\n+#include <oidset.h>\n+#include <tree.h>\n \n static const char *short_commit_name(struct commit *commit)\n {\n"},{"id":"476952","messageId":"CAP8UFD0pTrWZzvVCx5X+KbFuLHCH7YVryXCQ+P1GZ3BGfZfX=g@mail.gmail.com","threadId":"59560","inReplyTo":"xmqq8rdxuokg.fsf@gitster.g","subject":"Re: [PATCH v2 00/15] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-05-10T07:33:59Z","receivedAt":"2023-05-10T07:35:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, May 10, 2023 at 12:28 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> It would help to include the following in future updated rounds, as\n> Elijah is shuffling the header files around agressively and these\n> patches do not build when merged into 'seen'.  When these two new\n> includes are added, the series would compile both standalone\n> (i.e. applied on top of 'master') and in 'seen' (i.e. with shuffled\n> headers).\n\nI have just added the two new includes in my current version.\n\nThanks!\n"},{"id":"477345","messageId":"CABPp-BEcrS6DskLtYa25MNu8L_hn03bgGqpUPsfkq_k2TFV9bg@mail.gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-13-christian.couder@gmail.com","subject":"Re: [PATCH v2 12/15] replay: disallow revision specific options and pathspecs","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-05-16T04:25:27Z","receivedAt":"2023-05-16T04:25:54Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, May 9, 2023 at 10:54 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> A previous commit changed `git replay` to make it accept standard\n> revision ranges using the setup_revisions() function. While this is good\n\ns/is good/is a good/\n\n> thing to make this command more standard and more flexible, it has the\n> downside of enabling many revision related options accepted and eaten by\n> setup_revisions().\n[...]\n> @@ -135,6 +135,20 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n>         argc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n>                              PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n>\n> +       /*\n> +        * TODO: For now, we reject any unknown or invalid option,\n> +        * including revision related ones, like --not,\n> +        * --first-parent, etc that would be allowed and eaten by\n> +        * setup_revisions() below. In the future we should definitely\n> +        * accept those that make sense and add related tests and doc\n> +        * though.\n> +        */\n> +       for (i = 0; i < argc; i++)\n> +               if (argv[i][0] == '-') {\n> +                       error(_(\"invalid option: %s\"), argv[i]);\n> +                       usage_with_options(replay_usage, replay_options);\n> +               }\n> +\n>         if (!onto_name) {\n>                 error(_(\"option --onto is mandatory\"));\n>                 usage_with_options(replay_usage, replay_options);\n> @@ -150,6 +164,17 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n>                 goto cleanup;\n>         }\n>\n> +       /*\n> +        * TODO: For now, we reject any pathspec. (They are allowed\n> +        * and eaten by setup_revisions() above.) In the future we\n> +        * should definitely accept them and add related tests and doc\n> +        * though.\n> +        */\n\nI like the previous TODO, but I think this one can just be left out.\nWhile it might be possible to do something sensible with pathspecs at\nleast for linear history, it's not clear to me how it could generally\nwork with non-linear history.  And since replay has handling\nnon-linear history as a primary (eventual) goal, the \"definitely\" here\nseems incorrect to me.\n\n[...]\n\nRest looks good.\n"},{"id":"477346","messageId":"CABPp-BHmbU=8dr_H1393eLtbN4zkaaWsKhMHzjABd7nfVG1Grg@mail.gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-15-christian.couder@gmail.com","subject":"Re: [PATCH v2 14/15] replay: add --contained to rebase contained branches","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-05-16T04:26:06Z","receivedAt":"2023-05-16T04:26:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, May 9, 2023 at 10:54 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> From: Elijah Newren <newren@gmail.com>\n>\n> Let's add a `--contained` option that can be used along with\n> `--onto` to rebase all the branches contained in the <revision-range>\n> argument.\n>\n> Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n>  Documentation/git-replay.txt | 16 +++++++++++++++-\n>  builtin/replay.c             | 12 ++++++++++--\n>  t/t3650-replay-basics.sh     | 29 +++++++++++++++++++++++++++++\n>  3 files changed, 54 insertions(+), 3 deletions(-)\n>\n> diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\n> index 439b2f92e7..3e06ab2f5e 100644\n> --- a/Documentation/git-replay.txt\n> +++ b/Documentation/git-replay.txt\n> @@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n> +'git replay' [--contained] (--onto <newbase> | --advance <branch>) <revision-range>...\n\nShould this be\n\n  'git replay' ([--contained] --onto <newbase> | --advance <branch>)\n<revision-range>...\n\n?\n\n>\n>  DESCRIPTION\n>  -----------\n> @@ -90,6 +90,20 @@ top of the exact same new base, they only differ in that the first\n>  provides instructions to make mybranch point at the new commits and\n>  the second provides instructions to make target point at them.\n>\n> +What if you have a stack of branches, one depending upon another, and\n> +you'd really like to rebase the whole set?\n> +\n> +------------\n> +$ git replay --contained --onto origin/main origin/main..tipbranch\n> +update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n> +update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n> +update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}\n> +------------\n> +\n> +In contrast, trying to do this with rebase would require 3 separate\n> +rebases, eacho of which involves a different <ONTO> and <UPSTREAM> and\n\ns/eacho/each/\n\n> +forces you to first check out each branch in turn.\n\nThis paragraph isn't true anymore with rebase's --update-refs, right?\n\n> +\n>  When calling `git replay`, one does not need to specify a range of\n>  commits to replay using the syntax `A..B`; any range expression will\n>  do:\n> diff --git a/builtin/replay.c b/builtin/replay.c\n> index c146f38f58..4d24eb95d8 100644\n> --- a/builtin/replay.c\n> +++ b/builtin/replay.c\n> @@ -256,6 +256,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n>         const char *advance_name = NULL;\n>         struct commit *onto = NULL;\n>         const char *onto_name = NULL;\n> +       int contained = 0;\n>\n>         struct rev_info revs;\n>         struct commit *last_commit = NULL;\n> @@ -266,7 +267,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n>         int ret = 0, i;\n>\n>         const char * const replay_usage[] = {\n> -               N_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n> +               N_(\"git replay [--contained] (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n\nPossibly need to update this here too.\n\n>                 NULL\n>         };\n>         struct option replay_options[] = {\n> @@ -276,6 +277,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n>                 OPT_STRING(0, \"onto\", &onto_name,\n>                            N_(\"revision\"),\n>                            N_(\"replay onto given commit\")),\n> +               OPT_BOOL(0, \"contained\", &contained,\n> +                        N_(\"advance all branches contained in revision-range\")),\n>                 OPT_END()\n>         };\n>\n> @@ -301,6 +304,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n>                 usage_with_options(replay_usage, replay_options);\n>         }\n>\n> +       if (advance_name && contained)\n> +               die(_(\"options '%s' and '%s' cannot be used together\"),\n> +                   \"--advance\", \"--contained\");\n\nBut the code does check that these are incompatible.  Good.\n\n\n> +\n>         repo_init_revisions(the_repository, &revs, prefix);\n>\n>         argc = setup_revisions(argc, argv, &revs, NULL);\n> @@ -363,7 +370,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n>                         continue;\n>                 while (decoration) {\n>                         if (decoration->type == DECORATION_REF_LOCAL &&\n> -                           strset_contains(update_refs, decoration->name)) {\n> +                           (contained || strset_contains(update_refs,\n> +                                                         decoration->name))) {\n>                                 printf(\"update %s %s %s\\n\",\n>                                        decoration->name,\n>                                        oid_to_hex(&last_commit->object.oid),\n> diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\n> index bca405c431..3fb4167e69 100755\n> --- a/t/t3650-replay-basics.sh\n> +++ b/t/t3650-replay-basics.sh\n> @@ -122,4 +122,33 @@ test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n>         test_cmp expect result-bare\n>  '\n>\n> +test_expect_success 'using replay to also rebase a contained branch' '\n> +       git replay --contained --onto main main..topic3 >result &&\n> +\n> +       test_line_count = 2 result &&\n> +       cut -f 3 -d \" \" result >new-branch-tips &&\n> +\n> +       git log --format=%s $(head -n 1 new-branch-tips) >actual &&\n> +       test_write_lines F C M L B A >expect &&\n> +       test_cmp expect actual &&\n> +\n> +       git log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n> +       test_write_lines H G F C M L B A >expect &&\n> +       test_cmp expect actual &&\n> +\n> +       printf \"update refs/heads/topic1 \" >expect &&\n> +       printf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n> +       git rev-parse topic1 >>expect &&\n> +       printf \"update refs/heads/topic3 \" >>expect &&\n> +       printf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n> +       git rev-parse topic3 >>expect &&\n> +\n> +       test_cmp expect result\n> +'\n> +\n> +test_expect_success 'using replay on bare repo to also rebase a contained branch' '\n> +       git -C bare replay --contained --onto main main..topic3 >result-bare &&\n> +       test_cmp expect result-bare\n> +'\n> +\n>  test_done\n> --\n> 2.40.1.491.gdff9a222ea\n>\n"},{"id":"477347","messageId":"CABPp-BEoBimdtHQkU=fanOH8LvUsZZxwG0Og7Y8q4bX3io5D-w@mail.gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-16-christian.couder@gmail.com","subject":"Re: [PATCH v2 15/15] replay: stop assuming replayed branches do not diverge","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-05-16T04:26:51Z","receivedAt":"2023-05-16T04:27:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, May 9, 2023 at 10:54 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> From: Elijah Newren <newren@gmail.com>\n>\n> The replay command is able to replay multiple branches but when some of\n> them are based on other replayed branches, their commit should be\n> replayed onto already replayed commits.\n>\n> For this purpose, let's store the replayed commit and its original\n> commit in a key value store, so that we can easily find and reused a\n\ns/resued/reuse/\n\n\n> replayed commit instead of the original one.\n>\n> Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n"},{"id":"477348","messageId":"CABPp-BFz688Hz3FMjO9TwxicnS6S76f2zbsBMMK5JtYju5iBbA@mail.gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"Re: [PATCH v2 00/15] Introduce new `git replay` command","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-05-16T04:42:47Z","receivedAt":"2023-05-16T04:43:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, May 9, 2023 at 10:54 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n[...]\n> Now the changes between v1 and v2 are:\n>\n> * The patch series has been rebased on top of current master, so that\n>   conflicts with master are resolved. This required replacing calls to\n>   functions like get_oid() with repo_get_oid(), as well as changing\n>   some `#include \"*.h\"` to use different header files. This\n>   unfortunately makes the range-diff between v1 and v2 a bit more\n>   difficult to read than we would like.\n>\n> * Patch 1/15 is a new patch. It has been introduced to rework t6429,\n>   so that this test script doesn't break when conflict output is\n>   removed later in the series. This allowed us to remove the old 9/14\n>   patch (\"replay: very coarse worktree updating\").\n>\n> * The old 9/14 patch (\"replay: very coarse worktree updating\") was\n>   removed. This is to make sure that the new command can be used on\n>   bare repos. This enables us to focus first on making sure the new\n>   command a good plumbing command that works well on bare repos.\n>\n> * The old 12/14 patch (\"replay: introduce guess_new_base()\") was\n>   removed. This is also to focus first on making sure the new command\n>   is a good plumbing command.\n>\n> * The old 13/14 patch (\"replay: add different modes\") has been split\n>   into 2 patches: patch 13/15 (\"replay: add --advance or 'cherry-pick'\n>   mode\") and patch 14/15 (\"replay: add --contained to rebase contained\n>   branches\").\n>\n> * Patch 12/15 (\"replay: disallow revision specific options and\n>   pathspecs\") is a new patch that disallow using revision specific\n>   options that would be allowed and eaten by setup_revisions(). Even\n>   if it would be very nice to have some of these options in the\n>   future, not all these options make sense for the new command, and it\n>   might require improving the setup_revisions() mechanism (as well as\n>   adding tests and doc) to cleanly allow those we want, but that is\n>   outside the scope of this first patch series.\n>\n>   While at it, this patch also disallow pathspecs too.\n>\n>   It would be nice to have them in the future,\n\nOther than these 10 words and the code comment they represent, all the\nchanges made in v2 look good to me...\n\n>   but for now it is not necessary and\n>   would require work (at least for tests and doc) that can be done\n>   later.\n>\n> * Patches 11/15 to 15/15 include a number of new tests to check that\n>   the command works well on bare repos, and in case of 11/15 to check\n>   that exit code in case of conflict is 1.\n>\n> * Patch 11/15, in which the doc for the `git replay` command is\n>   introduced, now says that the new command is experimental.\n\n..especially this one.  Thanks for including this!\n\n> * Note that a segfault that was found by Derrick Stolee in the v1 is\n>   fixed by 000c4ceca7 (merge-ort: fix calling merge_finalize() with no\n>   intermediate merge, 2023-04-22) merged to master by 3927312601\n>   (Merge branch 'en/ort-finalize-after-0-merges-fix', 2023-05-02).\n"},{"id":"477930","messageId":"20230602102533.876905-2-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 01/15] t6429: remove switching aspects of fast-rebase","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:19Z","receivedAt":"2023-06-02T10:28:15Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAt the time t6429 was written, merge-ort was still under development,\ndid not have quite as many tests, and certainly was not widely deployed.\nSince t6429 was exercising some codepaths just a little differently, we\nthought having them also test the \"merge_switch_to_result()\" bits of\nmerge-ort was useful even though they weren't intrinsic to the real\npoint of these tests.\n\nHowever, the value provided by doing extra testing of the\n\"merge_switch_to_result()\" bits has decreased a bit over time, and it's\nactively making it harder to refactor `test-tool fast-rebase` into `git\nreplay`, which we are going to do in following commits.  Dispense with\nthese bits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/helper/test-fast-rebase.c              | 9 +--------\n t/t6429-merge-sequence-rename-caching.sh | 9 +++++++--\n 2 files changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c\nindex d1d63feaa9..403bdf8e75 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/t/helper/test-fast-rebase.c\n@@ -193,7 +193,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n-\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\tmerge_finalize(&merge_opt, &result);\n \n \tif (result.clean < 0)\n \t\texit(128);\n@@ -212,9 +212,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n-\n-\t\tprime_cache_tree(the_repository, the_repository->index,\n-\t\t\t\t result.tree);\n \t} else {\n \t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n@@ -227,10 +224,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t\tdie(\"Failed to update %s\", argv[4]);\n \t\t}\n \t}\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-\n \tret = (result.clean == 0);\n cleanup:\n \tstrbuf_release(&reflog_msg);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex d02fa16614..75d3fd2dba 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -72,6 +72,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \t\tgit switch upstream &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n@@ -200,6 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -277,6 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -356,8 +359,6 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n \t\t#git cherry-pick upstream..topic &&\n \n-\t\tgrep CONFLICT..rename/rename output &&\n-\n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n \t)\n@@ -456,6 +457,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -522,6 +524,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -624,6 +627,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -682,6 +686,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477931","messageId":"20230602102533.876905-1-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230509175347.1714141-1-christian.couder@gmail.com","subject":"[PATCH v3 00/15] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:18Z","receivedAt":"2023-06-02T10:28:18Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"# Intro\n\n`git replay` has initially been developed entirely by Elijah Newren\nmostly last year (2022) at:\n\nhttps://github.com/newren/git/commits/replay\n\nI took over this year to polish and upstream it as GitLab is\ninterested in replacing libgit2, and for that purpose needs a command\nto do server side (so without using a worktree) rebases, cherry-picks\nand reverts.\n\nI reduced the number of commits and features in this first patch\nseries, compared to what Elijah already developed. Especially I\nstopped short of replaying merge commits and replaying\ninteractively. These and other features might be upstreamed in the\nfuture after this patch series has graduated.\n\nThe focus in this series is to make it a good plumbing command that\ncan already be used server side and that replaces the \"fast-rebase\"\ntest-tool command. So things to make it easier to use on the command\nline, and more advanced features (like replaying merges) are left out.\n\n# Content of this cover letter\n\nThe \"Quick Overview\" and \"Reasons for diverging from cherry-pick &\nrebase\" sections just below are describing the purpose of the new\ncommand in the big scheme of things. They are taken from Elijah's\ndesign notes\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt)\nand describe what we want this command to become and the reasons for\nthat, not what the command is after only this patch series. Also these\ndesign notes were written at least one year ago, so parts of those 2\nsections are not true anymore. I have added Phillip Wood's or Felipe\nContreras' notes (thanks to them) where that's the case, but some now\nflawed parts may have missed.\n\nAfter these two sections, starting with the \"Important limitations\"\nsection, you will find sections describing what is actually in this\npatch series.\n\nMore interesting material is available in Elijah's design notes like\nan \"Intro via examples\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L37-L132),\na discussion about \"Preserving topology, replaying merges\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L264-L341)\nand a \"Current status\" section describing Elijah's work\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L344-L392)\nbefore I started working on upstreaming it.\n\nI have not included this material here though, as the documentation\nadded by this patch series for the `git replay` command already\nincludes an \"EXAMPLES\" section, and other sections of Elijah's design\nnotes might not be interesting for now. Also this cover letter is\nalready pretty long.  But reviewers can refer to the links above if\nthey think it can help.\n\n# Quick Overview (from Elijah's design notes)\n\n`git replay`, at a basic level, can perhaps be thought of as a\n\"default-to-dry-run rebase\" -- meaning no updates to the working tree,\nor to the index, or to any references.  However, it differs from\nrebase in that it:\n\n  * Works for branches that aren't checked out\n\n  * Works in a bare repository\n\n  * Can replay multiple branches simultaneously (with or without common\n    history in the range being replayed)\n\n  * Preserves relative topology by default (merges are replayed too in\n    Elijah's original work, not in this series)\n\n  * Focuses on performance\n\n  * Has several altered defaults as a result of the above\n\nI sometimes think of `git replay` as \"fast-replay\", a patch-based\nanalogue to the snapshot-based fast-export & fast-import tools.\n\n# Reasons for diverging from cherry-pick & rebase (from Elijah's\n  design notes)\n\nThere are multiple reasons to diverge from the defaults in cherry-pick and\nrebase.\n\n* Server side needs\n\n  * Both cherry-pick and rebase, via the sequencer, are heavily tied\n    to updating the working tree, index, some refs, and a lot of\n    control files with every commit replayed, and invoke a mess of\n    hooks[1] that might be hard to avoid for backward compatibility\n    reasons (at least, that's been brought up a few times on the\n    list).\n\n  * cherry-pick and rebase both fork various subprocesses\n    unnecessarily, but somewhat intrinsically in part to ensure the\n    same hooks are called that old scripted implementations would have\n    called.\n\n    Note: since 356ee4659bb (sequencer: try to commit without forking\n    'git commit', 2017-11-24) cherry-pick and rebase do not fork\n    subprocesses other than hooks for the cases covered by this patch\n    series (i.e. they do not fork \"git commit\" for simple picks).\n\n  * \"Dry run\" behavior, where there are no updates to worktree, index,\n    or even refs might be important.\n\n  * Should not assume users only want to operate on HEAD (see next\n    section)\n\n* Decapitate HEAD-centric assumptions\n\n  * cherry-pick forces commits to be played on top of HEAD;\n    inflexible.\n\n  * rebase assumes the range of commits to be replayed is\n    upstream..HEAD by default, though it allows one to replay\n    upstream..otherbranch -- but it still forcibly and needlessly\n    checks out 'otherbranch' before starting to replay things.\n\n    Note: since 767a9c417eb (rebase -i: stop checking out the tip of\n    the branch to rebase, 2020-01-24) it's not true that rebase\n    forcibly and needlessly checks out 'otherbranch'.\n\n  * Assuming HEAD is involved severely limits replaying multiple\n    (possibly divergent) branches.\n\n    Note: since 89fc0b53fdb (rebase: update refs from 'update-ref'\n    commands, 2022-07-19) the sequencer can update multiple\n    branches. The issue with divergent branch is with command line\n    arguments and the todo list generation rather than the\n    capabilities of the sequencer.\n\n  * Once you stop assuming HEAD has a certain meaning, there's not\n    much reason to have two separate commands anymore (except for the\n    funny extra not-necessarily-compatible options both have gained\n    over time).\n\n  * (Micro issue: Assuming HEAD is involved also makes it harder for\n    new users to learn what rebase means and does; it makes command\n    lines hard to parse.  Not sure I want to harp on this too much, as\n    I have a suspicion I might be creating a tool for experts with\n    complicated use cases, but it's a minor quibble.)\n\n* Performance\n\n  * jj is slaughtering us on rebase speed[2].  I would like us to become\n    competitive.  (I dropped a few comments in the link at [2] about why\n    git is currently so bad.)\n\n  * From [3], there was a simple 4-patch series in linux.git that took\n    53 seconds to rebase.  Switching to ort dropped it to 16 seconds.\n    While that sounds great, only 11 *milliseconds* were needed to do\n    the actual merges.  That means almost *all* the time (>99%) was\n    overhead!  Big offenders:\n\n    * --reapply-cherry-picks should be the default\n\n    * can_fast_forward() should be ripped out, and perhaps other extraneous\n      revision walks\n\n      Note: d42c9ffa0f (rebase: factor out branch_base calculation,\n      2022-10-17) might already deal with that (according to Felipe\n      Contreras).\n\n    * avoid updating working tree, index, refs, reflogs, and control\n      structures except when needed (e.g. hitting a conflict, or operation\n      finished)\n\n  * Other performance ideas (mostly for future work, not in this\n    series)\n\n    * single-file control structures instead of directory of files\n      (when doing interactive things which is in Elijah's original\n      work, but not in this series)\n\n    * avoid forking subprocesses unless explicitly requested (e.g.\n      --exec, --strategy, --run-hooks).  For example, definitely do not\n      invoke `git commit` or `git merge`.\n\n    * Sanitize hooks:\n\n      * dispense with all per-commit hooks for sure (pre-commit,\n        post-commit, post-checkout).\n\n      * pre-rebase also seems to assume exactly 1 ref is written, and\n        invoking it repeatedly would be stupid.  Plus, it's specific\n        to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n        rebase probably broke the pre-rebase assumptions already...)\n\n      * post-rewrite hook might make sense, but fast-import got\n        exempted, and I think of replay like a patch-based analogue\n        to the snapshot-based fast-import.\n\n    * When not running server side, resolve conflicts in a sparse-cone\n      sparse-index worktree to reduce number of files written to a\n      working tree.  (See below as well.)\n\n    * [High risk of possible premature optimization] Avoid large\n      numbers of newly created loose objects, when replaying large\n      numbers of commits.  Two possibilities: (1) Consider using\n      tmp-objdir and pack objects from the tmp-objdir at end of\n      exercise, (2) Lift code from git-fast-import to immediately\n      stuff new objects into a pack?\n\n* Multiple branches and non-checked out branches\n\n  * The ability to operate on non-checked out branches also implies\n    that we should generally be able to replay when in a dirty working\n    tree (exception being when we expect to update HEAD and any of the\n    dirty files is one that needs to be updated by the replay).\n\n  * Also, if we are operating locally on a non-checked out branch and\n    hit a conflict, we should have a way to resolve the conflict\n    without messing with the user's work on their current\n    branch. (This is not is this patch series though.)\n\n    * Idea: new worktree with sparse cone + sparse index checkout,\n      containing only files in the root directory, and whatever is\n      necessary to get the conflicts\n\n    * Companion to above idea: control structures should be written to\n      $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n      replay sessions, and so we know which worktrees are associated\n      with which replay operations.\n\n  - [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/\n  - [2] https://github.com/martinvonz/jj/discussions/49\n  - [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/\n\n# Important limitations\n\n* The code exits with code 1 if there are any conflict. No\n  resumability. No nice output. No interactivity. No special exit code\n  depending on the reason.\n\n* When a commit becomes empty as it is replayed, it is still replayed\n  as an empty commit, instead of being dropped.\n\n* No replaying merges, nor root commits. Only regular commits.\n\n* Signed commits are not properly handled. It's not clear what to do\n  to such commits when replaying on the server side.\n\n* Notes associated with replayed commits are not updated nor\n  duplicated. (Thanks to Phillip Wood for noticing.)\n\n# Commit overview\n\n* 1/15 t6429: remove switching aspects of fast-rebase\n\n    New preparatory commit to make it easier to later replace the\n    fast-rebase test-tool by `git replay` without breaking existing\n    tests.\n\n* 2/15 replay: introduce new builtin\n\n     This creates a minimal `git replay` command by moving the code\n     from the `fast-rebase` test helper from `t/helper/` into\n     `builtin/` and doing some renames and a few other needed changes.\n     (In v3 some `#include ...` were changed to deal with upstream\n     changes in this area as suggested by Junio.)\n\n* - 3/15 replay: start using parse_options API\n  - 4/15 replay: die() instead of failing assert()\n  - 5/15 replay: introduce pick_regular_commit()\n  - 6/15 replay: don't simplify history\n  - 7/15 replay: add an important FIXME comment about gpg signing\n  - 8/15 replay: remove progress and info output\n  - 9/15 replay: remove HEAD related sanity check\n\n     These slowly change the command to make it behave more like\n     regular commands and to start cleaning up its output.\n\n* 10/15 replay: make it a minimal server side command\n\n     After the cleaning up in previous commits, it's now time to\n     radically change the way it works by stopping it to do ref\n     updates, to update the index and worktree, to consider HEAD as\n     special. Instead just make it output commands that should be\n     passed to `git update-ref --stdin`.\n\n* 11/15 replay: use standard revision ranges\n\n     Start addind new interesting features and also documentation and\n     tests, as the interface of the command is cristalizing into its\n     final form.\n\n* 12/15 replay: disallow revision specific options and pathspecs\n\n     For now disallow revision specific options and pathspecs that are\n     allowed and eaten by setup_revisions(), as it's not clear if all\n     of these extra features are really needed, and anyway they would\n     require tests and doc. So we leave them for future improvements.\n     (In v3 a typo was fixed in the commit message and a code comment\n     has been improved as suggested by Elijah.)\n\n* - 13/15 replay: add --advance or 'cherry-pick' mode\n  - 14/15 replay: add --contained to rebase contained branches\n\n    Add new option and features to the command. (In v3, in commit\n    14/15, the synopsys of the command was improved and a sentence\n    about git rebase in its doc was removed as suggested by Elijah.)\n\n* 15/15 replay: stop assuming replayed branches do not diverge\n\n      This adds another interesting feature, as well as related\n      documentation and tests. (In v3 a typo in the commit message was\n      fixed as suggested by Elijah.)\n\n# Notes about `fast-rebase`, tests and documentation\n\nThe `fast-rebase` test-tool helper was developed by Elijah to\nexperiment with a rebasing tool that would be developed from scratch\nbased on his merge-ort work, could be used to test that merge-ort\nwork, and would not have the speed and interface limitations of `git\nrebase` or `git cherry-pick`.\n\nThis `fast-rebase` helper was used before this series in:\n\nt6429-merge-sequence-rename-caching.sh\n\nSo when `git replay` is created from `fast-rebase` in patch 2/15, the\nt6429 test script is also converted to use `git replay`. This ensures\nthat `git replay` doesn't break too badly during the first 10 patches\nin this patch series.\n\nTests and documentation are introduced specifically for `git replay`\nonly in 11/15 and later patches as it doesn't make much sense to\ndocument and test behavior that we know is going to change soon. So\nit's only when the command is crystalizing towards its final form that\nwe start documenting and testing it.\n\n# Possibly controversial issues \n\n* bare or not bare: this series works towards a plumbing command with\n  the end goal of it being usable and used first on bare repos,\n  contrary to existing commands like `git rebase` and `git\n  cherry-pick`. The tests check that the command works on both bare\n  and non-bare repo though.\n\n* exit status: a successful, non-conflicted replay exits with code\n  0. When the replay has conflicts, the exit status is 1. If the\n  replay is not able to complete (or start) due to some kind of error,\n  the exit status is something other than 0 or 1. There are a few\n  tests checking that. It has been suggested in an internal review\n  that conflicts might want to get a more specific error code as an\n  error code of 1 might be quite easy to return by accident. It\n  doesn't seem to me from their docs (which might want to be improved,\n  I didn't look at the code) that other commands like `git merge` and\n  `git rebase` exit with a special error code in case of conflict.\n\n* make worktree and index changes optional: commit 10/15 stops\n  updating the index and worktree, but it might be better especially\n  for cli users to make that optional. The issue is that this would\n  make the command more complex while we are developing a number of\n  important features so that the command can be used on bare repos. It\n  seems that this should rather be done in an iterative improvement\n  after the important features have landed.\n\n* when and where to add tests and docs: although t6429 has tests that\n  are changed to use the new command instead of the fast-rebase\n  test-tool command as soon as the former is introduced, there is no\n  specific test script and no doc for the new command until commit\n  11/15 when standard revision ranges are used. This is done to avoid\n  churn in tests and docs while the final form of the command hasn't\n  crystalized enough. Adding tests and doc at this point makes this\n  commit quite big and possibly more difficult to review than if they\n  were in separate commits though. On the other hand when tests and\n  docs are added in specific commits some reviewers say it would be\n  better to introduce them when the related changes are made.\n\n* --advance and --contained: these two advanced options might not\n  belong to this first series and could perhaps be added in a followup\n  series in separate commits. On the other hand the code for\n  --contained seems involved with the code of --advance and it's nice\n  to see soon that git replay can indeed do cherry-picking and rebase\n  many refs at once, and this way fullfil these parts of its promise.\n\n* replaying diverging branches: 15/15 the last patch in the series,\n  which allow replaying diverging branches, can be seen as a\n  fundamental fix or alternatively as adding an interesting\n  feature. So it's debatable if it should be in its own patch along\n  with its own tests as in this series, or if it should be merged into\n  a previous patch and which one.\n\n* only 2 patches: this patch series can be seen from a high level\n  point of view as 1) introducing the new `git replay` command, and 2)\n  using `git replay` to replace, and get rid of, the fast-rebase\n  test-tool command. The fact that not much of the original\n  fast-rebase code and interface is left would agree with that point\n  of view. On the other hand, fast-rebase can also be seen as a first\n  iteration towards `git replay`. So it can also make sense to see how\n  `git replay` evolved from it.\n\n# Changes between v2 and v3\n\nThanks to Elijah and Junio for their suggestions on the previous\nversion! The very few and minor changes compared to v2 are:\n\n* The patch series was rebased onto master at v2.41.0.\n\n* Patch 2/15 contains `#include ...` changes suggested by Junio to\n  deal with upstream changes in this area that were merged while I was\n  working on v2 and that conflicted with this series.\n\n* Patch 12/15 has a typo fix in its commit message and an improved\n  code comment both suggested by ELijah. The code comment is about\n  rejecting pathspec. I removed the 'TODO' mention in it and worded it\n  like we \"might accept\" pathspec in the future, instead of saying\n  that \"we should definitely accept them\".\n\n* In patch 14/15, as suggested by Elijah, the command synopsys now\n  contains:\n\n  ([--contained] --onto <newbase> | --advance <branch>)\n\n  instead of:\n\n  [--contained] (--onto <newbase> | --advance <branch>)\n\n  Also in the doc about --contained the sentence talking about git\n  rebase has been removed.\n\n* Patch 15/15 has a typo fix in its commit message.\n\n# Changes between v1 and v2\n\nThanks to all the reviewers on the mailing list or during a Review\nClub! Especially, thanks to Elijah, Dscho, Phillip Wood, Derrick\nStolee, Patrick Steinhardt, Junio, Felipe Contreras, Calvin Wan and\nall the Review Club participants.\n\nThe Review Club notes (see those on 2023-04-18) are available on:\n\nhttps://docs.google.com/document/d/14L8BAumGTpsXpjDY8VzZ4rRtpAjuGrFSRqn3stCuS_w/\n\nNow the changes between v1 and v2 are:\n\n* The patch series has been rebased on top of current master, so that\n  conflicts with master are resolved. This required replacing calls to\n  functions like get_oid() with repo_get_oid(), as well as changing\n  some `#include \"*.h\"` to use different header files. This\n  unfortunately makes the range-diff between v1 and v2 a bit more\n  difficult to read than we would like.\n\n* Patch 1/15 is a new patch. It has been introduced to rework t6429,\n  so that this test script doesn't break when conflict output is\n  removed later in the series. This allowed us to remove the old 9/14\n  patch (\"replay: very coarse worktree updating\").\n\n* The old 9/14 patch (\"replay: very coarse worktree updating\") was\n  removed. This is to make sure that the new command can be used on\n  bare repos. This enables us to focus first on making sure the new\n  command a good plumbing command that works well on bare repos.\n\n* The old 12/14 patch (\"replay: introduce guess_new_base()\") was\n  removed. This is also to focus first on making sure the new command\n  is a good plumbing command.\n\n* The old 13/14 patch (\"replay: add different modes\") has been split\n  into 2 patches: patch 13/15 (\"replay: add --advance or 'cherry-pick'\n  mode\") and patch 14/15 (\"replay: add --contained to rebase contained\n  branches\").\n\n* Patch 12/15 (\"replay: disallow revision specific options and\n  pathspecs\") is a new patch that disallow using revision specific\n  options that would be allowed and eaten by setup_revisions(). Even\n  if it would be very nice to have some of these options in the\n  future, not all these options make sense for the new command, and it\n  might require improving the setup_revisions() mechanism (as well as\n  adding tests and doc) to cleanly allow those we want, but that is\n  outside the scope of this first patch series.\n\n  While at it, this patch also disallow pathspecs too. It would be\n  nice to have them in the future, but for now it is not necessary and\n  would require work (at least for tests and doc) that can be done\n  later.\n\n* Patches 11/15 to 15/15 include a number of new tests to check that\n  the command works well on bare repos, and in case of 11/15 to check\n  that exit code in case of conflict is 1.\n\n* Patch 11/15, in which the doc for the `git replay` command is\n  introduced, now says that the new command is experimental.\n\n* Note that a segfault that was found by Derrick Stolee in the v1 is\n  fixed by 000c4ceca7 (merge-ort: fix calling merge_finalize() with no\n  intermediate merge, 2023-04-22) merged to master by 3927312601\n  (Merge branch 'en/ort-finalize-after-0-merges-fix', 2023-05-02).\n\n# Range-diff between v2 and v3\n\n 1:  e1bef69d8f =  1:  51fa1c7aea t6429: remove switching aspects of fast-rebase\n 2:  73396c4fc9 !  2:  19f8cf1b2e replay: introduce new builtin\n    @@ t/helper/test-fast-rebase.c => builtin/replay.c\n      #include \"cache-tree.h\"\n      #include \"commit.h\"\n      #include \"environment.h\"\n    +@@\n    + #include \"sequencer.h\"\n    + #include \"setup.h\"\n    + #include \"strvec.h\"\n    +-#include \"tree.h\"\n    ++#include <oidset.h>\n    ++#include <tree.h>\n    + \n    + static const char *short_commit_name(struct commit *commit)\n    + {\n     @@ builtin/replay.c: static struct commit *create_commit(struct tree *tree,\n        return (struct commit *)obj;\n      }\n 3:  1a1b214146 =  3:  295e876db6 replay: start using parse_options API\n 4:  f63d6f0c9f =  4:  6ece7d3751 replay: die() instead of failing assert()\n 5:  e7de455815 =  5:  9b4bc659fb replay: introduce pick_regular_commit()\n 6:  103c18b3eb =  6:  9ab68d50ab replay: don't simplify history\n 7:  5be7eec24f =  7:  37e93faafa replay: add an important FIXME comment about gpg signing\n 8:  bb5d3a0564 =  8:  03036781ed replay: remove progress and info output\n 9:  a75b02ddae =  9:  4ea289952e replay: remove HEAD related sanity check\n10:  cff278a0b9 ! 10:  fba98eda07 replay: make it a minimal server side command\n    @@ builtin/replay.c\n     -#include \"sequencer.h\"\n     -#include \"setup.h\"\n      #include \"strvec.h\"\n    --#include \"tree.h\"\n    - \n    - static const char *short_commit_name(struct commit *commit)\n    - {\n    + #include <oidset.h>\n    + #include <tree.h>\n     @@ builtin/replay.c: static struct commit *pick_regular_commit(struct commit *pickme,\n        pickme_tree = repo_get_commit_tree(the_repository, pickme);\n        base_tree = repo_get_commit_tree(the_repository, base);\n11:  926b332d44 ! 11:  03f9f20f6e replay: use standard revision ranges\n    @@ builtin/replay.c\n      #include \"refs.h\"\n      #include \"revision.h\"\n     -#include \"strvec.h\"\n    + #include <oidset.h>\n    + #include <tree.h>\n      \n    - static const char *short_commit_name(struct commit *commit)\n    - {\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n        struct commit *onto;\n        const char *onto_name = NULL;\n12:  f7eb2230a7 ! 12:  e651250ac7 replay: disallow revision specific options and pathspecs\n    @@ Commit message\n         replay: disallow revision specific options and pathspecs\n     \n         A previous commit changed `git replay` to make it accept standard\n    -    revision ranges using the setup_revisions() function. While this is good\n    -    thing to make this command more standard and more flexible, it has the\n    -    downside of enabling many revision related options accepted and eaten by\n    -    setup_revisions().\n    +    revision ranges using the setup_revisions() function. While this is a\n    +    good thing to make this command more standard and more flexible, it has\n    +    the downside of enabling many revision related options accepted and eaten\n    +    by setup_revisions().\n     \n         Some of these options might make sense, but others, like those\n         generating non-contiguous history, might not. Anyway those we might want\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        }\n      \n     +  /*\n    -+   * TODO: For now, we reject any pathspec. (They are allowed\n    -+   * and eaten by setup_revisions() above.) In the future we\n    -+   * should definitely accept them and add related tests and doc\n    -+   * though.\n    ++   * Reject any pathspec. (They are allowed and eaten by\n    ++   * setup_revisions() above.) In the future we might accept\n    ++   * them, after adding related tests and doc though.\n     +   */\n     +  if (revs.prune_data.nr) {\n     +          error(_(\"invalid pathspec: %s\"), revs.prune_data.items[0].match);\n13:  bc301cf93a ! 13:  56e5416dad replay: add --advance or 'cherry-pick' mode\n    @@ builtin/replay.c\n      #include \"refs.h\"\n      #include \"revision.h\"\n     +#include \"strmap.h\"\n    + #include <oidset.h>\n    + #include <tree.h>\n      \n    - static const char *short_commit_name(struct commit *commit)\n    - {\n     @@ builtin/replay.c: static struct commit *create_commit(struct tree *tree,\n        return (struct commit *)obj;\n      }\n14:  3eb582b509 ! 14:  2cc17dfdc7 replay: add --contained to rebase contained branches\n    @@ Documentation/git-replay.txt: git-replay - Replay commits on a different base, w\n      --------\n      [verse]\n     -'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n    -+'git replay' [--contained] (--onto <newbase> | --advance <branch>) <revision-range>...\n    ++'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\n      \n      DESCRIPTION\n      -----------\n    @@ Documentation/git-replay.txt: top of the exact same new base, they only differ i\n     +update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n     +update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}\n     +------------\n    -+\n    -+In contrast, trying to do this with rebase would require 3 separate\n    -+rebases, eacho of which involves a different <ONTO> and <UPSTREAM> and\n    -+forces you to first check out each branch in turn.\n     +\n      When calling `git replay`, one does not need to specify a range of\n      commits to replay using the syntax `A..B`; any range expression will\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n      \n        const char * const replay_usage[] = {\n     -          N_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n    -+          N_(\"git replay [--contained] (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n    ++          N_(\"git replay ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\"),\n                NULL\n        };\n        struct option replay_options[] = {\n15:  bdd8a3b3e1 ! 15:  a6d88fc8f0 replay: stop assuming replayed branches do not diverge\n    @@ Commit message\n         replayed onto already replayed commits.\n     \n         For this purpose, let's store the replayed commit and its original\n    -    commit in a key value store, so that we can easily find and reused a\n    +    commit in a key value store, so that we can easily find and reuse a\n         replayed commit instead of the original one.\n     \n         Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n\n\nChristian Couder (1):\n  replay: disallow revision specific options and pathspecs\n\nElijah Newren (14):\n  t6429: remove switching aspects of fast-rebase\n  replay: introduce new builtin\n  replay: start using parse_options API\n  replay: die() instead of failing assert()\n  replay: introduce pick_regular_commit()\n  replay: don't simplify history\n  replay: add an important FIXME comment about gpg signing\n  replay: remove progress and info output\n  replay: remove HEAD related sanity check\n  replay: make it a minimal server side command\n  replay: use standard revision ranges\n  replay: add --advance or 'cherry-pick' mode\n  replay: add --contained to rebase contained branches\n  replay: stop assuming replayed branches do not diverge\n\n .gitignore                               |   1 +\n Documentation/git-replay.txt             | 121 +++++++\n Makefile                                 |   2 +-\n builtin.h                                |   1 +\n builtin/replay.c                         | 432 +++++++++++++++++++++++\n command-list.txt                         |   1 +\n git.c                                    |   1 +\n t/helper/test-fast-rebase.c              | 240 -------------\n t/helper/test-tool.c                     |   1 -\n t/helper/test-tool.h                     |   1 -\n t/t3650-replay-basics.sh                 | 206 +++++++++++\n t/t6429-merge-sequence-rename-caching.sh |  45 +--\n 12 files changed, 789 insertions(+), 263 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100644 builtin/replay.c\n delete mode 100644 t/helper/test-fast-rebase.c\n create mode 100755 t/t3650-replay-basics.sh\n\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477932","messageId":"20230602102533.876905-3-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 02/15] replay: introduce new builtin","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:20Z","receivedAt":"2023-06-02T10:28:20Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor now, this is just a rename from `t/helper/test-fast-rebase.c` into\n`builtin/replay.c` with minimal changes to make it build appropriately.\n\nSubsequent commits will flesh out its capabilities and make it a more\nstandard regular builtin.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n .gitignore                                    |  1 +\n Makefile                                      |  2 +-\n builtin.h                                     |  1 +\n .../test-fast-rebase.c => builtin/replay.c    | 28 ++++++-------------\n command-list.txt                              |  1 +\n git.c                                         |  1 +\n t/helper/test-tool.c                          |  1 -\n t/helper/test-tool.h                          |  1 -\n t/t6429-merge-sequence-rename-caching.sh      | 27 ++++++------------\n 9 files changed, 22 insertions(+), 41 deletions(-)\n rename t/helper/test-fast-rebase.c => builtin/replay.c (89%)\n\ndiff --git a/.gitignore b/.gitignore\nindex e875c59054..b5f025a296 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -135,6 +135,7 @@\n /git-remote-ext\n /git-repack\n /git-replace\n+/git-replay\n /git-request-pull\n /git-rerere\n /git-reset\ndiff --git a/Makefile b/Makefile\nindex e440728c24..a6ab78840f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -799,7 +799,6 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o\n TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n TEST_BUILTINS_OBJS += test-env-helper.o\n TEST_BUILTINS_OBJS += test-example-decorate.o\n-TEST_BUILTINS_OBJS += test-fast-rebase.o\n TEST_BUILTINS_OBJS += test-fsmonitor-client.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n TEST_BUILTINS_OBJS += test-genzeros.o\n@@ -1286,6 +1285,7 @@ BUILTIN_OBJS += builtin/remote-fd.o\n BUILTIN_OBJS += builtin/remote.o\n BUILTIN_OBJS += builtin/repack.o\n BUILTIN_OBJS += builtin/replace.o\n+BUILTIN_OBJS += builtin/replay.o\n BUILTIN_OBJS += builtin/rerere.o\n BUILTIN_OBJS += builtin/reset.o\n BUILTIN_OBJS += builtin/rev-list.o\ndiff --git a/builtin.h b/builtin.h\nindex cb0db67681..c3f0b56915 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -214,6 +214,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix);\n int cmd_remote_ext(int argc, const char **argv, const char *prefix);\n int cmd_remote_fd(int argc, const char **argv, const char *prefix);\n int cmd_repack(int argc, const char **argv, const char *prefix);\n+int cmd_replay(int argc, const char **argv, const char *prefix);\n int cmd_rerere(int argc, const char **argv, const char *prefix);\n int cmd_reset(int argc, const char **argv, const char *prefix);\n int cmd_restore(int argc, const char **argv, const char *prefix);\ndiff --git a/t/helper/test-fast-rebase.c b/builtin/replay.c\nsimilarity index 89%\nrename from t/helper/test-fast-rebase.c\nrename to builtin/replay.c\nindex 403bdf8e75..252af62d12 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/builtin/replay.c\n@@ -1,18 +1,11 @@\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+ * \"git replay\" builtin command\n  */\n \n #define USE_THE_INDEX_VARIABLE\n-#include \"test-tool.h\"\n-#include \"cache.h\"\n+#include \"git-compat-util.h\"\n+\n+#include \"builtin.h\"\n #include \"cache-tree.h\"\n #include \"commit.h\"\n #include \"environment.h\"\n@@ -26,7 +19,8 @@\n #include \"sequencer.h\"\n #include \"setup.h\"\n #include \"strvec.h\"\n-#include \"tree.h\"\n+#include <oidset.h>\n+#include <tree.h>\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -93,7 +87,7 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n-int cmd__fast_rebase(int argc, const char **argv)\n+int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n@@ -109,12 +103,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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@@ -135,7 +123,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n \n-\trepo_init_revisions(the_repository, &revs, NULL);\n+\trepo_init_revisions(the_repository, &revs, prefix);\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\ndiff --git a/command-list.txt b/command-list.txt\nindex 54b2a50f5f..d74836ab21 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -160,6 +160,7 @@ git-reflog                              ancillarymanipulators           complete\n git-remote                              ancillarymanipulators           complete\n git-repack                              ancillarymanipulators           complete\n git-replace                             ancillarymanipulators           complete\n+git-replay                              mainporcelain           history\n git-request-pull                        foreignscminterface             complete\n git-rerere                              ancillaryinterrogators\n git-reset                               mainporcelain           history\ndiff --git a/git.c b/git.c\nindex 2f42da20f4..cf95c61813 100644\n--- a/git.c\n+++ b/git.c\n@@ -593,6 +593,7 @@ static struct cmd_struct commands[] = {\n \t{ \"remote-fd\", cmd_remote_fd, NO_PARSEOPT },\n \t{ \"repack\", cmd_repack, RUN_SETUP },\n \t{ \"replace\", cmd_replace, RUN_SETUP },\n+\t{ \"replay\", cmd_replay, RUN_SETUP },\n \t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t{ \"reset\", cmd_reset, RUN_SETUP },\n \t{ \"restore\", cmd_restore, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex abe8a785eb..9ca1586de7 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -30,7 +30,6 @@ static struct test_cmd cmds[] = {\n \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n \t{ \"env-helper\", cmd__env_helper },\n \t{ \"example-decorate\", cmd__example_decorate },\n-\t{ \"fast-rebase\", cmd__fast_rebase },\n \t{ \"fsmonitor-client\", cmd__fsmonitor_client },\n \t{ \"genrandom\", cmd__genrandom },\n \t{ \"genzeros\", cmd__genzeros },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ea2672436c..a03bbfc6b2 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -24,7 +24,6 @@ int cmd__dump_untracked_cache(int argc, const char **argv);\n int cmd__dump_reftable(int argc, const char **argv);\n int cmd__env_helper(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__fsmonitor_client(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n int cmd__genzeros(int argc, const char **argv);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 75d3fd2dba..7670b72008 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,9 +71,8 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -141,8 +140,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -200,9 +198,8 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -278,9 +275,8 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -356,8 +352,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,9 +451,8 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -523,9 +517,8 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -626,9 +619,8 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -685,9 +677,8 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477933","messageId":"20230602102533.876905-5-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 04/15] replay: die() instead of failing assert()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:22Z","receivedAt":"2023-06-02T10:28:23Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIt's not a good idea for regular Git commands to use an assert() to\ncheck for things that could happen but are not supported.\n\nLet's die() with an explanation of the issue instead.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 7eb05048a5..2537adbaea 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -178,7 +178,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\n+\t\tif (!commit->parents)\n+\t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n+\t\tif (commit->parents->next)\n+\t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n+\n \t\tbase = commit->parents->item;\n \n \t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477934","messageId":"20230602102533.876905-6-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 05/15] replay: introduce pick_regular_commit()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:23Z","receivedAt":"2023-06-02T10:28:27Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's refactor the code to handle a regular commit (a commit that is\nneither a root commit nor a merge commit) into a single function instead\nof keeping it inside cmd_replay().\n\nThis is good for separation of concerns, and this will help further work\nin the future to replay merge commits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 54 ++++++++++++++++++++++++++++++------------------\n 1 file changed, 34 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 2537adbaea..319020f5fd 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -88,6 +88,35 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+static struct commit *pick_regular_commit(struct commit *pickme,\n+\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  struct merge_options *merge_opt,\n+\t\t\t\t\t  struct merge_result *result)\n+{\n+\tstruct commit *base;\n+\tstruct tree *pickme_tree, *base_tree;\n+\n+\tbase = pickme->parents->item;\n+\n+\tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n+\tbase_tree = repo_get_commit_tree(the_repository, base);\n+\n+\tmerge_opt->branch2 = short_commit_name(pickme);\n+\tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n+\n+\tmerge_incore_nonrecursive(merge_opt,\n+\t\t\t\t  base_tree,\n+\t\t\t\t  result->tree,\n+\t\t\t\t  pickme_tree,\n+\t\t\t\t  result);\n+\n+\tfree((char*)merge_opt->ancestor);\n+\tmerge_opt->ancestor = NULL;\n+\tif (!result->clean)\n+\t\treturn NULL;\n+\treturn create_commit(result->tree, pickme, last_commit);\n+}\n+\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n@@ -99,7 +128,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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 tree *head_tree;\n \tstruct merge_result result;\n \tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n@@ -174,7 +203,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tresult.tree = head_tree;\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *base;\n+\t\tstruct commit *pick;\n \n \t\tfprintf(stderr, \"Rebasing %s...\\r\",\n \t\t\toid_to_hex(&commit->object.oid));\n@@ -184,26 +213,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tbase = commit->parents->item;\n-\n-\t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-\t\tbase_tree = repo_get_commit_tree(the_repository, 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\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!pick)\n \t\t\tbreak;\n+\t\tlast_commit = pick;\n \t\tlast_picked_commit = commit;\n-\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477935","messageId":"20230602102533.876905-4-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 03/15] replay: start using parse_options API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:21Z","receivedAt":"2023-06-02T10:28:29Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of manually parsing arguments, let's start using the parse_options\nAPI. This way this new builtin will look more standard, and in some\nupcoming commits will more easily be able to handle more command line\noptions.\n\nNote that we plan to later use standard revision ranges instead of\nhardcoded \"<oldbase> <branch>\" arguments. When we will use standard\nrevision ranges, it will be easier to check if there are no spurious\narguments if we keep ARGV[0], so let's call parse_options() with\nPARSE_OPT_KEEP_ARGV0 even if we don't need ARGV[0] right now to avoid\nsome useless code churn.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 44 ++++++++++++++++++++++++++++++++------------\n 1 file changed, 32 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 252af62d12..7eb05048a5 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,6 +14,7 @@\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n #include \"object-name.h\"\n+#include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n #include \"sequencer.h\"\n@@ -90,6 +91,7 @@ static struct commit *create_commit(struct tree *tree,\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n+\tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n \tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n@@ -103,16 +105,32 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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+\tconst char * const replay_usage[] = {\n+\t\tN_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tNULL\n+\t};\n+\tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"onto\", &onto_name,\n+\t\t\t   N_(\"revision\"),\n+\t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n+\n+\tif (!onto_name) {\n+\t\terror(_(\"option --onto is mandatory\"));\n+\t\tusage_with_options(replay_usage, replay_options);\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+\tif (argc != 3) {\n+\t\terror(_(\"bad number of arguments\"));\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n \n-\tonto = peel_committish(argv[2]);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\tonto = peel_committish(onto_name);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \t/* Sanity check */\n \tif (repo_get_oid(the_repository, \"HEAD\", &head))\n@@ -124,6 +142,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tBUG(\"Could not read index\");\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n+\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\n@@ -132,7 +151,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n@@ -195,8 +215,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &last_picked_commit->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -208,8 +228,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &head,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t}\n \tret = (result.clean == 0);\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477936","messageId":"20230602102533.876905-7-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 06/15] replay: don't simplify history","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:24Z","receivedAt":"2023-06-02T10:28:51Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's set the rev walking options we need after calling\nsetup_revisions() instead of before. This makes it clearer which options\nwe need.\n\nAlso we don't want history simplification, as we want to deal with all\nthe commits in the affected range.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 15 ++++++---------\n 1 file changed, 6 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 319020f5fd..d28d468008 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -172,15 +172,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \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-\n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n@@ -188,6 +179,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \n+\t/* requirements/overrides for revs */\n+\trevs.reverse = 1;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\trevs.simplify_history = 0;\n+\n \tstrvec_clear(&rev_walk_args);\n \n \tif (prepare_revision_walk(&revs) < 0) {\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477937","messageId":"20230602102533.876905-8-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 07/15] replay: add an important FIXME comment about gpg signing","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:25Z","receivedAt":"2023-06-02T10:28:55Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want to be able to handle signed commits in some way in the future,\nbut we are not ready to do it now. So for the time being let's just add\na FIXME comment to remind us about it.\n\nThese are different ways we could handle them:\n\n  - in case of a cli user and if there was an interactive mode, we could\n    perhaps ask if the user wants to sign again\n  - we could add an option to just fail if there are signed commits\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex d28d468008..52db8e0941 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -61,7 +61,7 @@ static struct commit *create_commit(struct tree *tree,\n \tstruct object *obj;\n \tstruct commit_list *parents = NULL;\n \tchar *author;\n-\tchar *sign_commit = NULL;\n+\tchar *sign_commit = NULL; /* FIXME: cli users might want to sign again */\n \tstruct commit_extra_header *extra;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *out_enc = get_commit_output_encoding();\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477938","messageId":"20230602102533.876905-9-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 08/15] replay: remove progress and info output","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:26Z","receivedAt":"2023-06-02T10:29:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command will be changed in a follow up commit, so that it\nwill not update refs directly, but instead it will print on stdout a\nlist of commands that can be consumed by `git update-ref --stdin`.\n\nWe don't want this output to be polluted by its current low value\noutput, so let's just remove the latter.\n\nIn the future, when the command gets an option to update refs by\nitself, it will make a lot of sense to display a progress meter, but\nwe are not there yet.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 +------\n 1 file changed, 1 insertion(+), 6 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 52db8e0941..2ad0835c06 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -194,7 +194,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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.show_rename_progress = 0;\n \tmerge_opt.branch1 = \"HEAD\";\n \thead_tree = repo_get_commit_tree(the_repository, onto);\n \tresult.tree = head_tree;\n@@ -202,9 +202,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \twhile ((commit = get_revision(&revs))) {\n \t\tstruct commit *pick;\n \n-\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n-\t\t\toid_to_hex(&commit->object.oid));\n-\n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n@@ -223,7 +220,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\texit(128);\n \n \tif (result.clean) {\n-\t\tfprintf(stderr, \"\\nDone.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n \t\t\t    oid_to_hex(&last_commit->object.oid));\n@@ -237,7 +233,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t} else {\n-\t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477939","messageId":"20230602102533.876905-10-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 09/15] replay: remove HEAD related sanity check","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:27Z","receivedAt":"2023-06-02T10:29:08Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want replay to be a command that can be used on the server side on\nany branch, not just the current one, so we are going to stop updating\nHEAD in a future commit.\n\nA \"sanity check\" that makes sure we are replaying the current branch\ndoesn't make sense anymore. Let's remove it.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 8 +-------\n 1 file changed, 1 insertion(+), 7 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 2ad0835c06..3b86fe140f 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -122,7 +122,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct strvec rev_walk_args = STRVEC_INIT;\n \tstruct rev_info revs;\n@@ -161,11 +160,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\t/* Sanity check */\n-\tif (repo_get_oid(the_repository, \"HEAD\", &head))\n-\t\tdie(_(\"Cannot read HEAD\"));\n-\tassert(oideq(&onto->object.oid, &head));\n-\n \trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n@@ -237,7 +231,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n \t\t\t       &last_commit->object.oid,\n-\t\t\t       &head,\n+\t\t\t       &onto->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n \t\t\terror(_(\"could not update %s\"), argv[2]);\n \t\t\tdie(\"Failed to update %s\", argv[2]);\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477940","messageId":"20230602102533.876905-11-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 10/15] replay: make it a minimal server side command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:28Z","receivedAt":"2023-06-02T10:29:17Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want this command to be a minimal command that just does server side\npicking of commits, displaying the results on stdout for higher level\nscripts to consume.\n\nSo let's simplify it:\n  * remove the worktree and index reading/writing,\n  * remove the ref (and reflog) updating,\n  * remove the assumptions tying us to HEAD, since (a) this is not a\n    rebase and (b) we want to be able to pick commits in a bare repo,\n    i.e. to/from branches that are not checked out and not the main\n    branch,\n  * remove unneeded includes,\n  * handle rebasing multiple branches by printing on stdout the update\n    ref commands that should be performed.\n\nThe output can be piped into `git update-ref --stdin` for the ref\nupdates to happen.\n\nIn the future to make it easier for users to use this command\ndirectly maybe an option can be added to automatically pipe its output\ninto `git update-ref`.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c                         | 78 ++++++++----------------\n t/t6429-merge-sequence-rename-caching.sh | 39 +++++++-----\n 2 files changed, 51 insertions(+), 66 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 3b86fe140f..9385973ffc 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -6,10 +6,7 @@\n #include \"git-compat-util.h\"\n \n #include \"builtin.h\"\n-#include \"cache-tree.h\"\n-#include \"commit.h\"\n #include \"environment.h\"\n-#include \"gettext.h\"\n #include \"hex.h\"\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n@@ -17,8 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"sequencer.h\"\n-#include \"setup.h\"\n #include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n@@ -101,6 +96,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n+\tmerge_opt->branch1 = short_commit_name(last_commit);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -121,15 +117,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct lock_file lock = LOCK_INIT;\n+\tstruct commit *last_commit = NULL;\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 *head_tree;\n \tstruct merge_result result;\n-\tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n@@ -160,10 +153,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index(the_repository) < 0)\n-\t\tBUG(\"Could not read index\");\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n@@ -189,58 +178,45 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\tmerge_opt.branch1 = \"HEAD\";\n-\thead_tree = repo_get_commit_tree(the_repository, onto);\n-\tresult.tree = head_tree;\n+\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *pick;\n+\t\tconst struct name_decoration *decoration;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n-\t\tif (!pick)\n+\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!last_commit)\n \t\t\tbreak;\n-\t\tlast_commit = pick;\n-\t\tlast_picked_commit = commit;\n+\n+\t\tdecoration = get_name_decoration(&commit->object);\n+\t\tif (!decoration)\n+\t\t\tcontinue;\n+\n+\t\twhile (decoration) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\t\tprintf(\"update %s %s %s\\n\",\n+\t\t\t\t       decoration->name,\n+\t\t\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t\t\t       oid_to_hex(&commit->object.oid));\n+\t\t\t}\n+\t\t\tdecoration = decoration->next;\n+\t\t}\n \t}\n \n+\t/* Cleanup */\n \tmerge_finalize(&merge_opt, &result);\n+\tret = result.clean;\n \n-\tif (result.clean < 0)\n-\t\texit(128);\n-\n-\tif (result.clean) {\n-\t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n-\t\t\t    oid_to_hex(&last_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, branch_name.buf,\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &last_picked_commit->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n-\t\t\tdie(_(\"unable to update HEAD\"));\n-\t} else {\n-\t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &onto->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t}\n-\tret = (result.clean == 0);\n cleanup:\n-\tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n-\treturn ret;\n+\n+\t/* Return */\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret ? 0 : 1;\n }\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 7670b72008..099aefeffc 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,8 +71,9 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -140,7 +141,9 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -198,8 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -275,8 +279,9 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -451,8 +456,9 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -517,8 +523,9 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -619,8 +626,9 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -677,8 +685,9 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477941","messageId":"20230602102533.876905-12-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 11/15] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:29Z","receivedAt":"2023-06-02T10:29:29Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of the fixed \"<oldbase> <branch>\" arguments, the replay\ncommand now accepts \"<revision-range>...\" arguments in a similar\nway as many other Git commands. This makes its interface more\nstandard and more flexible.\n\nAlso as the interface of the command is now mostly finalized,\nwe can add some documentation as well as testcases to make sure\nthe command will continue to work as designed in the future.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             | 88 ++++++++++++++++++++++++\n builtin/replay.c                         | 21 ++----\n t/t3650-replay-basics.sh                 | 83 ++++++++++++++++++++++\n t/t6429-merge-sequence-rename-caching.sh | 18 ++---\n 4 files changed, 184 insertions(+), 26 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100755 t/t3650-replay-basics.sh\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nnew file mode 100644\nindex 0000000000..394d7b0050\n--- /dev/null\n+++ b/Documentation/git-replay.txt\n@@ -0,0 +1,88 @@\n+git-replay(1)\n+=============\n+\n+NAME\n+----\n+git-replay - Replay commits on a different base, without touching working tree\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git replay' --onto <newbase> <revision-range>...\n+\n+DESCRIPTION\n+-----------\n+\n+Takes a range of commits, and replays them onto a new location.  Does\n+not touch the working tree or index, and does not update any\n+references.  However, the output of this command is meant to be used\n+as input to `git update-ref --stdin`, which would update the relevant\n+branches.\n+\n+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n+\n+OPTIONS\n+-------\n+\n+--onto <newbase>::\n+\tStarting point at which to create the new commits.  May be any\n+\tvalid commit, and not just an existing branch name.\n++\n+The update-ref commands in the output will update the branch(es)\n+in the revision range to point at the new commits (in other\n+words, this mimics a rebase operation).\n+\n+<revision-range>::\n+\tRange of commits to replay; see \"Specifying Ranges\" in\n+\tlinkgit:git-rev-parse.\n+\n+OUTPUT\n+------\n+\n+When there are no conflicts, the output of this command is usable as\n+input to `git update-ref --stdin`.  It is basically of the form:\n+\n+\tupdate refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+\tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+\tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+\n+where the number of refs updated depend on the arguments passed.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted replay, the exit status is 0.  When\n+the replay has conflicts, the exit status is 1.  If the replay is not\n+able to complete (or start) due to some kind of error, the exit status\n+is something other than 0 or 1.\n+\n+EXAMPLES\n+--------\n+\n+To simply rebase mybranch onto target:\n+\n+------------\n+$ git replay --onto target origin/main..mybranch\n+update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n+------------\n+\n+When calling `git replay`, one does not need to specify a range of\n+commits to replay using the syntax `A..B`; any range expression will\n+do:\n+\n+------------\n+$ git replay --onto origin/main ^base branch1 branch2 branch3\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+------------\n+\n+This will simultaneously rebase branch1, branch2, and branch3 -- all\n+commits they have since base, playing them on top of origin/main.\n+These three branches may have commits on top of base that they have in\n+common, but that does not need to be the case.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 9385973ffc..c1bd72c0e5 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,7 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -118,16 +117,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL;\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 merge_result result;\n-\tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -145,20 +142,13 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tif (argc != 3) {\n-\t\terror(_(\"bad number of arguments\"));\n-\t\tusage_with_options(replay_usage, replay_options);\n-\t}\n-\n \tonto = peel_committish(onto_name);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n-\n-\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n-\t\tret = error(_(\"unhandled options\"));\n+\targc = setup_revisions(argc, argv, &revs, NULL);\n+\tif (argc > 1) {\n+\t\tret = error(_(\"unrecognized argument: %s\"), argv[1]);\n \t\tgoto cleanup;\n \t}\n \n@@ -168,8 +158,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n-\tstrvec_clear(&rev_walk_args);\n-\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -212,7 +200,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tret = result.clean;\n \n cleanup:\n-\tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nnew file mode 100755\nindex 0000000000..a1da4f9ef9\n--- /dev/null\n+++ b/t/t3650-replay-basics.sh\n@@ -0,0 +1,83 @@\n+#!/bin/sh\n+\n+test_description='basic git replay tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+GIT_AUTHOR_NAME=author@name\n+GIT_AUTHOR_EMAIL=bogus@email@address\n+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\n+\tgit switch -c topic1 &&\n+\ttest_commit C &&\n+\tgit switch -c topic2 &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit switch topic1 &&\n+\ttest_commit F &&\n+\tgit switch -c topic3 &&\n+\ttest_commit G &&\n+\ttest_commit H &&\n+\tgit switch -c topic4 main &&\n+\ttest_commit I &&\n+\ttest_commit J &&\n+\n+\tgit switch -c next main &&\n+\ttest_commit K &&\n+\tgit merge -m \"Merge topic1\" topic1 &&\n+\tgit merge -m \"Merge topic2\" topic2 &&\n+\tgit merge -m \"Merge topic3\" topic3 &&\n+\t>evil &&\n+\tgit add evil &&\n+\tgit commit --amend &&\n+\tgit merge -m \"Merge topic4\" topic4 &&\n+\n+\tgit switch main &&\n+\ttest_commit L &&\n+\ttest_commit M &&\n+\n+\tgit switch -c conflict B &&\n+\ttest_commit C.conflict C.t conflict\n+'\n+\n+test_expect_success 'setup bare' '\n+\tgit clone --bare . bare\n+'\n+\n+test_expect_success 'using replay to rebase two branches, one on top of other' '\n+\tgit replay --onto main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n+\tgit -C bare replay --onto main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n+test_expect_success 'using replay to rebase with a conflict' '\n+\ttest_expect_code 1 git replay --onto topic1 B..conflict\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase with a conflict' '\n+\ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n+'\n+\n+test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 099aefeffc..0f39ed0d08 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,7 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -201,7 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -279,7 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -357,7 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1..topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,7 +456,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -523,7 +523,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -626,7 +626,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -685,7 +685,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477942","messageId":"20230602102533.876905-13-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 12/15] replay: disallow revision specific options and pathspecs","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:30Z","receivedAt":"2023-06-02T10:29:41Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"A previous commit changed `git replay` to make it accept standard\nrevision ranges using the setup_revisions() function. While this is a\ngood thing to make this command more standard and more flexible, it has\nthe downside of enabling many revision related options accepted and eaten\nby setup_revisions().\n\nSome of these options might make sense, but others, like those\ngenerating non-contiguous history, might not. Anyway those we might want\nto allow should probably be tested and perhaps documented a bit, which\ncould be done in future work.\n\nFor now it is just simpler and safer to just disallow all of them, so\nlet's do that.\n\nOther commands, like `git fast-export`, currently allow all these\nrevision specific options even though some of them might not make sense,\nas these commands also use setup_revisions() but do not check the\noptions that might be passed to this function.\n\nSo a way to fix those commands as well as git replay could be to improve\nor refactor the setup_revisions() mechanism to let callers allow and\ndisallow options in a relevant way for them. Such improvements are\noutside the scope of this work though.\n\nPathspecs, which are also accepted and eaten by setup_revisions(), are\nlikely to result in disconnected history. That could perhaps be useful,\nbut that would need tests and documentation, which can be added in\nfuture work. So, while at it, let's disallow them too.\n\nHelped-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 26 +++++++++++++++++++++++++-\n t/t3650-replay-basics.sh | 16 ++++++++++++++++\n 2 files changed, 41 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex c1bd72c0e5..cffbf34290 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -121,7 +121,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n-\tint ret = 0;\n+\tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n \t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n@@ -137,6 +137,20 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n \t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n \n+\t/*\n+\t * TODO: For now, we reject any unknown or invalid option,\n+\t * including revision related ones, like --not,\n+\t * --first-parent, etc that would be allowed and eaten by\n+\t * setup_revisions() below. In the future we should definitely\n+\t * accept those that make sense and add related tests and doc\n+\t * though.\n+\t */\n+\tfor (i = 0; i < argc; i++)\n+\t\tif (argv[i][0] == '-') {\n+\t\t\terror(_(\"invalid option: %s\"), argv[i]);\n+\t\t\tusage_with_options(replay_usage, replay_options);\n+\t\t}\n+\n \tif (!onto_name) {\n \t\terror(_(\"option --onto is mandatory\"));\n \t\tusage_with_options(replay_usage, replay_options);\n@@ -152,6 +166,16 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \n+\t/*\n+\t * Reject any pathspec. (They are allowed and eaten by\n+\t * setup_revisions() above.) In the future we might accept\n+\t * them, after adding related tests and doc though.\n+\t */\n+\tif (revs.prune_data.nr) {\n+\t\terror(_(\"invalid pathspec: %s\"), revs.prune_data.items[0].match);\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n+\n \t/* requirements/overrides for revs */\n \trevs.reverse = 1;\n \trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex a1da4f9ef9..de6e40950e 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -80,4 +80,20 @@ test_expect_success 'using replay on bare repo to rebase with a conflict' '\n \ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n '\n \n+test_expect_success 'using replay with (for now) disallowed revision specific option --not' '\n+\ttest_must_fail git replay --onto main topic2 --not topic1\n+'\n+\n+test_expect_success 'using replay on bare repo with (for now) disallowed revision specific option --first-parent' '\n+\ttest_must_fail git -C bare replay --onto main --first-parent topic1..topic2\n+'\n+\n+test_expect_success 'using replay with disallowed pathspec' '\n+\ttest_must_fail git replay --onto main topic1..topic2 A.t\n+'\n+\n+test_expect_success 'using replay on bare repo with disallowed pathspec' '\n+\ttest_must_fail git -C bare replay --onto main topic1..topic2 -- A.t\n+'\n+\n test_done\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477943","messageId":"20230602102533.876905-14-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 13/15] replay: add --advance or 'cherry-pick' mode","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:31Z","receivedAt":"2023-06-02T10:31:31Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere is already a 'rebase' mode with `--onto`. Let's add an 'advance' or\n'cherry-pick' mode with `--advance`. This new mode will make the target\nbranch advance as we replay commits onto it.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt |  33 ++++++-\n builtin/replay.c             | 185 +++++++++++++++++++++++++++++++++--\n t/t3650-replay-basics.sh     |  26 +++++\n 3 files changed, 230 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 394d7b0050..439b2f92e7 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n SYNOPSIS\n --------\n [verse]\n-'git replay' --onto <newbase> <revision-range>...\n+'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -29,9 +29,17 @@ OPTIONS\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n +\n-The update-ref commands in the output will update the branch(es)\n-in the revision range to point at the new commits (in other\n-words, this mimics a rebase operation).\n+When `--onto` is specified, the update-ref command(s) in the output will\n+update the branch(es) in the revision range to point at the new\n+commits (in other words, this mimics a rebase operation).\n+\n+--advance <branch>::\n+\tStarting point at which to create the new commits; must be a\n+\tbranch name.\n++\n+When `--advance` is specified, the update-ref command(s) in the output\n+will update the branch passed as an argument to `--advance` to point at\n+the new commits (in other words, this mimics a cherry-pick operation).\n \n <revision-range>::\n \tRange of commits to replay; see \"Specifying Ranges\" in\n@@ -47,7 +55,10 @@ input to `git update-ref --stdin`.  It is basically of the form:\n \tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n \tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n \n-where the number of refs updated depend on the arguments passed.\n+where the number of refs updated depend on the arguments passed.  When\n+using `--advance`, the number of refs updated is always one, but for\n+`--onto`, it can be one or more (rebasing multiple branches\n+simultaneously is supported).\n \n EXIT STATUS\n -----------\n@@ -67,6 +78,18 @@ $ git replay --onto target origin/main..mybranch\n update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n ------------\n \n+To cherry-pick the commits from mybranch onto target:\n+\n+------------\n+$ git replay --advance target origin/main..mybranch\n+update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH}\n+------------\n+\n+Note that the first two examples replay the exact same commits and on\n+top of the exact same new base, they only differ in that the first\n+provides instructions to make mybranch point at the new commits and\n+the second provides instructions to make target point at them.\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex cffbf34290..4b64d15159 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,6 +14,7 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"strmap.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -82,6 +83,146 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+struct ref_info {\n+\tstruct commit *onto;\n+\tstruct strset positive_refs;\n+\tstruct strset negative_refs;\n+\tint positive_refexprs;\n+\tint negative_refexprs;\n+};\n+\n+static void get_ref_information(struct rev_cmdline_info *cmd_info,\n+\t\t\t\tstruct ref_info *ref_info)\n+{\n+\tint i;\n+\n+\tref_info->onto = NULL;\n+\tstrset_init(&ref_info->positive_refs);\n+\tstrset_init(&ref_info->negative_refs);\n+\tref_info->positive_refexprs = 0;\n+\tref_info->negative_refexprs = 0;\n+\n+\t/*\n+\t * When the user specifies e.g.\n+\t *   git replay origin/main..mybranch\n+\t *   git replay ^origin/next mybranch1 mybranch2\n+\t * we want to be able to determine where to replay the commits.  In\n+\t * these examples, the branches are probably based on an old version\n+\t * of either origin/main or origin/next, so we want to replay on the\n+\t * newest version of that branch.  In contrast we would want to error\n+\t * out if they ran\n+\t *   git replay ^origin/master ^origin/next mybranch\n+\t *   git replay mybranch~2..mybranch\n+\t * the first of those because there's no unique base to choose, and\n+\t * the second because they'd likely just be replaying commits on top\n+\t * of the same commit and not making any difference.\n+\t */\n+\tfor (i = 0; i < cmd_info->nr; i++) {\n+\t\tstruct rev_cmdline_entry *e = cmd_info->rev + i;\n+\t\tstruct object_id oid;\n+\t\tconst char *refexpr = e->name;\n+\t\tchar *fullname = NULL;\n+\t\tint can_uniquely_dwim = 1;\n+\n+\t\tif (*refexpr == '^')\n+\t\t\trefexpr++;\n+\t\tif (repo_dwim_ref(the_repository, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)\n+\t\t\tcan_uniquely_dwim = 0;\n+\n+\t\tif (e->flags & BOTTOM) {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->negative_refs, fullname);\n+\t\t\tif (!ref_info->negative_refexprs)\n+\t\t\t\tref_info->onto = lookup_commit_reference_gently(the_repository,\n+\t\t\t\t\t\t\t\t\t\t&e->item->oid, 1);\n+\t\t\tref_info->negative_refexprs++;\n+\t\t} else {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->positive_refs, fullname);\n+\t\t\tref_info->positive_refexprs++;\n+\t\t}\n+\n+\t\tfree(fullname);\n+\t}\n+}\n+\n+static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n+\t\t\t\t  const char *onto_name,\n+\t\t\t\t  const char **advance_name,\n+\t\t\t\t  struct commit **onto,\n+\t\t\t\t  struct strset **update_refs)\n+{\n+\tstruct ref_info rinfo;\n+\n+\tget_ref_information(cmd_info, &rinfo);\n+\tif (!rinfo.positive_refexprs)\n+\t\tdie(_(\"need some commits to replay\"));\n+\tif (onto_name && *advance_name)\n+\t\tdie(_(\"--onto and --advance are incompatible\"));\n+\telse if (onto_name) {\n+\t\t*onto = peel_committish(onto_name);\n+\t\tif (rinfo.positive_refexprs <\n+\t\t    strset_get_size(&rinfo.positive_refs))\n+\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t} else if (*advance_name) {\n+\t\tstruct object_id oid;\n+\t\tchar *fullname = NULL;\n+\n+\t\t*onto = peel_committish(*advance_name);\n+\t\tif (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name),\n+\t\t\t     &oid, &fullname, 0) == 1) {\n+\t\t\t*advance_name = fullname;\n+\t\t} else {\n+\t\t\tdie(_(\"argument to --advance must be a reference\"));\n+\t\t}\n+\t\tif (rinfo.positive_refexprs > 1)\n+\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\t} else {\n+\t\tint positive_refs_complete = (\n+\t\t\trinfo.positive_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.positive_refs));\n+\t\tint negative_refs_complete = (\n+\t\t\trinfo.negative_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.negative_refs));\n+\t\t/*\n+\t\t * We need either positive_refs_complete or\n+\t\t * negative_refs_complete, but not both.\n+\t\t */\n+\t\tif (rinfo.negative_refexprs > 0 &&\n+\t\t    positive_refs_complete == negative_refs_complete)\n+\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\tif (negative_refs_complete) {\n+\t\t\tstruct hashmap_iter iter;\n+\t\t\tstruct strmap_entry *entry;\n+\n+\t\t\tif (rinfo.negative_refexprs == 0)\n+\t\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t\t\telse if (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\t\telse if (rinfo.positive_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\n+\t\t\t/* Only one entry, but we have to loop to get it */\n+\t\t\tstrset_for_each_entry(&rinfo.negative_refs,\n+\t\t\t\t\t      &iter, entry) {\n+\t\t\t\t*advance_name = entry->key;\n+\t\t\t}\n+\t\t} else { /* positive_refs_complete */\n+\t\t\tif (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine correct base for --onto\"));\n+\t\t\tif (rinfo.negative_refexprs == 1)\n+\t\t\t\t*onto = rinfo.onto;\n+\t\t}\n+\t}\n+\tif (!*advance_name) {\n+\t\t*update_refs = xcalloc(1, sizeof(**update_refs));\n+\t\t**update_refs = rinfo.positive_refs;\n+\t\tmemset(&rinfo.positive_refs, 0, sizeof(**update_refs));\n+\t}\n+\tstrset_clear(&rinfo.negative_refs);\n+\tstrset_clear(&rinfo.positive_refs);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n \t\t\t\t\t  struct commit *last_commit,\n \t\t\t\t\t  struct merge_options *merge_opt,\n@@ -114,20 +255,26 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n-\tstruct commit *onto;\n+\tconst char *advance_name = NULL;\n+\tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL;\n+\n \tstruct rev_info revs;\n+\tstruct commit *last_commit = NULL;\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n+\tstruct strset *update_refs = NULL;\n \tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n+\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"advance\", &advance_name,\n+\t\t\t   N_(\"branch\"),\n+\t\t\t   N_(\"make replay advance given branch\")),\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n@@ -151,13 +298,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tusage_with_options(replay_usage, replay_options);\n \t\t}\n \n-\tif (!onto_name) {\n-\t\terror(_(\"option --onto is mandatory\"));\n+\tif (!onto_name && !advance_name) {\n+\t\terror(_(\"option --onto or --advance is mandatory\"));\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tonto = peel_committish(onto_name);\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \targc = setup_revisions(argc, argv, &revs, NULL);\n@@ -182,6 +327,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n+\tdetermine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n+\t\t\t      &onto, &update_refs);\n+\n+\tif (!onto) /* FIXME: Should handle replaying down to root commit */\n+\t\tdie(\"Replaying down to root commit is not supported yet!\");\n+\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -190,6 +341,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n+\n \tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n@@ -204,12 +356,15 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Update any necessary branches */\n+\t\tif (advance_name)\n+\t\t\tcontinue;\n \t\tdecoration = get_name_decoration(&commit->object);\n \t\tif (!decoration)\n \t\t\tcontinue;\n-\n \t\twhile (decoration) {\n-\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n+\t\t\t    strset_contains(update_refs, decoration->name)) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\n@@ -219,11 +374,23 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\t/* In --advance mode, advance the target ref */\n+\tif (result.clean == 1 && advance_name) {\n+\t\tprintf(\"update %s %s %s\\n\",\n+\t\t       advance_name,\n+\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t       oid_to_hex(&onto->object.oid));\n+\t}\n+\n \t/* Cleanup */\n \tmerge_finalize(&merge_opt, &result);\n \tret = result.clean;\n \n cleanup:\n+\tif (update_refs) {\n+\t\tstrset_clear(update_refs);\n+\t\tfree(update_refs);\n+\t}\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex de6e40950e..bca405c431 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -96,4 +96,30 @@ test_expect_success 'using replay on bare repo with disallowed pathspec' '\n \ttest_must_fail git -C bare replay --onto main topic1..topic2 -- A.t\n '\n \n+test_expect_success 'using replay to perform basic cherry-pick' '\n+\t# The differences between this test and previous ones are:\n+\t#   --advance vs --onto\n+\t# 2nd field of result is refs/heads/main vs. refs/heads/topic2\n+\t# 4th field of result is hash for main instead of hash for topic2\n+\n+\tgit replay --advance main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/main \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse main >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n+\tgit -C bare replay --advance main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n test_done\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477944","messageId":"20230602102533.876905-16-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 15/15] replay: stop assuming replayed branches do not diverge","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:33Z","receivedAt":"2023-06-02T10:31:34Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command is able to replay multiple branches but when some of\nthem are based on other replayed branches, their commit should be\nreplayed onto already replayed commits.\n\nFor this purpose, let's store the replayed commit and its original\ncommit in a key value store, so that we can easily find and reuse a\nreplayed commit instead of the original one.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 44 ++++++++++++++++++++++++++--------\n t/t3650-replay-basics.sh | 52 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 86 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 9b182eaa90..a06649fa6e 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -223,20 +223,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n \tstrset_clear(&rinfo.positive_refs);\n }\n \n+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,\n+\t\t\t\t    struct commit *commit,\n+\t\t\t\t    struct commit *fallback)\n+{\n+\tkhint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);\n+\tif (pos == kh_end(replayed_commits))\n+\t\treturn fallback;\n+\treturn kh_value(replayed_commits, pos);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n-\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  kh_oid_map_t *replayed_commits,\n+\t\t\t\t\t  struct commit *onto,\n \t\t\t\t\t  struct merge_options *merge_opt,\n \t\t\t\t\t  struct merge_result *result)\n {\n-\tstruct commit *base;\n+\tstruct commit *base, *replayed_base;\n \tstruct tree *pickme_tree, *base_tree;\n \n \tbase = pickme->parents->item;\n+\treplayed_base = mapped_commit(replayed_commits, base, onto);\n \n+\tresult->tree = repo_get_commit_tree(the_repository, replayed_base);\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n-\tmerge_opt->branch1 = short_commit_name(last_commit);\n+\tmerge_opt->branch1 = short_commit_name(replayed_base);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -250,7 +263,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tmerge_opt->ancestor = NULL;\n \tif (!result->clean)\n \t\treturn NULL;\n-\treturn create_commit(result->tree, pickme, last_commit);\n+\treturn create_commit(result->tree, pickme, replayed_base);\n }\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n@@ -266,6 +279,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n \tstruct strset *update_refs = NULL;\n+\tkh_oid_map_t *replayed_commits;\n \tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n@@ -348,21 +362,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\n-\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n+\treplayed_commits = kh_init_oid_map();\n \twhile ((commit = get_revision(&revs))) {\n \t\tconst struct name_decoration *decoration;\n+\t\tkhint_t pos;\n+\t\tint hr;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tlast_commit = pick_regular_commit(commit, replayed_commits, onto,\n+\t\t\t\t\t\t  &merge_opt, &result);\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Record commit -> last_commit mapping */\n+\t\tpos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);\n+\t\tif (hr == 0)\n+\t\t\tBUG(\"Duplicate rewritten commit: %s\\n\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tkh_value(replayed_commits, pos) = last_commit;\n+\n \t\t/* Update any necessary branches */\n \t\tif (advance_name)\n \t\t\tcontinue;\n@@ -392,13 +415,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \t/* Cleanup */\n \tmerge_finalize(&merge_opt, &result);\n-\tret = result.clean;\n-\n-cleanup:\n+\tkh_destroy_oid_map(replayed_commits);\n \tif (update_refs) {\n \t\tstrset_clear(update_refs);\n \t\tfree(update_refs);\n \t}\n+\tret = result.clean;\n+\n+cleanup:\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 3fb4167e69..5aafad448f 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -151,4 +151,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to rebase multiple divergent branches' '\n+\tgit replay --onto main ^topic1 topic2 topic4 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines J I M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\tprintf \"update refs/heads/topic4 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic4 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '\n+\tgit -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&\n+\n+\ttest_line_count = 4 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\t>expect &&\n+\tfor i in 2 1 3 4\n+\tdo\n+\t\tprintf \"update refs/heads/topic$i \" >>expect &&\n+\t\tprintf \"%s \" $(grep topic$i result | cut -f 3 -d \" \") >>expect &&\n+\t\tgit -C bare rev-parse topic$i >>expect || return 1\n+\tdone &&\n+\n+\ttest_cmp expect result &&\n+\n+\ttest_write_lines F C M L B A >expect1 &&\n+\ttest_write_lines E D C M L B A >expect2 &&\n+\ttest_write_lines H G F C M L B A >expect3 &&\n+\ttest_write_lines J I M L B A >expect4 &&\n+\n+\tfor i in 1 2 3 4\n+\tdo\n+\t\tgit -C bare log --format=%s $(grep topic$i result | cut -f 3 -d \" \") >actual &&\n+\t\ttest_cmp expect$i actual || return 1\n+\tdone\n+'\n+\n test_done\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"477945","messageId":"20230602102533.876905-15-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v3 14/15] replay: add --contained to rebase contained branches","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-02T10:25:32Z","receivedAt":"2023-06-02T10:31:40Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's add a `--contained` option that can be used along with\n`--onto` to rebase all the branches contained in the <revision-range>\nargument.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt | 12 +++++++++++-\n builtin/replay.c             | 12 ++++++++++--\n t/t3650-replay-basics.sh     | 29 +++++++++++++++++++++++++++++\n 3 files changed, 50 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 439b2f92e7..6fcaa44ef2 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n SYNOPSIS\n --------\n [verse]\n-'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n+'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -90,6 +90,16 @@ top of the exact same new base, they only differ in that the first\n provides instructions to make mybranch point at the new commits and\n the second provides instructions to make target point at them.\n \n+What if you have a stack of branches, one depending upon another, and\n+you'd really like to rebase the whole set?\n+\n+------------\n+$ git replay --contained --onto origin/main origin/main..tipbranch\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}\n+------------\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 4b64d15159..9b182eaa90 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -258,6 +258,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tconst char *advance_name = NULL;\n \tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n+\tint contained = 0;\n \n \tstruct rev_info revs;\n \tstruct commit *last_commit = NULL;\n@@ -268,7 +269,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n+\t\tN_(\"git replay ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -278,6 +279,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_BOOL(0, \"contained\", &contained,\n+\t\t\t N_(\"advance all branches contained in revision-range\")),\n \t\tOPT_END()\n \t};\n \n@@ -303,6 +306,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n+\tif (advance_name && contained)\n+\t\tdie(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t    \"--advance\", \"--contained\");\n+\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \targc = setup_revisions(argc, argv, &revs, NULL);\n@@ -364,7 +371,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tcontinue;\n \t\twhile (decoration) {\n \t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n-\t\t\t    strset_contains(update_refs, decoration->name)) {\n+\t\t\t    (contained || strset_contains(update_refs,\n+\t\t\t\t\t\t\t  decoration->name))) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex bca405c431..3fb4167e69 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -122,4 +122,33 @@ test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to also rebase a contained branch' '\n+\tgit replay --contained --onto main main..topic3 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines H G F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic1 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic1 >>expect &&\n+\tprintf \"update refs/heads/topic3 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic3 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to also rebase a contained branch' '\n+\tgit -C bare replay --contained --onto main main..topic3 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n test_done\n-- \n2.41.0.15.ga6d88fc8f0\n\n"},{"id":"478002","messageId":"xmqqr0qt724c.fsf@gitster.g","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"Re: [PATCH v3 00/15] Introduce new `git replay` command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-03T01:42:27Z","receivedAt":"2023-06-03T01:42:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> # Changes between v2 and v3\n>\n> Thanks to Elijah and Junio for their suggestions on the previous\n> version! The very few and minor changes compared to v2 are:\n>\n> * The patch series was rebased onto master at v2.41.0.\n\nIt is good to say this than not to say it, but without \"in order to\n...\" it does not help very much.  I was hoping \"by rebasing, we can\navoid unnecessary conflicts with what happened in the upstream in\nthe meantime, namely, modify-remove conflict of X is now gone\" or\nsomething like that.\n\n"},{"id":"478038","messageId":"CAP8UFD3ATWf5x-xn5F+oFqqj_WHOcqQ_kxjtUDWnfg2U9OT+Hg@mail.gmail.com","threadId":"59560","inReplyTo":"xmqqr0qt724c.fsf@gitster.g","subject":"Re: [PATCH v3 00/15] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-06-05T07:11:42Z","receivedAt":"2023-06-05T07:12:56Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Jun 3, 2023 at 3:42 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > # Changes between v2 and v3\n> >\n> > Thanks to Elijah and Junio for their suggestions on the previous\n> > version! The very few and minor changes compared to v2 are:\n> >\n> > * The patch series was rebased onto master at v2.41.0.\n>\n> It is good to say this than not to say it, but without \"in order to\n> ...\" it does not help very much.  I was hoping \"by rebasing, we can\n> avoid unnecessary conflicts with what happened in the upstream in\n> the meantime, namely, modify-remove conflict of X is now gone\" or\n> something like that.\n\nThe reason was because v2 had conflicts with a patch series from\nElijah that changed \"#include ...\". You asked me to squash a small\npatch that fixed it, and I did squash it into v3, but I thought it was\nsafer to also rebase.\n"},{"id":"478676","messageId":"87h6qzst8u.fsf@iotcl.com","threadId":"59560","inReplyTo":"20230602102533.876905-14-christian.couder@gmail.com","subject":"Re: [PATCH v3 13/15] replay: add --advance or 'cherry-pick' mode","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2023-06-22T10:05:03Z","receivedAt":"2023-06-22T10:09:07Z","isPatch":true,"sender":{"key":"toon@iotcl.com","avatar":"https://avatars.githubusercontent.com/u/121621?v=4"},"body":"\nChristian Couder <christian.couder@gmail.com> writes:\n\n> +\t/*\n> +\t * When the user specifies e.g.\n> +\t *   git replay origin/main..mybranch\n> +\t *   git replay ^origin/next mybranch1 mybranch2\n\nWhen I'm trying these, I'm getting the error:\n    error: option --onto or --advance is mandatory\n\nIn what situation can I omit both --onto and --advance?\n\n> +static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n> +\t\t\t\t  const char *onto_name,\n> +\t\t\t\t  const char **advance_name,\n> +\t\t\t\t  struct commit **onto,\n\nWould it make sense to call this target?\n\n> +\t\t\t\t  struct strset **update_refs)\n> +{\n> +\tstruct ref_info rinfo;\n> +\n> +\tget_ref_information(cmd_info, &rinfo);\n> +\tif (!rinfo.positive_refexprs)\n> +\t\tdie(_(\"need some commits to replay\"));\n> +\tif (onto_name && *advance_name)\n> +\t\tdie(_(\"--onto and --advance are incompatible\"));\n\nDo we actually need to disallow this? I mean from git-replay's point of\nview, there's no technical limitation why can cannot support both modes\nat once. The update-ref commands in the output will update both target\nand source branches, but it's not up to us whether that's desired.\n\n> +\telse if (onto_name) {\n\nNo need to 'else' here IMHO.\n\n> +\t\t*onto = peel_committish(onto_name);\n> +\t\tif (rinfo.positive_refexprs <\n> +\t\t    strset_get_size(&rinfo.positive_refs))\n> +\t\t\tdie(_(\"all positive revisions given must be references\"));\n\nI tested this locally with the following command:\n\n$ git replay --onto main OID..OID\n\nThis command didn't give any errors, neither did it return any\nupdate-ref lines. I would have expected to hit this die().\n\n> +\t} else if (*advance_name) {\n> +\t\tstruct object_id oid;\n> +\t\tchar *fullname = NULL;\n> +\n> +\t\t*onto = peel_committish(*advance_name);\n> +\t\tif (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name),\n> +\t\t\t     &oid, &fullname, 0) == 1) {\n> +\t\t\t*advance_name = fullname;\n> +\t\t} else {\n> +\t\t\tdie(_(\"argument to --advance must be a reference\"));\n> +\t\t}\n> +\t\tif (rinfo.positive_refexprs > 1)\n> +\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n\nThe sources aren't always branches, so I suggest something like:\n\n+\t\t\tdie(_(\"cannot advance target with multiple sources because ordering would be ill-defined\"));\n\n> +\tdetermine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n> +\t\t\t      &onto, &update_refs);\n> +\n> +\tif (!onto) /* FIXME: Should handle replaying down to root commit */\n> +\t\tdie(\"Replaying down to root commit is not supported yet!\");\n\nWhen I was testing locally I tried the following:\n\n$ git replay --onto main feature\n\nI was expecting this command to find the common ancestor automatically,\nbut instead I got this error. I'm fine if for now the command does not\ndetermine the common ancestor yet, but I think we should provide a\nbetter error for this scenario.\n\n> +test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n> +\tgit -C bare replay --advance main topic1..topic2 >result-bare &&\n> +\ttest_cmp expect result-bare\n> +'\n> +\n>  test_done\n\nShall we add a test case when providing both --onto and --advance? And\none that omits both?\n"},{"id":"478677","messageId":"87pm5nstli.fsf@iotcl.com","threadId":"59560","inReplyTo":"20230602102533.876905-11-christian.couder@gmail.com","subject":"Re: [PATCH v3 10/15] replay: make it a minimal server side command","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2023-06-22T10:01:11Z","receivedAt":"2023-06-22T10:10:10Z","isPatch":true,"sender":{"key":"toon@iotcl.com","avatar":"https://avatars.githubusercontent.com/u/121621?v=4"},"body":"\nChristian Couder <christian.couder@gmail.com> writes:\n\n> +\t\t\tdecoration = decoration->next;\n> +\t\t}\n>  \t}\n>\n> +\t/* Cleanup */\n\nNit: I don't think this comment adds much value. I would argue it's more\nconfusing to have it, because there's a label cleanup: just a few lines\ndown.\n\n>  \tmerge_finalize(&merge_opt, &result);\n> +\tret = result.clean;\n"},{"id":"478678","messageId":"87legbsths.fsf@iotcl.com","threadId":"59560","inReplyTo":"20230602102533.876905-12-christian.couder@gmail.com","subject":"Re: [PATCH v3 11/15] replay: use standard revision ranges","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2023-06-22T10:03:03Z","receivedAt":"2023-06-22T10:10:32Z","isPatch":true,"sender":{"key":"toon@iotcl.com","avatar":"https://avatars.githubusercontent.com/u/121621?v=4"},"body":"\nChristian Couder <christian.couder@gmail.com> writes:\n\n> +DESCRIPTION\n> +-----------\n> +\n> +Takes a range of commits, and replays them onto a new location.  Does\n> +not touch the working tree or index, and does not update any\n> +references.  However, the output of this command is meant to be used\n\nSmall suggestion here:\n\nTakes a range of commits, and replays them onto a new location.  Does\nneither touch the working tree nor index, and does not update any\nreferences.\n"},{"id":"478679","messageId":"87cz1nst1s.fsf@iotcl.com","threadId":"59560","inReplyTo":"20230602102533.876905-15-christian.couder@gmail.com","subject":"Re: [PATCH v3 14/15] replay: add --contained to rebase contained branches","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2023-06-22T10:10:51Z","receivedAt":"2023-06-22T10:13:22Z","isPatch":true,"sender":{"key":"toon@iotcl.com","avatar":"https://avatars.githubusercontent.com/u/121621?v=4"},"body":"\nChristian Couder <christian.couder@gmail.com> writes:\n\n> diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\n> index 439b2f92e7..6fcaa44ef2 100644\n> --- a/Documentation/git-replay.txt\n> +++ b/Documentation/git-replay.txt\n> @@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n> +'git replay' ([--contained] --onto <newbase> | --advance <branch>)\n> <revision-range>...\n\nI'm not sure we need this, or at least not right now.\nI've been testing with a repo having:\n\n* a13d9c4 (another-feature) yet another commit\n* c7afc2e (HEAD -> feature) third commit\n* e95cecc second commit\n* f141e77 first commit\n| * 7bb62ac (main) later commit\n| * 506cb0a another commit\n|/\n* e7acac6 initial commit\n\nI tried both commands:\n\n$ git replay --onto main main..feature main..another-feature\n$ git replay --onto main --contained main..another-feature\n\nand they both give the same result (especially with the commit following\nup this one). What is the upside of having this --contained option?\n\nMaybe it's better to defer this patch to a separate series.\n\nAnd another question, in git-rebase(1) a similar option is called\n--update-refs. Would you consider reusing that name here is a good idea\nfor consistency?\n\n> diff --git a/builtin/replay.c b/builtin/replay.c\n> index 4b64d15159..9b182eaa90 100644\n> --- a/builtin/replay.c\n> +++ b/builtin/replay.c\n> @@ -303,6 +306,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n>  \t\tusage_with_options(replay_usage, replay_options);\n>  \t}\n>\n> +\tif (advance_name && contained)\n> +\t\tdie(_(\"options '%s' and '%s' cannot be used together\"),\n> +\t\t    \"--advance\", \"--contained\");\n\nIs there a technical issue why we don't support this? I don't really see\nwhy we couldn't.\n\n--\nToon\n"},{"id":"479860","messageId":"xmqqy1j3itdw.fsf@gitster.g","threadId":"59560","inReplyTo":"20230602102533.876905-13-christian.couder@gmail.com","subject":"Re: [PATCH v3 12/15] replay: disallow revision specific options and pathspecs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-07-25T21:16:43Z","receivedAt":"2023-07-25T21:16:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> +\t/*\n> +\t * Reject any pathspec. (They are allowed and eaten by\n> +\t * setup_revisions() above.) In the future we might accept\n> +\t * them, after adding related tests and doc though.\n> +\t */\n> +\tif (revs.prune_data.nr) {\n> +\t\terror(_(\"invalid pathspec: %s\"), revs.prune_data.items[0].match);\n\nThis made me waste a few minutes wondering if and how I misspelt my\npathspec elements.  If we mean \"no pathspec is allowed\", we should\nsay so instead.\n\n> +\t\tusage_with_options(replay_usage, replay_options);\n> +\t}\n> +\n>  \t/* requirements/overrides for revs */\n>  \trevs.reverse = 1;\n>  \trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n> diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\n> index a1da4f9ef9..de6e40950e 100755\n> --- a/t/t3650-replay-basics.sh\n> +++ b/t/t3650-replay-basics.sh\n> @@ -80,4 +80,20 @@ test_expect_success 'using replay on bare repo to rebase with a conflict' '\n>  \ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n>  '\n>  \n> +test_expect_success 'using replay with (for now) disallowed revision specific option --not' '\n> +\ttest_must_fail git replay --onto main topic2 --not topic1\n> +'\n> +\n> +test_expect_success 'using replay on bare repo with (for now) disallowed revision specific option --first-parent' '\n> +\ttest_must_fail git -C bare replay --onto main --first-parent topic1..topic2\n> +'\n> +\n> +test_expect_success 'using replay with disallowed pathspec' '\n> +\ttest_must_fail git replay --onto main topic1..topic2 A.t\n> +'\n> +\n> +test_expect_success 'using replay on bare repo with disallowed pathspec' '\n> +\ttest_must_fail git -C bare replay --onto main topic1..topic2 -- A.t\n> +'\n> +\n>  test_done\n"},{"id":"479863","messageId":"xmqq8rb3is8c.fsf@gitster.g","threadId":"59560","inReplyTo":"20230602102533.876905-14-christian.couder@gmail.com","subject":"Re: [PATCH v3 13/15] replay: add --advance or 'cherry-pick' mode","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-07-25T21:41:39Z","receivedAt":"2023-07-25T21:41:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> There is already a 'rebase' mode with `--onto`. Let's add an 'advance' or\n> 'cherry-pick' mode with `--advance`. This new mode will make the target\n> branch advance as we replay commits onto it.\n\nIf I say\n\n\t$ git replay --(advance|onto) xyzzy frotz..nitfol yomin\n\nwhere the topology of the cherry-picked range look like this\n\n                      x---x---Y yomin\n                     /\n            E---F---x----x----N nitfol\n           /  frotz\n          /\n         X xyzzy     \n\nafter transplanting the commits, we would get something like\n\n                      x---x---Y yomin\n                     /\n            E---F---x----x----N nitfol\n           /  frotz\n          /\n         X---x----x----N'\n              \\\n               x---x---Y'\n\nNow, if this was done with --onto, nitfol and yomin would point at\nN' and Y', but with --advance, where would xyzzy go?\n\nYes, my point is that without --advance, there always is a\nreasonable set of branch tips that will be moved, but with\n\"--advance\", you cannot guarantee that you have any reasonable\nanswer to that question.\n\nThe answer could be \"when there is no single 'tip of the new\nhistory', the command with '--advance' errors out\", but whatever\nbehaviour we choose, it should be documented.\n\n> @@ -29,9 +29,17 @@ OPTIONS\n>  \tStarting point at which to create the new commits.  May be any\n>  \tvalid commit, and not just an existing branch name.\n>  +\n> -The update-ref commands in the output will update the branch(es)\n> -in the revision range to point at the new commits (in other\n> -words, this mimics a rebase operation).\n> +When `--onto` is specified, the update-ref command(s) in the output will\n> +update the branch(es) in the revision range to point at the new\n> +commits (in other words, this mimics a rebase operation).\n> +\n> +--advance <branch>::\n> +\tStarting point at which to create the new commits; must be a\n> +\tbranch name.\n> ++\n> +When `--advance` is specified, the update-ref command(s) in the output\n> +will update the branch passed as an argument to `--advance` to point at\n> +the new commits (in other words, this mimics a cherry-pick operation).\n\nThis part is not giving much useful information to determine the\nanswer (which might be fine, as long as the answer can easily be\nfound in some other parts of this document, but I would have\nexpected everything necessary would be found here or one item before\nthis one about --onto).\n\n> @@ -47,7 +55,10 @@ input to `git update-ref --stdin`.  It is basically of the form:\n>  \tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n>  \tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n>  \n> -where the number of refs updated depend on the arguments passed.\n> +where the number of refs updated depend on the arguments passed.  When\n> +using `--advance`, the number of refs updated is always one, but for\n> +`--onto`, it can be one or more (rebasing multiple branches\n> +simultaneously is supported).\n\n\"dependS on the arguments passed\" is not incorrect per-se, in the\nsense that if you \"replay --onto X E..N\" (in the above picture), I\nthink you'll move F and N (two), while \"F..N\" will only move N\n(one).  But the important thing to convey is how many branches had\ntheir tips in the replayed range, no?  \"depends on the shape of the\nhistory being replayed\" would be a more correct thing to say for the\n\"--onto\" mode.  For \"--advance\", presumably you would require to\nhave a single positive endpoint [*], so \"depends on the arguments\"\nis still not wrong per-se, because \"when --advance is part of the\narguments, the number becomes one\".\n\n\tSide note: even then, I suspect that\n\n\t\tgit replay --advance X E..F N\n\n        should be allowed, simply because there is only one sensible\n        interpretation.  You'll end up with a single strand of\n        pearls F--x--x--N transplanted on top of X, and the range\n        happens to contain F and N but it is obvious the end result\n        should advance xyzzy to N because F fast-forwards to N.\n\nI'd say \"where the number of ...\" and everything after these sample\n\"update\" lines should be removed, and instead we should add a few\nwords to the main description of the options, e.g.  for \"--onto\"\n\n> +When `--onto` is specified, the update-ref command(s) in the output will\n> +update the branch(es) in the revision range to point at the new\n> +commits (in other words, this mimics a rebase operation).\n\nwe could update the above to something like\n\n    ... will update the branches in the revision range to point at the\n    new commits, similar to the way how \"rebase --update-refs\" updates\n    multiple branches in the affected range.\n\n"},{"id":"481354","messageId":"f74fb509-0e1a-9542-d80c-0bec2a1e6740@gmx.de","threadId":"59560","inReplyTo":"CABPp-BGRtcBQ_6fkMrTskV9dk71ffycXZ8hEE_RaOrAdza_wLA@mail.gmail.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-09-03T15:47:29Z","receivedAt":"2023-09-03T15:47:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah & Stolee,\n\nOn Sat, 29 Apr 2023, Elijah Newren wrote:\n\n> On Mon, Apr 24, 2023 at 8:23 AM Derrick Stolee <derrickstolee@github.com> wrote:\n> >\n> > On 4/22/2023 9:18 PM, Elijah Newren wrote:\n> > > On Thu, Apr 20, 2023 at 6:44 AM Derrick Stolee <derrickstolee@github.com> wrote:\n> > >>\n> > >> On 4/20/2023 12:53 AM, Elijah Newren wrote:\n> > >>> On Tue, Apr 18, 2023 at 6:10 AM Derrick Stolee <derrickstolee@github.com> wrote:\n> >\n> > >>  3. (Ordering options) Modifications to how those commits are ordered,\n> > >>     such as --topo-order, --date-order, and --reverse. These seem to\n> > >>     be overridden by git-replay (although, --reverse probably causes\n> > >>     some confusion right now).\n> > >\n> > > Yep, intentionally overridden.\n> > >\n> > > Could you elaborate on what you mean by --reverse causing confusion?\n> >\n> > It's very unlikely that a list of patches will successfully apply\n> > when applied in the reverse order. If we allow the argument, then\n> > someone will try it thinking they can flip their commits. Then they\n> > might be surprised when there are a bunch of conflicts on every\n> > commit.\n> >\n> > Basically, I'm not super thrilled about exposing options that are\n> > unlikely to be valuable to users and instead are more likely to cause\n> > confusion due to changes that won't successfully apply.\n>\n> Oh, I got thrown by the \"right now\" portion of your comment; I\n> couldn't see how time or future changes would affect anything to make\n> it less (or more) confusing for users.\n>\n> Quick clarification, though: while you correctly point out the type of\n> confusion the user would experience without my overriding, my\n> overriding of rev.reverse (after setup_revisions() returns, not before\n> it is called) precludes that experience.  The override means none of\n> the above happens, and they would instead just wonder why their option\n> is being ignored.\n\nFWIW here is my view on the matter: `git replay`, at least in its current\nincarnation, is a really low-level tool. As such, I actually do not want\nto worry much about protecting users from nonsensical invocations.\n\nIn that light, I would like to see that code rejecting all revision\noptions except `--diff-algorithm` be dropped. Should we ever decide to add\na non-low-level mode to `git replay`, we can easily add some user-friendly\nsanity check of the options then, and only for that non-low-level code.\nFor now, I feel that it's just complicating things, and `git replay` is in\nthe experimental phase anyway.\n\nAnd further, I would even like to see that `--reverse` override go, and\nturn it into `revs.reverse = !revs.reverse` instead. (And yes, I can\neasily think of instances where I would have wanted to reverse a series of\npatches...).\n\nCiao,\nJohannes\n"},{"id":"481460","messageId":"4a648214-2627-c9c0-0be2-5a694e8c3b3c@gmx.de","threadId":"59560","inReplyTo":"CAP8UFD2KMikjJJQ0FGXO1EYYHRNWHH2n70UATU_o61PJ411f2Q@mail.gmail.com","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-09-07T10:22:45Z","receivedAt":"2023-09-07T15:24:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Thu, 7 Sep 2023, Christian Couder wrote:\n\n> On Sun, Sep 3, 2023 at 5:47 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Sat, 29 Apr 2023, Elijah Newren wrote:\n> >\n> > > On Mon, Apr 24, 2023 at 8:23 AM Derrick Stolee <derrickstolee@github.com> wrote:\n>\n> > > > Basically, I'm not super thrilled about exposing options that are\n> > > > unlikely to be valuable to users and instead are more likely to cause\n> > > > confusion due to changes that won't successfully apply.\n> > >\n> > > Oh, I got thrown by the \"right now\" portion of your comment; I\n> > > couldn't see how time or future changes would affect anything to make\n> > > it less (or more) confusing for users.\n> > >\n> > > Quick clarification, though: while you correctly point out the type of\n> > > confusion the user would experience without my overriding, my\n> > > overriding of rev.reverse (after setup_revisions() returns, not before\n> > > it is called) precludes that experience.  The override means none of\n> > > the above happens, and they would instead just wonder why their option\n> > > is being ignored.\n> >\n> > FWIW here is my view on the matter: `git replay`, at least in its current\n> > incarnation, is a really low-level tool. As such, I actually do not want\n> > to worry much about protecting users from nonsensical invocations.\n> >\n> > In that light, I would like to see that code rejecting all revision\n> > options except `--diff-algorithm` be dropped. Should we ever decide to add\n> > a non-low-level mode to `git replay`, we can easily add some user-friendly\n> > sanity check of the options then, and only for that non-low-level code.\n> > For now, I feel that it's just complicating things, and `git replay` is in\n> > the experimental phase anyway.\n>\n> I would be Ok with removing the patch (called \"replay: disallow\n> revision specific options and pathspecs\")\n> that rejects all revision options and pathspecs if there is a\n> consensus for that.\n\nWell, since you talk about \"consensus\" and I already made my strong\npreference known, how about you add yours?\n\n> It might not simplify things too much if there is still an exception for\n> `--diff-algorithm` though. Also it's not clear if you are Ok with\n> allowing pathspecs or not.\n\nI want to remove all the rev-list-option-disallowing code. Including the\npathspec one.\n\n> The idea with disallowing all of them was to later add back those that\n> make sense along with tests and maybe docs to explain them in the\n> context of this command. It was not to disallow them permanently. So I\n> would think the best path forward would be a patch series on top of this\n> one that would revert the patch disallowing these options and maybe\n> pathspecs, and instead allow most of them and document and test things a\n> bit.\n\nI understand that that was the reasoning.\n\nWhat I would like to suggest is that we should treat `git replay` as a\nlow-level (or: \"plumbing\" in Git Speak) command.\n\nThat would allow us to leave the option for stricter command-line\nparameter validation to an _additional_ option, to be added later (or\nnever), and to stop worrying about this tool being used in ways that make\nno sense (or make no sense _to us_, _now_).\n\n> > And further, I would even like to see that `--reverse` override go, and\n> > turn it into `revs.reverse = !revs.reverse` instead. (And yes, I can\n> > easily think of instances where I would have wanted to reverse a series of\n> > patches...).\n>\n> I think this might deserve docs and tests too,\n\nI agree.\n\n> so it might want to be part of a separate patch series once the existing\n> one has graduated.\n\nHere, I disagree. This is a bug, from my perspective, and needs to be\nfixed before graduating.\n\n> At this point I don't think it's worth delaying this patch series for\n> relatively small issues like this. There are many different ways this\n> new command can be polished and improved. The important thing is that\n> it looks like we all agree that the new command makes sense and should\n> have roughly the basic set of features that Elijah originally\n> implemented, so let's go with this, and then we can improve and\n> iterate on top of this.\n\nIf it were for a small issue such as a typo, sure.\n\nBut it is a bug that `--no-reverse` is hard-coded (in a confusing manner\nso because the `reverse` attribute is set), and the\n`--no-reverse`/`--reverse` options are ignored, silently so.\n\nCiao,\nJohannes\n"},{"id":"481462","messageId":"f0e75d47-c277-9fbb-7bcd-53e4e5686f3c@gmx.de","threadId":"59560","inReplyTo":"20230907092521.733746-13-christian.couder@gmail.com","subject":"Re: [PATCH v4 12/15] replay: disallow revision specific options and pathspecs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-09-07T10:24:36Z","receivedAt":"2023-09-07T15:26:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian & Elijah,\n\nOn Thu, 7 Sep 2023, Christian Couder wrote:\n\n> A previous commit changed `git replay` to make it accept standard\n> revision ranges using the setup_revisions() function. While this is a\n> good thing to make this command more standard and more flexible, it has\n> the downside of enabling many revision related options accepted and eaten\n> by setup_revisions().\n>\n> Some of these options might make sense, but others, like those\n> generating non-contiguous history, might not. Anyway those we might want\n> to allow should probably be tested and perhaps documented a bit, which\n> could be done in future work.\n>\n> For now it is just simpler and safer to just disallow all of them, so\n> let's do that.\n>\n> Other commands, like `git fast-export`, currently allow all these\n> revision specific options even though some of them might not make sense,\n> as these commands also use setup_revisions() but do not check the\n> options that might be passed to this function.\n>\n> So a way to fix those commands as well as git replay could be to improve\n> or refactor the setup_revisions() mechanism to let callers allow and\n> disallow options in a relevant way for them. Such improvements are\n> outside the scope of this work though.\n>\n> Pathspecs, which are also accepted and eaten by setup_revisions(), are\n> likely to result in disconnected history. That could perhaps be useful,\n> but that would need tests and documentation, which can be added in\n> future work. So, while at it, let's disallow them too.\n\nAs pointed out elsewhere in this mail thread, I consider this patch to do\nmore harm than good. After switching the command to plumbingmanipulators\nit should be possible to just forego all command-line validation and leave\nthat job to the caller.\n\nTherefore I would love to see this patch dropped.\n\nCiao,\nJohannes\n"},{"id":"481461","messageId":"20230907092521.733746-7-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 06/15] replay: don't simplify history","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:12Z","receivedAt":"2023-09-07T15:26:01Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's set the rev walking options we need after calling\nsetup_revisions() instead of before. This makes it clearer which options\nwe need.\n\nAlso we don't want history simplification, as we want to deal with all\nthe commits in the affected range.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 15 ++++++---------\n 1 file changed, 6 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex c66888679b..4b1e501595 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -173,15 +173,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \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-\n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n@@ -189,6 +180,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \n+\t/* requirements/overrides for revs */\n+\trevs.reverse = 1;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\trevs.simplify_history = 0;\n+\n \tstrvec_clear(&rev_walk_args);\n \n \tif (prepare_revision_walk(&revs) < 0) {\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481463","messageId":"58daa706-7efb-51dd-9061-202ef650b96a@gmx.de","threadId":"59560","inReplyTo":"20230907092521.733746-7-christian.couder@gmail.com","subject":"Re: [PATCH v4 06/15] replay: don't simplify history","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-09-07T10:23:29Z","receivedAt":"2023-09-07T15:26:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian & Elijah,\n\nOn Thu, 7 Sep 2023, Christian Couder wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Let's set the rev walking options we need after calling\n> setup_revisions() instead of before. This makes it clearer which options\n> we need.\n\nIn light of the currently open issue about command-line validation, this\nchange does more than this paragraph lets on: It hardcodes certain\nsettings, overriding (silently) any rev-list options the user might have\npassed.\n\nIs there any chance that we can avoid this change?\n\n> Also we don't want history simplification, as we want to deal with all\n> the commits in the affected range.\n\nThis, however, is a good change. It deserves to live in its own commit,\nwith its own commit message, in particular because it is not obvious from\nthe attribute names which ones we're talking about (I guess it's `limited`\nand `simplify_history`, not just the latter.\n\nCiao,\nJohannes\n"},{"id":"481464","messageId":"20230907092521.733746-8-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 07/15] replay: add an important FIXME comment about gpg signing","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:13Z","receivedAt":"2023-09-07T15:26:02Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want to be able to handle signed commits in some way in the future,\nbut we are not ready to do it now. So for the time being let's just add\na FIXME comment to remind us about it.\n\nThese are different ways we could handle them:\n\n  - in case of a cli user and if there was an interactive mode, we could\n    perhaps ask if the user wants to sign again\n  - we could add an option to just fail if there are signed commits\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 4b1e501595..47d695df93 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -62,7 +62,7 @@ static struct commit *create_commit(struct tree *tree,\n \tstruct object *obj;\n \tstruct commit_list *parents = NULL;\n \tchar *author;\n-\tchar *sign_commit = NULL;\n+\tchar *sign_commit = NULL; /* FIXME: cli users might want to sign again */\n \tstruct commit_extra_header *extra;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *out_enc = get_commit_output_encoding();\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481465","messageId":"CAP8UFD3bpLrVW97DH7j=V9H2GsTSAkksC9L3QujQERFk_kLnZA@mail.gmail.com","threadId":"59560","inReplyTo":"xmqq8rb3is8c.fsf@gitster.g","subject":"Re: [PATCH v3 13/15] replay: add --advance or 'cherry-pick' mode","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T08:35:26Z","receivedAt":"2023-09-07T15:26:02Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Jul 25, 2023 at 11:41 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > There is already a 'rebase' mode with `--onto`. Let's add an 'advance' or\n> > 'cherry-pick' mode with `--advance`. This new mode will make the target\n> > branch advance as we replay commits onto it.\n>\n> If I say\n>\n>         $ git replay --(advance|onto) xyzzy frotz..nitfol yomin\n>\n> where the topology of the cherry-picked range look like this\n>\n>                       x---x---Y yomin\n>                      /\n>             E---F---x----x----N nitfol\n>            /  frotz\n>           /\n>          X xyzzy\n>\n> after transplanting the commits, we would get something like\n>\n>                       x---x---Y yomin\n>                      /\n>             E---F---x----x----N nitfol\n>            /  frotz\n>           /\n>          X---x----x----N'\n>               \\\n>                x---x---Y'\n>\n> Now, if this was done with --onto, nitfol and yomin would point at\n> N' and Y', but with --advance, where would xyzzy go?\n>\n> Yes, my point is that without --advance, there always is a\n> reasonable set of branch tips that will be moved, but with\n> \"--advance\", you cannot guarantee that you have any reasonable\n> answer to that question.\n>\n> The answer could be \"when there is no single 'tip of the new\n> history', the command with '--advance' errors out\", but whatever\n> behaviour we choose, it should be documented.\n\nOk, I have improved the commit message by adding the following:\n\n\"The replayed commits should have a single tip, so that it's clear where\nthe target branch should be advanced. If they have more than one tip,\nthis new mode will error out.\"\n\nI have also updated the doc for <revision-range> like this:\n\n\"<revision-range>::\n    Range of commits to replay. More than one <revision-range> can\n    be passed, but in `--advance <branch>` mode, they should have\n    a single tip, so that it's clear where <branch> should point\n    to. See \"Specifying Ranges\" in linkgit:git-rev-parse.\"\n\n> > +--advance <branch>::\n> > +     Starting point at which to create the new commits; must be a\n> > +     branch name.\n> > ++\n> > +When `--advance` is specified, the update-ref command(s) in the output\n> > +will update the branch passed as an argument to `--advance` to point at\n> > +the new commits (in other words, this mimics a cherry-pick operation).\n>\n> This part is not giving much useful information to determine the\n> answer (which might be fine, as long as the answer can easily be\n> found in some other parts of this document, but I would have\n> expected everything necessary would be found here or one item before\n> this one about --onto).\n\nThe doc about <revision-range> is just after the above, so I think the\nabove change in the <revision-range> doc is Ok and enough here.\n\n> > @@ -47,7 +55,10 @@ input to `git update-ref --stdin`.  It is basically of the form:\n> >       update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n> >       update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n> >\n> > -where the number of refs updated depend on the arguments passed.\n> > +where the number of refs updated depend on the arguments passed.  When\n> > +using `--advance`, the number of refs updated is always one, but for\n> > +`--onto`, it can be one or more (rebasing multiple branches\n> > +simultaneously is supported).\n>\n> \"dependS on the arguments passed\" is not incorrect per-se, in the\n> sense that if you \"replay --onto X E..N\" (in the above picture), I\n> think you'll move F and N (two), while \"F..N\" will only move N\n> (one).  But the important thing to convey is how many branches had\n> their tips in the replayed range, no?  \"depends on the shape of the\n> history being replayed\" would be a more correct thing to say for the\n> \"--onto\" mode.  For \"--advance\", presumably you would require to\n> have a single positive endpoint [*], so \"depends on the arguments\"\n> is still not wrong per-se, because \"when --advance is part of the\n> arguments, the number becomes one\".\n\nYeah, I agree.\n\n>         Side note: even then, I suspect that\n>\n>                 git replay --advance X E..F N\n>\n>         should be allowed, simply because there is only one sensible\n>         interpretation.  You'll end up with a single strand of\n>         pearls F--x--x--N transplanted on top of X, and the range\n>         happens to contain F and N but it is obvious the end result\n>         should advance xyzzy to N because F fast-forwards to N.\n>\n> I'd say \"where the number of ...\" and everything after these sample\n> \"update\" lines should be removed,\n\nI am not sure it's a good thing to remove that, as I think repeating\nhow things work in the context of an example output can help people\nunderstand. I have updated these sentences to the following:\n\n\"where the number of refs updated depends on the arguments passed and\nthe shape of the history being replayed.  When using `--advance`, the\nnumber of refs updated is always one, but for `--onto`, it can be one\nor more (rebasing multiple branches simultaneously is supported).\"\n\n> and instead we should add a few\n> words to the main description of the options, e.g.  for \"--onto\"\n>\n> > +When `--onto` is specified, the update-ref command(s) in the output will\n> > +update the branch(es) in the revision range to point at the new\n> > +commits (in other words, this mimics a rebase operation).\n>\n> we could update the above to something like\n>\n>     ... will update the branches in the revision range to point at the\n>     new commits, similar to the way how \"rebase --update-refs\" updates\n>     multiple branches in the affected range.\n\nYeah, I agree. In the version 4 I will send soon, have updated the above to:\n\n\"When `--onto` is specified, the update-ref command(s) in the output will\nupdate the branch(es) in the revision range to point at the new\ncommits, similar to the way how `git rebase --update-refs` updates\n\nmultiple branches in the affected range.\"\n"},{"id":"481468","messageId":"20230907092521.733746-5-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 04/15] replay: die() instead of failing assert()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:10Z","receivedAt":"2023-09-07T15:51:33Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIt's not a good idea for regular Git commands to use an assert() to\ncheck for things that could happen but are not supported.\n\nLet's die() with an explanation of the issue instead.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex d6dec7c866..f3fdbe48c9 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -179,7 +179,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\n+\t\tif (!commit->parents)\n+\t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n+\t\tif (commit->parents->next)\n+\t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n+\n \t\tbase = commit->parents->item;\n \n \t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481470","messageId":"CAP8UFD1_67rNke1y-GV7qX46GXQkNJzw7fXfK=kkUjc-jb5S_g@mail.gmail.com","threadId":"59560","inReplyTo":"87legbsths.fsf@iotcl.com","subject":"Re: [PATCH v3 11/15] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T08:32:45Z","receivedAt":"2023-09-07T16:16:23Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Jun 22, 2023 at 12:03 PM Toon Claes <toon@iotcl.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > +DESCRIPTION\n> > +-----------\n> > +\n> > +Takes a range of commits, and replays them onto a new location.  Does\n> > +not touch the working tree or index, and does not update any\n> > +references.  However, the output of this command is meant to be used\n>\n> Small suggestion here:\n>\n> Takes a range of commits, and replays them onto a new location.  Does\n> neither touch the working tree nor index, and does not update any\n> references.\n\nI am not a native speaker, so I am not sure what's best here. I find\nyour suggestion a bit less clear though, so until a native speaker\nagrees with it or maybe finds something even better, I prefer to leave\nit as-is.\n"},{"id":"481472","messageId":"CAP8UFD3bkVGB+K9HAtGooGoJTpMck2xUv+wJzikNbvZq3nyHTQ@mail.gmail.com","threadId":"59560","inReplyTo":"87pm5nstli.fsf@iotcl.com","subject":"Re: [PATCH v3 10/15] replay: make it a minimal server side command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T08:32:05Z","receivedAt":"2023-09-07T16:24:28Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Jun 22, 2023 at 12:01 PM Toon Claes <toon@iotcl.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > +                     decoration = decoration->next;\n> > +             }\n> >       }\n> >\n> > +     /* Cleanup */\n>\n> Nit: I don't think this comment adds much value. I would argue it's more\n> confusing to have it, because there's a label cleanup: just a few lines\n> down.\n\nOk, I have removed that comment in the version 4 I will send soon.\n"},{"id":"481473","messageId":"20230907092521.733746-11-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 10/15] replay: make it a minimal server side command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:16Z","receivedAt":"2023-09-07T16:35:08Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want this command to be a minimal command that just does server side\npicking of commits, displaying the results on stdout for higher level\nscripts to consume.\n\nSo let's simplify it:\n  * remove the worktree and index reading/writing,\n  * remove the ref (and reflog) updating,\n  * remove the assumptions tying us to HEAD, since (a) this is not a\n    rebase and (b) we want to be able to pick commits in a bare repo,\n    i.e. to/from branches that are not checked out and not the main\n    branch,\n  * remove unneeded includes,\n  * handle rebasing multiple branches by printing on stdout the update\n    ref commands that should be performed.\n\nThe output can be piped into `git update-ref --stdin` for the ref\nupdates to happen.\n\nIn the future to make it easier for users to use this command\ndirectly maybe an option can be added to automatically pipe its output\ninto `git update-ref`.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c                         | 78 ++++++++----------------\n t/t6429-merge-sequence-rename-caching.sh | 39 +++++++-----\n 2 files changed, 50 insertions(+), 67 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex a2636fbdcc..e45cd59da1 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -6,11 +6,7 @@\n #include \"git-compat-util.h\"\n \n #include \"builtin.h\"\n-#include \"cache-tree.h\"\n-#include \"commit.h\"\n #include \"environment.h\"\n-#include \"gettext.h\"\n-#include \"hash.h\"\n #include \"hex.h\"\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n@@ -18,8 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"sequencer.h\"\n-#include \"setup.h\"\n #include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n@@ -102,6 +96,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n+\tmerge_opt->branch1 = short_commit_name(last_commit);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -122,15 +117,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct lock_file lock = LOCK_INIT;\n+\tstruct commit *last_commit = NULL;\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 *head_tree;\n \tstruct merge_result result;\n-\tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n@@ -161,10 +153,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index(the_repository) < 0)\n-\t\tBUG(\"Could not read index\");\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n@@ -190,58 +178,44 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\tmerge_opt.branch1 = \"HEAD\";\n-\thead_tree = repo_get_commit_tree(the_repository, onto);\n-\tresult.tree = head_tree;\n+\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *pick;\n+\t\tconst struct name_decoration *decoration;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n-\t\tif (!pick)\n+\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!last_commit)\n \t\t\tbreak;\n-\t\tlast_commit = pick;\n-\t\tlast_picked_commit = commit;\n+\n+\t\tdecoration = get_name_decoration(&commit->object);\n+\t\tif (!decoration)\n+\t\t\tcontinue;\n+\n+\t\twhile (decoration) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\t\tprintf(\"update %s %s %s\\n\",\n+\t\t\t\t       decoration->name,\n+\t\t\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t\t\t       oid_to_hex(&commit->object.oid));\n+\t\t\t}\n+\t\t\tdecoration = decoration->next;\n+\t\t}\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n+\tret = result.clean;\n \n-\tif (result.clean < 0)\n-\t\texit(128);\n-\n-\tif (result.clean) {\n-\t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n-\t\t\t    oid_to_hex(&last_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, branch_name.buf,\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &last_picked_commit->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n-\t\t\tdie(_(\"unable to update HEAD\"));\n-\t} else {\n-\t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &onto->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t}\n-\tret = (result.clean == 0);\n cleanup:\n-\tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n-\treturn ret;\n+\n+\t/* Return */\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret ? 0 : 1;\n }\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 7670b72008..099aefeffc 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,8 +71,9 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -140,7 +141,9 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -198,8 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -275,8 +279,9 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -451,8 +456,9 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -517,8 +523,9 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -619,8 +626,9 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -677,8 +685,9 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481474","messageId":"20230907092521.733746-15-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 14/15] replay: add --contained to rebase contained branches","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:20Z","receivedAt":"2023-09-07T16:38:43Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's add a `--contained` option that can be used along with\n`--onto` to rebase all the branches contained in the <revision-range>\nargument.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt | 12 +++++++++++-\n builtin/replay.c             | 12 ++++++++++--\n t/t3650-replay-basics.sh     | 29 +++++++++++++++++++++++++++++\n 3 files changed, 50 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 5c5c15237d..b94c39b161 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n SYNOPSIS\n --------\n [verse]\n-'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n+'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -93,6 +93,16 @@ top of the exact same new base, they only differ in that the first\n provides instructions to make mybranch point at the new commits and\n the second provides instructions to make target point at them.\n \n+What if you have a stack of branches, one depending upon another, and\n+you'd really like to rebase the whole set?\n+\n+------------\n+$ git replay --contained --onto origin/main origin/main..tipbranch\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}\n+------------\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 6b89964be9..a7d36a639c 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -258,6 +258,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tconst char *advance_name = NULL;\n \tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n+\tint contained = 0;\n \n \tstruct rev_info revs;\n \tstruct commit *last_commit = NULL;\n@@ -268,7 +269,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n+\t\tN_(\"git replay ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -278,6 +279,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_BOOL(0, \"contained\", &contained,\n+\t\t\t N_(\"advance all branches contained in revision-range\")),\n \t\tOPT_END()\n \t};\n \n@@ -303,6 +306,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n+\tif (advance_name && contained)\n+\t\tdie(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t    \"--advance\", \"--contained\");\n+\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \targc = setup_revisions(argc, argv, &revs, NULL);\n@@ -364,7 +371,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tcontinue;\n \t\twhile (decoration) {\n \t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n-\t\t\t    strset_contains(update_refs, decoration->name)) {\n+\t\t\t    (contained || strset_contains(update_refs,\n+\t\t\t\t\t\t\t  decoration->name))) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 1919f7d5d1..57d2ef9ea4 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -130,4 +130,33 @@ test_expect_success 'replay fails when both --advance and --onto are omitted' '\n \ttest_must_fail git replay topic1..topic2 >result\n '\n \n+test_expect_success 'using replay to also rebase a contained branch' '\n+\tgit replay --contained --onto main main..topic3 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines H G F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic1 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic1 >>expect &&\n+\tprintf \"update refs/heads/topic3 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic3 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to also rebase a contained branch' '\n+\tgit -C bare replay --contained --onto main main..topic3 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n test_done\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481475","messageId":"CAP8UFD0T64UcDLUrbjrR0DvD4DQLuZ==ATk9ewSMkMn+9VhMAg@mail.gmail.com","threadId":"59560","inReplyTo":"87h6qzst8u.fsf@iotcl.com","subject":"Re: [PATCH v3 13/15] replay: add --advance or 'cherry-pick' mode","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T08:35:09Z","receivedAt":"2023-09-07T16:40:02Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Jun 22, 2023 at 12:09 PM Toon Claes <toon@iotcl.com> wrote:\n>\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > +     /*\n> > +      * When the user specifies e.g.\n> > +      *   git replay origin/main..mybranch\n> > +      *   git replay ^origin/next mybranch1 mybranch2\n>\n> When I'm trying these, I'm getting the error:\n>     error: option --onto or --advance is mandatory\n>\n> In what situation can I omit both --onto and --advance?\n\nIt was possible with version 1 of this series as one of the patches\nallowed the command to guess the base:\n\nhttps://lore.kernel.org/git/20230407072415.1360068-13-christian.couder@gmail.com/\n\nso --onto wasn't needed to specify it.\n\nComments on that patch said that it might be better to focus on a\nplumbing command first and for that the patch wasn't needed, so I\nremoved it in version 2.\n\n> > +static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n> > +                               const char *onto_name,\n> > +                               const char **advance_name,\n> > +                               struct commit **onto,\n>\n> Would it make sense to call this target?\n\nThis is more the new base we are rebasing target branches onto. So if\nwe want to change the name, `--base` or `--newbase` would make more\nsense. `git rebase` already has `--onto` though, so, if we want to be\nconsistent with it, we should keep `--onto` for this.\n\n> > +                               struct strset **update_refs)\n> > +{\n> > +     struct ref_info rinfo;\n> > +\n> > +     get_ref_information(cmd_info, &rinfo);\n> > +     if (!rinfo.positive_refexprs)\n> > +             die(_(\"need some commits to replay\"));\n> > +     if (onto_name && *advance_name)\n> > +             die(_(\"--onto and --advance are incompatible\"));\n>\n> Do we actually need to disallow this? I mean from git-replay's point of\n> view, there's no technical limitation why can cannot support both modes\n> at once. The update-ref commands in the output will update both target\n> and source branches, but it's not up to us whether that's desired.\n\nI am not sure what you call \"target\" and \"source\" branches. Anyway\nhere is in simple terms the way the command works:\n\n  1) it takes either `--onto <newbase>` or `--advance <branch>` and\nthen one or more <revision-range>,\n  2) it replays all the commits in the <revision-range> onto either\n<newbase> or <branch>,\n  3) in case of `--advance`, it outputs a single command for `git\nupdate-ref --stdin` to advance <branch> to the last commit that was\njust replayed,\n  4) in case of `--onto`, it outputs a number of commands for `git\nupdate-ref --stdin` to update the branches in <revision-range> to\nwhere the tip commits of these branches have been replayed.\n\nSo `--advance` is like a cherry-pick, and `--onto` is like a rebase.\n\nIt would be possible to do both a rebase onto a branch and a\ncherry-pick of the rebased commits onto that branch, but this is not\nvery common and you can achieve the same result by just rebasing and\nthen using `git reset` or `git update-ref` to make the branch point to\nthe result of the rebase. So I don't see the point of complicating the\ncommand at this point.\n\n> > +     else if (onto_name) {\n>\n> No need to 'else' here IMHO.\n>\n> > +             *onto = peel_committish(onto_name);\n> > +             if (rinfo.positive_refexprs <\n> > +                 strset_get_size(&rinfo.positive_refs))\n> > +                     die(_(\"all positive revisions given must be references\"));\n>\n> I tested this locally with the following command:\n>\n> $ git replay --onto main OID..OID\n>\n> This command didn't give any errors, neither did it return any\n> update-ref lines. I would have expected to hit this die().\n\nYeah, this might be unexpected.\n\nI tested it too and 'rinfo.positive_refexprs' is 1 while\n'strset_get_size(&rinfo.positive_refs)' is 0 with such a command.\n\nThe result does not look wrong though. Above that code there is:\n\n    if (!rinfo.positive_refexprs)\n        die(_(\"need some commits to replay\"));\n\nso it looks like there is at least a check that the revision range\npassed to the command contains positive commits.\n\nIt might be possible that users prefer a command that outputs nothing\nwhen there is nothing to replay instead of erroring out.\n\n> > +     } else if (*advance_name) {\n> > +             struct object_id oid;\n> > +             char *fullname = NULL;\n> > +\n> > +             *onto = peel_committish(*advance_name);\n> > +             if (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name),\n> > +                          &oid, &fullname, 0) == 1) {\n> > +                     *advance_name = fullname;\n> > +             } else {\n> > +                     die(_(\"argument to --advance must be a reference\"));\n> > +             }\n> > +             if (rinfo.positive_refexprs > 1)\n> > +                     die(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n>\n> The sources aren't always branches, so I suggest something like:\n>\n> +                       die(_(\"cannot advance target with multiple sources because ordering would be ill-defined\"));\n\nYeah, that looks reasonable. I have made this change in version 4 I\nwill send very soon.\n\n> > +     determine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n> > +                           &onto, &update_refs);\n> > +\n> > +     if (!onto) /* FIXME: Should handle replaying down to root commit */\n> > +             die(\"Replaying down to root commit is not supported yet!\");\n>\n> When I was testing locally I tried the following:\n>\n> $ git replay --onto main feature\n>\n> I was expecting this command to find the common ancestor automatically,\n> but instead I got this error. I'm fine if for now the command does not\n> determine the common ancestor yet, but I think we should provide a\n> better error for this scenario.\n\nI agree that it isn't very user friendly. We could indeed try to find\nif there is a common ancestor, and, if that's the case, suggest\nanother way to call the command. This is a plumbing command in its\nearly stage though for now. So I guess it's Ok to postpone working on\nnicer error messages.\n\n> > +test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n> > +     git -C bare replay --advance main topic1..topic2 >result-bare &&\n> > +     test_cmp expect result-bare\n> > +'\n> > +\n> >  test_done\n>\n> Shall we add a test case when providing both --onto and --advance? And\n> one that omits both?\n\nOk, I have made this change in version 4.\n"},{"id":"481476","messageId":"CAP8UFD0Jj5KrAyAa6uD_gTYNxSy8zLtjk8Pg8QyCc0d5ZZfRvw@mail.gmail.com","threadId":"59560","inReplyTo":"87cz1nst1s.fsf@iotcl.com","subject":"Re: [PATCH v3 14/15] replay: add --contained to rebase contained branches","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T08:37:11Z","receivedAt":"2023-09-07T16:40:03Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Jun 22, 2023 at 12:13 PM Toon Claes <toon@iotcl.com> wrote:\n>\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\n> > index 439b2f92e7..6fcaa44ef2 100644\n> > --- a/Documentation/git-replay.txt\n> > +++ b/Documentation/git-replay.txt\n> > @@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n> >  SYNOPSIS\n> >  --------\n> >  [verse]\n> > -'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n> > +'git replay' ([--contained] --onto <newbase> | --advance <branch>)\n> > <revision-range>...\n>\n> I'm not sure we need this, or at least not right now.\n> I've been testing with a repo having:\n>\n> * a13d9c4 (another-feature) yet another commit\n> * c7afc2e (HEAD -> feature) third commit\n> * e95cecc second commit\n> * f141e77 first commit\n> | * 7bb62ac (main) later commit\n> | * 506cb0a another commit\n> |/\n> * e7acac6 initial commit\n>\n> I tried both commands:\n>\n> $ git replay --onto main main..feature main..another-feature\n> $ git replay --onto main --contained main..another-feature\n>\n> and they both give the same result (especially with the commit following\n> up this one). What is the upside of having this --contained option?\n\nThis is expected. The thing is that:\n\n$ git replay --onto main main..another-feature\n\nwill only output something to update 'another-feature'\n\nwhile:\n\n$ git replay --onto main --contained main..another-feature\n\nwill output something to update 'another-feature' and also something\nto update 'feature'.\n\nSo when you use --contained you don't need to first find the other\nbranches like 'feature' that point to commits between 'main' and\n'another-feature', as --contained will find them for you.\n\n> Maybe it's better to defer this patch to a separate series.\n\nI am not sure why you are proposing this. It's true that there are\nother means to achieve the same thing as --contained, but that doesn't\nmean that it cannot be useful already. If there were things that\nneeded to be more polished in this feature, then maybe leaving it for\na separate series later might allow this series to graduate while\n--contained is polished, but I don't think we are in this case.\n\n> And another question, in git-rebase(1) a similar option is called\n> --update-refs. Would you consider reusing that name here is a good idea\n> for consistency?\n\n`git replay` outputs commands that can be passed to `git update-ref\n--stdin`, but for now it doesn't run that command itself. So no refs\nare actually updated. If we ever add an option for git replay to also\nupdate the refs, it would have a name probably quite similar to\n--update-refs so it would be unfortunate if --update-refs is already\nused for something else.\n\n--contained also tells about the fact that the branches affected by\nthe option are \"contained\" in the revision range that is passed, which\nis nice.\n\nIn short I think it's just unfortunate that git rebase already uses\n--update-refs for the \"contained\" branches, as it would be likely to\nconfuse people if we would use it here for that.\n"},{"id":"481477","messageId":"20230907092521.733746-2-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 01/15] t6429: remove switching aspects of fast-rebase","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:07Z","receivedAt":"2023-09-07T16:49:35Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAt the time t6429 was written, merge-ort was still under development,\ndid not have quite as many tests, and certainly was not widely deployed.\nSince t6429 was exercising some codepaths just a little differently, we\nthought having them also test the \"merge_switch_to_result()\" bits of\nmerge-ort was useful even though they weren't intrinsic to the real\npoint of these tests.\n\nHowever, the value provided by doing extra testing of the\n\"merge_switch_to_result()\" bits has decreased a bit over time, and it's\nactively making it harder to refactor `test-tool fast-rebase` into `git\nreplay`, which we are going to do in following commits.  Dispense with\nthese bits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/helper/test-fast-rebase.c              | 9 +--------\n t/t6429-merge-sequence-rename-caching.sh | 9 +++++++--\n 2 files changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c\nindex cac20a72b3..2bfab66b1b 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/t/helper/test-fast-rebase.c\n@@ -194,7 +194,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n-\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\tmerge_finalize(&merge_opt, &result);\n \n \tif (result.clean < 0)\n \t\texit(128);\n@@ -213,9 +213,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n-\n-\t\tprime_cache_tree(the_repository, the_repository->index,\n-\t\t\t\t result.tree);\n \t} else {\n \t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n@@ -228,10 +225,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t\tdie(\"Failed to update %s\", argv[4]);\n \t\t}\n \t}\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-\n \tret = (result.clean == 0);\n cleanup:\n \tstrbuf_release(&reflog_msg);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex d02fa16614..75d3fd2dba 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -72,6 +72,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \t\tgit switch upstream &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n@@ -200,6 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -277,6 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -356,8 +359,6 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n \t\t#git cherry-pick upstream..topic &&\n \n-\t\tgrep CONFLICT..rename/rename output &&\n-\n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n \t)\n@@ -456,6 +457,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -522,6 +524,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -624,6 +627,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -682,6 +686,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481478","messageId":"20230907092521.733746-13-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 12/15] replay: disallow revision specific options and pathspecs","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:18Z","receivedAt":"2023-09-07T16:50:45Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"A previous commit changed `git replay` to make it accept standard\nrevision ranges using the setup_revisions() function. While this is a\ngood thing to make this command more standard and more flexible, it has\nthe downside of enabling many revision related options accepted and eaten\nby setup_revisions().\n\nSome of these options might make sense, but others, like those\ngenerating non-contiguous history, might not. Anyway those we might want\nto allow should probably be tested and perhaps documented a bit, which\ncould be done in future work.\n\nFor now it is just simpler and safer to just disallow all of them, so\nlet's do that.\n\nOther commands, like `git fast-export`, currently allow all these\nrevision specific options even though some of them might not make sense,\nas these commands also use setup_revisions() but do not check the\noptions that might be passed to this function.\n\nSo a way to fix those commands as well as git replay could be to improve\nor refactor the setup_revisions() mechanism to let callers allow and\ndisallow options in a relevant way for them. Such improvements are\noutside the scope of this work though.\n\nPathspecs, which are also accepted and eaten by setup_revisions(), are\nlikely to result in disconnected history. That could perhaps be useful,\nbut that would need tests and documentation, which can be added in\nfuture work. So, while at it, let's disallow them too.\n\nHelped-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 26 +++++++++++++++++++++++++-\n t/t3650-replay-basics.sh | 16 ++++++++++++++++\n 2 files changed, 41 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex de2ddeae3e..60abdaee9e 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -121,7 +121,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n-\tint ret = 0;\n+\tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n \t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n@@ -137,6 +137,20 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n \t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n \n+\t/*\n+\t * TODO: For now, we reject any unknown or invalid option,\n+\t * including revision related ones, like --not,\n+\t * --first-parent, etc that would be allowed and eaten by\n+\t * setup_revisions() below. In the future we should definitely\n+\t * accept those that make sense and add related tests and doc\n+\t * though.\n+\t */\n+\tfor (i = 0; i < argc; i++)\n+\t\tif (argv[i][0] == '-') {\n+\t\t\terror(_(\"invalid option: %s\"), argv[i]);\n+\t\t\tusage_with_options(replay_usage, replay_options);\n+\t\t}\n+\n \tif (!onto_name) {\n \t\terror(_(\"option --onto is mandatory\"));\n \t\tusage_with_options(replay_usage, replay_options);\n@@ -152,6 +166,16 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \n+\t/*\n+\t * Reject any pathspec. (They are allowed and eaten by\n+\t * setup_revisions() above.) In the future we might accept\n+\t * them, after adding related tests and doc though.\n+\t */\n+\tif (revs.prune_data.nr) {\n+\t\terror(_(\"no pathspec is allowed: '%s'\"), revs.prune_data.items[0].match);\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n+\n \t/* requirements/overrides for revs */\n \trevs.reverse = 1;\n \trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex a1da4f9ef9..de6e40950e 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -80,4 +80,20 @@ test_expect_success 'using replay on bare repo to rebase with a conflict' '\n \ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n '\n \n+test_expect_success 'using replay with (for now) disallowed revision specific option --not' '\n+\ttest_must_fail git replay --onto main topic2 --not topic1\n+'\n+\n+test_expect_success 'using replay on bare repo with (for now) disallowed revision specific option --first-parent' '\n+\ttest_must_fail git -C bare replay --onto main --first-parent topic1..topic2\n+'\n+\n+test_expect_success 'using replay with disallowed pathspec' '\n+\ttest_must_fail git replay --onto main topic1..topic2 A.t\n+'\n+\n+test_expect_success 'using replay on bare repo with disallowed pathspec' '\n+\ttest_must_fail git -C bare replay --onto main topic1..topic2 -- A.t\n+'\n+\n test_done\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481479","messageId":"20230907092521.733746-14-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 13/15] replay: add --advance or 'cherry-pick' mode","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:19Z","receivedAt":"2023-09-07T16:55:39Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere is already a 'rebase' mode with `--onto`. Let's add an 'advance' or\n'cherry-pick' mode with `--advance`. This new mode will make the target\nbranch advance as we replay commits onto it.\n\nThe replayed commits should have a single tip, so that it's clear where\nthe target branch should be advanced. If they have more than one tip,\nthis new mode will error out.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt |  40 ++++++--\n builtin/replay.c             | 185 +++++++++++++++++++++++++++++++++--\n t/t3650-replay-basics.sh     |  34 +++++++\n 3 files changed, 242 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 9a2087b01a..5c5c15237d 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - Replay commits on a different base, without touching working tree\n SYNOPSIS\n --------\n [verse]\n-'git replay' --onto <newbase> <revision-range>...\n+'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -29,14 +29,24 @@ OPTIONS\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n +\n-The update-ref command(s) in the output will update the branch(es) in\n-the revision range to point at the new commits, similar to the way how\n-`git rebase --update-refs` updates multiple branches in the affected\n-range.\n+When `--onto` is specified, the update-ref command(s) in the output will\n+update the branch(es) in the revision range to point at the new\n+commits, similar to the way how `git rebase --update-refs` updates\n+multiple branches in the affected range.\n+\n+--advance <branch>::\n+\tStarting point at which to create the new commits; must be a\n+\tbranch name.\n++\n+When `--advance` is specified, the update-ref command(s) in the output\n+will update the branch passed as an argument to `--advance` to point at\n+the new commits (in other words, this mimics a cherry-pick operation).\n \n <revision-range>::\n-\tRange of commits to replay; see \"Specifying Ranges\" in\n-\tlinkgit:git-rev-parse.\n+\tRange of commits to replay. More than one <revision-range> can\n+\tbe passed, but in `--advance <branch>` mode, they should have\n+\ta single tip, so that it's clear where <branch> should point\n+\tto. See \"Specifying Ranges\" in linkgit:git-rev-parse.\n \n OUTPUT\n ------\n@@ -49,7 +59,9 @@ input to `git update-ref --stdin`.  It is basically of the form:\n \tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n \n where the number of refs updated depends on the arguments passed and\n-the shape of the history being replayed.\n+the shape of the history being replayed.  When using `--advance`, the\n+number of refs updated is always one, but for `--onto`, it can be one\n+or more (rebasing multiple branches simultaneously is supported).\n \n EXIT STATUS\n -----------\n@@ -69,6 +81,18 @@ $ git replay --onto target origin/main..mybranch\n update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n ------------\n \n+To cherry-pick the commits from mybranch onto target:\n+\n+------------\n+$ git replay --advance target origin/main..mybranch\n+update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH}\n+------------\n+\n+Note that the first two examples replay the exact same commits and on\n+top of the exact same new base, they only differ in that the first\n+provides instructions to make mybranch point at the new commits and\n+the second provides instructions to make target point at them.\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 60abdaee9e..6b89964be9 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,6 +14,7 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"strmap.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -82,6 +83,146 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+struct ref_info {\n+\tstruct commit *onto;\n+\tstruct strset positive_refs;\n+\tstruct strset negative_refs;\n+\tint positive_refexprs;\n+\tint negative_refexprs;\n+};\n+\n+static void get_ref_information(struct rev_cmdline_info *cmd_info,\n+\t\t\t\tstruct ref_info *ref_info)\n+{\n+\tint i;\n+\n+\tref_info->onto = NULL;\n+\tstrset_init(&ref_info->positive_refs);\n+\tstrset_init(&ref_info->negative_refs);\n+\tref_info->positive_refexprs = 0;\n+\tref_info->negative_refexprs = 0;\n+\n+\t/*\n+\t * When the user specifies e.g.\n+\t *   git replay origin/main..mybranch\n+\t *   git replay ^origin/next mybranch1 mybranch2\n+\t * we want to be able to determine where to replay the commits.  In\n+\t * these examples, the branches are probably based on an old version\n+\t * of either origin/main or origin/next, so we want to replay on the\n+\t * newest version of that branch.  In contrast we would want to error\n+\t * out if they ran\n+\t *   git replay ^origin/master ^origin/next mybranch\n+\t *   git replay mybranch~2..mybranch\n+\t * the first of those because there's no unique base to choose, and\n+\t * the second because they'd likely just be replaying commits on top\n+\t * of the same commit and not making any difference.\n+\t */\n+\tfor (i = 0; i < cmd_info->nr; i++) {\n+\t\tstruct rev_cmdline_entry *e = cmd_info->rev + i;\n+\t\tstruct object_id oid;\n+\t\tconst char *refexpr = e->name;\n+\t\tchar *fullname = NULL;\n+\t\tint can_uniquely_dwim = 1;\n+\n+\t\tif (*refexpr == '^')\n+\t\t\trefexpr++;\n+\t\tif (repo_dwim_ref(the_repository, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)\n+\t\t\tcan_uniquely_dwim = 0;\n+\n+\t\tif (e->flags & BOTTOM) {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->negative_refs, fullname);\n+\t\t\tif (!ref_info->negative_refexprs)\n+\t\t\t\tref_info->onto = lookup_commit_reference_gently(the_repository,\n+\t\t\t\t\t\t\t\t\t\t&e->item->oid, 1);\n+\t\t\tref_info->negative_refexprs++;\n+\t\t} else {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->positive_refs, fullname);\n+\t\t\tref_info->positive_refexprs++;\n+\t\t}\n+\n+\t\tfree(fullname);\n+\t}\n+}\n+\n+static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n+\t\t\t\t  const char *onto_name,\n+\t\t\t\t  const char **advance_name,\n+\t\t\t\t  struct commit **onto,\n+\t\t\t\t  struct strset **update_refs)\n+{\n+\tstruct ref_info rinfo;\n+\n+\tget_ref_information(cmd_info, &rinfo);\n+\tif (!rinfo.positive_refexprs)\n+\t\tdie(_(\"need some commits to replay\"));\n+\tif (onto_name && *advance_name)\n+\t\tdie(_(\"--onto and --advance are incompatible\"));\n+\telse if (onto_name) {\n+\t\t*onto = peel_committish(onto_name);\n+\t\tif (rinfo.positive_refexprs <\n+\t\t    strset_get_size(&rinfo.positive_refs))\n+\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t} else if (*advance_name) {\n+\t\tstruct object_id oid;\n+\t\tchar *fullname = NULL;\n+\n+\t\t*onto = peel_committish(*advance_name);\n+\t\tif (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name),\n+\t\t\t     &oid, &fullname, 0) == 1) {\n+\t\t\t*advance_name = fullname;\n+\t\t} else {\n+\t\t\tdie(_(\"argument to --advance must be a reference\"));\n+\t\t}\n+\t\tif (rinfo.positive_refexprs > 1)\n+\t\t\tdie(_(\"cannot advance target with multiple sources because ordering would be ill-defined\"));\n+\t} else {\n+\t\tint positive_refs_complete = (\n+\t\t\trinfo.positive_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.positive_refs));\n+\t\tint negative_refs_complete = (\n+\t\t\trinfo.negative_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.negative_refs));\n+\t\t/*\n+\t\t * We need either positive_refs_complete or\n+\t\t * negative_refs_complete, but not both.\n+\t\t */\n+\t\tif (rinfo.negative_refexprs > 0 &&\n+\t\t    positive_refs_complete == negative_refs_complete)\n+\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\tif (negative_refs_complete) {\n+\t\t\tstruct hashmap_iter iter;\n+\t\t\tstruct strmap_entry *entry;\n+\n+\t\t\tif (rinfo.negative_refexprs == 0)\n+\t\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t\t\telse if (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\t\telse if (rinfo.positive_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\n+\t\t\t/* Only one entry, but we have to loop to get it */\n+\t\t\tstrset_for_each_entry(&rinfo.negative_refs,\n+\t\t\t\t\t      &iter, entry) {\n+\t\t\t\t*advance_name = entry->key;\n+\t\t\t}\n+\t\t} else { /* positive_refs_complete */\n+\t\t\tif (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine correct base for --onto\"));\n+\t\t\tif (rinfo.negative_refexprs == 1)\n+\t\t\t\t*onto = rinfo.onto;\n+\t\t}\n+\t}\n+\tif (!*advance_name) {\n+\t\t*update_refs = xcalloc(1, sizeof(**update_refs));\n+\t\t**update_refs = rinfo.positive_refs;\n+\t\tmemset(&rinfo.positive_refs, 0, sizeof(**update_refs));\n+\t}\n+\tstrset_clear(&rinfo.negative_refs);\n+\tstrset_clear(&rinfo.positive_refs);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n \t\t\t\t\t  struct commit *last_commit,\n \t\t\t\t\t  struct merge_options *merge_opt,\n@@ -114,20 +255,26 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n-\tstruct commit *onto;\n+\tconst char *advance_name = NULL;\n+\tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL;\n+\n \tstruct rev_info revs;\n+\tstruct commit *last_commit = NULL;\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n+\tstruct strset *update_refs = NULL;\n \tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n+\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"advance\", &advance_name,\n+\t\t\t   N_(\"branch\"),\n+\t\t\t   N_(\"make replay advance given branch\")),\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n@@ -151,13 +298,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tusage_with_options(replay_usage, replay_options);\n \t\t}\n \n-\tif (!onto_name) {\n-\t\terror(_(\"option --onto is mandatory\"));\n+\tif (!onto_name && !advance_name) {\n+\t\terror(_(\"option --onto or --advance is mandatory\"));\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tonto = peel_committish(onto_name);\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \targc = setup_revisions(argc, argv, &revs, NULL);\n@@ -182,6 +327,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n+\tdetermine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n+\t\t\t      &onto, &update_refs);\n+\n+\tif (!onto) /* FIXME: Should handle replaying down to root commit */\n+\t\tdie(\"Replaying down to root commit is not supported yet!\");\n+\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -190,6 +341,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n+\n \tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n@@ -204,12 +356,15 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Update any necessary branches */\n+\t\tif (advance_name)\n+\t\t\tcontinue;\n \t\tdecoration = get_name_decoration(&commit->object);\n \t\tif (!decoration)\n \t\t\tcontinue;\n-\n \t\twhile (decoration) {\n-\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n+\t\t\t    strset_contains(update_refs, decoration->name)) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\n@@ -219,10 +374,22 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\t/* In --advance mode, advance the target ref */\n+\tif (result.clean == 1 && advance_name) {\n+\t\tprintf(\"update %s %s %s\\n\",\n+\t\t       advance_name,\n+\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t       oid_to_hex(&onto->object.oid));\n+\t}\n+\n \tmerge_finalize(&merge_opt, &result);\n \tret = result.clean;\n \n cleanup:\n+\tif (update_refs) {\n+\t\tstrset_clear(update_refs);\n+\t\tfree(update_refs);\n+\t}\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex de6e40950e..1919f7d5d1 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -96,4 +96,38 @@ test_expect_success 'using replay on bare repo with disallowed pathspec' '\n \ttest_must_fail git -C bare replay --onto main topic1..topic2 -- A.t\n '\n \n+test_expect_success 'using replay to perform basic cherry-pick' '\n+\t# The differences between this test and previous ones are:\n+\t#   --advance vs --onto\n+\t# 2nd field of result is refs/heads/main vs. refs/heads/topic2\n+\t# 4th field of result is hash for main instead of hash for topic2\n+\n+\tgit replay --advance main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/main \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse main >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n+\tgit -C bare replay --advance main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n+test_expect_success 'replay on bare repo fails with both --advance and --onto' '\n+\ttest_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare\n+'\n+\n+test_expect_success 'replay fails when both --advance and --onto are omitted' '\n+\ttest_must_fail git replay topic1..topic2 >result\n+'\n+\n test_done\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481481","messageId":"20230907092521.733746-12-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 11/15] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:17Z","receivedAt":"2023-09-07T16:59:37Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of the fixed \"<oldbase> <branch>\" arguments, the replay\ncommand now accepts \"<revision-range>...\" arguments in a similar\nway as many other Git commands. This makes its interface more\nstandard and more flexible.\n\nAlso as the interface of the command is now mostly finalized,\nwe can add some documentation as well as testcases to make sure\nthe command will continue to work as designed in the future.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             | 90 ++++++++++++++++++++++++\n builtin/replay.c                         | 21 ++----\n t/t3650-replay-basics.sh                 | 83 ++++++++++++++++++++++\n t/t6429-merge-sequence-rename-caching.sh | 18 ++---\n 4 files changed, 186 insertions(+), 26 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100755 t/t3650-replay-basics.sh\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nnew file mode 100644\nindex 0000000000..9a2087b01a\n--- /dev/null\n+++ b/Documentation/git-replay.txt\n@@ -0,0 +1,90 @@\n+git-replay(1)\n+=============\n+\n+NAME\n+----\n+git-replay - Replay commits on a different base, without touching working tree\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git replay' --onto <newbase> <revision-range>...\n+\n+DESCRIPTION\n+-----------\n+\n+Takes a range of commits, and replays them onto a new location.  Does\n+not touch the working tree or index, and does not update any\n+references.  However, the output of this command is meant to be used\n+as input to `git update-ref --stdin`, which would update the relevant\n+branches.\n+\n+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n+\n+OPTIONS\n+-------\n+\n+--onto <newbase>::\n+\tStarting point at which to create the new commits.  May be any\n+\tvalid commit, and not just an existing branch name.\n++\n+The update-ref command(s) in the output will update the branch(es) in\n+the revision range to point at the new commits, similar to the way how\n+`git rebase --update-refs` updates multiple branches in the affected\n+range.\n+\n+<revision-range>::\n+\tRange of commits to replay; see \"Specifying Ranges\" in\n+\tlinkgit:git-rev-parse.\n+\n+OUTPUT\n+------\n+\n+When there are no conflicts, the output of this command is usable as\n+input to `git update-ref --stdin`.  It is basically of the form:\n+\n+\tupdate refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+\tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+\tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+\n+where the number of refs updated depends on the arguments passed and\n+the shape of the history being replayed.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted replay, the exit status is 0.  When\n+the replay has conflicts, the exit status is 1.  If the replay is not\n+able to complete (or start) due to some kind of error, the exit status\n+is something other than 0 or 1.\n+\n+EXAMPLES\n+--------\n+\n+To simply rebase mybranch onto target:\n+\n+------------\n+$ git replay --onto target origin/main..mybranch\n+update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n+------------\n+\n+When calling `git replay`, one does not need to specify a range of\n+commits to replay using the syntax `A..B`; any range expression will\n+do:\n+\n+------------\n+$ git replay --onto origin/main ^base branch1 branch2 branch3\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+------------\n+\n+This will simultaneously rebase branch1, branch2, and branch3 -- all\n+commits they have since base, playing them on top of origin/main.\n+These three branches may have commits on top of base that they have in\n+common, but that does not need to be the case.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex e45cd59da1..de2ddeae3e 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,7 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -118,16 +117,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL;\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 merge_result result;\n-\tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -145,20 +142,13 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tif (argc != 3) {\n-\t\terror(_(\"bad number of arguments\"));\n-\t\tusage_with_options(replay_usage, replay_options);\n-\t}\n-\n \tonto = peel_committish(onto_name);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n-\n-\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n-\t\tret = error(_(\"unhandled options\"));\n+\targc = setup_revisions(argc, argv, &revs, NULL);\n+\tif (argc > 1) {\n+\t\tret = error(_(\"unrecognized argument: %s\"), argv[1]);\n \t\tgoto cleanup;\n \t}\n \n@@ -168,8 +158,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n-\tstrvec_clear(&rev_walk_args);\n-\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -211,7 +199,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tret = result.clean;\n \n cleanup:\n-\tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nnew file mode 100755\nindex 0000000000..a1da4f9ef9\n--- /dev/null\n+++ b/t/t3650-replay-basics.sh\n@@ -0,0 +1,83 @@\n+#!/bin/sh\n+\n+test_description='basic git replay tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+GIT_AUTHOR_NAME=author@name\n+GIT_AUTHOR_EMAIL=bogus@email@address\n+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\n+\tgit switch -c topic1 &&\n+\ttest_commit C &&\n+\tgit switch -c topic2 &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit switch topic1 &&\n+\ttest_commit F &&\n+\tgit switch -c topic3 &&\n+\ttest_commit G &&\n+\ttest_commit H &&\n+\tgit switch -c topic4 main &&\n+\ttest_commit I &&\n+\ttest_commit J &&\n+\n+\tgit switch -c next main &&\n+\ttest_commit K &&\n+\tgit merge -m \"Merge topic1\" topic1 &&\n+\tgit merge -m \"Merge topic2\" topic2 &&\n+\tgit merge -m \"Merge topic3\" topic3 &&\n+\t>evil &&\n+\tgit add evil &&\n+\tgit commit --amend &&\n+\tgit merge -m \"Merge topic4\" topic4 &&\n+\n+\tgit switch main &&\n+\ttest_commit L &&\n+\ttest_commit M &&\n+\n+\tgit switch -c conflict B &&\n+\ttest_commit C.conflict C.t conflict\n+'\n+\n+test_expect_success 'setup bare' '\n+\tgit clone --bare . bare\n+'\n+\n+test_expect_success 'using replay to rebase two branches, one on top of other' '\n+\tgit replay --onto main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n+\tgit -C bare replay --onto main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n+test_expect_success 'using replay to rebase with a conflict' '\n+\ttest_expect_code 1 git replay --onto topic1 B..conflict\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase with a conflict' '\n+\ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n+'\n+\n+test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 099aefeffc..0f39ed0d08 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,7 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -201,7 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -279,7 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -357,7 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1..topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,7 +456,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -523,7 +523,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -626,7 +626,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -685,7 +685,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481483","messageId":"20230907092521.733746-1-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230602102533.876905-1-christian.couder@gmail.com","subject":"[PATCH v4 00/15] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:06Z","receivedAt":"2023-09-07T17:04:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"# Intro\n\n`git replay` has initially been developed entirely by Elijah Newren\nmostly last year (2022) at:\n\nhttps://github.com/newren/git/commits/replay\n\nI took over this year to polish and upstream it as GitLab is\ninterested in replacing libgit2, and for that purpose needs a command\nto do server side (so without using a worktree) rebases, cherry-picks\nand reverts.\n\nI reduced the number of commits and features in this first patch\nseries, compared to what Elijah already developed. Especially I\nstopped short of replaying merge commits and replaying\ninteractively. These and other features might be upstreamed in the\nfuture after this patch series has graduated.\n\nThe focus in this series is to make it a good plumbing command that\ncan already be used server side and that replaces the \"fast-rebase\"\ntest-tool command. So things to make it easier to use on the command\nline, and more advanced features (like replaying merges) are left out.\n\nIt looks like GitHub has actually already been using version 3 of this\npatch series in production with good results. See:\n\nhttps://github.blog/2023-07-27-scaling-merge-ort-across-github/\nhttps://lore.kernel.org/git/304f2a49-5e05-7655-9f87-2011606df5db@gmx.de/\n\n# Content of this cover letter\n\nThe \"Quick Overview\" and \"Reasons for diverging from cherry-pick &\nrebase\" sections just below are describing the purpose of the new\ncommand in the big scheme of things. They are taken from Elijah's\ndesign notes\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt)\nand describe what we want this command to become and the reasons for\nthat, not what the command is after only this patch series. Also these\ndesign notes were written at least one year ago, so parts of those 2\nsections are not true anymore. I have added Phillip Wood's or Felipe\nContreras' notes (thanks to them) where that's the case, but some now\nflawed parts may have missed.\n\nAfter these two sections, starting with the \"Important limitations\"\nsection, you will find sections describing what is actually in this\npatch series.\n\nMore interesting material is available in Elijah's design notes like\nan \"Intro via examples\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L37-L132),\na discussion about \"Preserving topology, replaying merges\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L264-L341)\nand a \"Current status\" section describing Elijah's work\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L344-L392)\nbefore I started working on upstreaming it.\n\nI have not included this material here though, as the documentation\nadded by this patch series for the `git replay` command already\nincludes an \"EXAMPLES\" section, and other sections of Elijah's design\nnotes might not be interesting for now. Also this cover letter is\nalready pretty long.  But reviewers can refer to the links above if\nthey think it can help.\n\n# Quick Overview (from Elijah's design notes)\n\n`git replay`, at a basic level, can perhaps be thought of as a\n\"default-to-dry-run rebase\" -- meaning no updates to the working tree,\nor to the index, or to any references.  However, it differs from\nrebase in that it:\n\n  * Works for branches that aren't checked out\n\n  * Works in a bare repository\n\n  * Can replay multiple branches simultaneously (with or without common\n    history in the range being replayed)\n\n  * Preserves relative topology by default (merges are replayed too in\n    Elijah's original work, not in this series)\n\n  * Focuses on performance\n\n  * Has several altered defaults as a result of the above\n\nI sometimes think of `git replay` as \"fast-replay\", a patch-based\nanalogue to the snapshot-based fast-export & fast-import tools.\n\n# Reasons for diverging from cherry-pick & rebase (from Elijah's\n  design notes)\n\nThere are multiple reasons to diverge from the defaults in cherry-pick and\nrebase.\n\n* Server side needs\n\n  * Both cherry-pick and rebase, via the sequencer, are heavily tied\n    to updating the working tree, index, some refs, and a lot of\n    control files with every commit replayed, and invoke a mess of\n    hooks[1] that might be hard to avoid for backward compatibility\n    reasons (at least, that's been brought up a few times on the\n    list).\n\n  * cherry-pick and rebase both fork various subprocesses\n    unnecessarily, but somewhat intrinsically in part to ensure the\n    same hooks are called that old scripted implementations would have\n    called.\n\n    Note: since 356ee4659bb (sequencer: try to commit without forking\n    'git commit', 2017-11-24) cherry-pick and rebase do not fork\n    subprocesses other than hooks for the cases covered by this patch\n    series (i.e. they do not fork \"git commit\" for simple picks).\n\n  * \"Dry run\" behavior, where there are no updates to worktree, index,\n    or even refs might be important.\n\n  * Should not assume users only want to operate on HEAD (see next\n    section)\n\n* Decapitate HEAD-centric assumptions\n\n  * cherry-pick forces commits to be played on top of HEAD;\n    inflexible.\n\n  * rebase assumes the range of commits to be replayed is\n    upstream..HEAD by default, though it allows one to replay\n    upstream..otherbranch -- but it still forcibly and needlessly\n    checks out 'otherbranch' before starting to replay things.\n\n    Note: since 767a9c417eb (rebase -i: stop checking out the tip of\n    the branch to rebase, 2020-01-24) it's not true that rebase\n    forcibly and needlessly checks out 'otherbranch'.\n\n  * Assuming HEAD is involved severely limits replaying multiple\n    (possibly divergent) branches.\n\n    Note: since 89fc0b53fdb (rebase: update refs from 'update-ref'\n    commands, 2022-07-19) the sequencer can update multiple\n    branches. The issue with divergent branch is with command line\n    arguments and the todo list generation rather than the\n    capabilities of the sequencer.\n\n  * Once you stop assuming HEAD has a certain meaning, there's not\n    much reason to have two separate commands anymore (except for the\n    funny extra not-necessarily-compatible options both have gained\n    over time).\n\n  * (Micro issue: Assuming HEAD is involved also makes it harder for\n    new users to learn what rebase means and does; it makes command\n    lines hard to parse.  Not sure I want to harp on this too much, as\n    I have a suspicion I might be creating a tool for experts with\n    complicated use cases, but it's a minor quibble.)\n\n* Performance\n\n  * jj is slaughtering us on rebase speed[2].  I would like us to become\n    competitive.  (I dropped a few comments in the link at [2] about why\n    git is currently so bad.)\n\n  * From [3], there was a simple 4-patch series in linux.git that took\n    53 seconds to rebase.  Switching to ort dropped it to 16 seconds.\n    While that sounds great, only 11 *milliseconds* were needed to do\n    the actual merges.  That means almost *all* the time (>99%) was\n    overhead!  Big offenders:\n\n    * --reapply-cherry-picks should be the default\n\n    * can_fast_forward() should be ripped out, and perhaps other extraneous\n      revision walks\n\n      Note: d42c9ffa0f (rebase: factor out branch_base calculation,\n      2022-10-17) might already deal with that (according to Felipe\n      Contreras).\n\n    * avoid updating working tree, index, refs, reflogs, and control\n      structures except when needed (e.g. hitting a conflict, or operation\n      finished)\n\n  * Other performance ideas (mostly for future work, not in this\n    series)\n\n    * single-file control structures instead of directory of files\n      (when doing interactive things which is in Elijah's original\n      work, but not in this series)\n\n    * avoid forking subprocesses unless explicitly requested (e.g.\n      --exec, --strategy, --run-hooks).  For example, definitely do not\n      invoke `git commit` or `git merge`.\n\n    * Sanitize hooks:\n\n      * dispense with all per-commit hooks for sure (pre-commit,\n        post-commit, post-checkout).\n\n      * pre-rebase also seems to assume exactly 1 ref is written, and\n        invoking it repeatedly would be stupid.  Plus, it's specific\n        to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n        rebase probably broke the pre-rebase assumptions already...)\n\n      * post-rewrite hook might make sense, but fast-import got\n        exempted, and I think of replay like a patch-based analogue\n        to the snapshot-based fast-import.\n\n    * When not running server side, resolve conflicts in a sparse-cone\n      sparse-index worktree to reduce number of files written to a\n      working tree.  (See below as well.)\n\n    * [High risk of possible premature optimization] Avoid large\n      numbers of newly created loose objects, when replaying large\n      numbers of commits.  Two possibilities: (1) Consider using\n      tmp-objdir and pack objects from the tmp-objdir at end of\n      exercise, (2) Lift code from git-fast-import to immediately\n      stuff new objects into a pack?\n\n* Multiple branches and non-checked out branches\n\n  * The ability to operate on non-checked out branches also implies\n    that we should generally be able to replay when in a dirty working\n    tree (exception being when we expect to update HEAD and any of the\n    dirty files is one that needs to be updated by the replay).\n\n  * Also, if we are operating locally on a non-checked out branch and\n    hit a conflict, we should have a way to resolve the conflict\n    without messing with the user's work on their current\n    branch. (This is not is this patch series though.)\n\n    * Idea: new worktree with sparse cone + sparse index checkout,\n      containing only files in the root directory, and whatever is\n      necessary to get the conflicts\n\n    * Companion to above idea: control structures should be written to\n      $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n      replay sessions, and so we know which worktrees are associated\n      with which replay operations.\n\n  - [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/\n  - [2] https://github.com/martinvonz/jj/discussions/49\n  - [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/\n\n# Important limitations\n\n* The code exits with code 1 if there are any conflict. No\n  resumability. No nice output. No interactivity. No special exit code\n  depending on the reason.\n\n* When a commit becomes empty as it is replayed, it is still replayed\n  as an empty commit, instead of being dropped.\n\n* No replaying merges, nor root commits. Only regular commits.\n\n* Signed commits are not properly handled. It's not clear what to do\n  to such commits when replaying on the server side.\n\n* Notes associated with replayed commits are not updated nor\n  duplicated. (Thanks to Phillip Wood for noticing.)\n\n# Commit overview\n\n* 1/15 t6429: remove switching aspects of fast-rebase\n\n    New preparatory commit to make it easier to later replace the\n    fast-rebase test-tool by `git replay` without breaking existing\n    tests.\n\n* 2/15 replay: introduce new builtin\n\n     This creates a minimal `git replay` command by moving the code\n     from the `fast-rebase` test helper from `t/helper/` into\n     `builtin/` and doing some renames and a few other needed changes.\n     (In v3 some `#include ...` were changed to deal with upstream\n     changes in this area as suggested by Junio.)\n\n* - 3/15 replay: start using parse_options API\n  - 4/15 replay: die() instead of failing assert()\n  - 5/15 replay: introduce pick_regular_commit()\n  - 6/15 replay: don't simplify history\n  - 7/15 replay: add an important FIXME comment about gpg signing\n  - 8/15 replay: remove progress and info output\n  - 9/15 replay: remove HEAD related sanity check\n\n     These slowly change the command to make it behave more like\n     regular commands and to start cleaning up its output.\n\n* 10/15 replay: make it a minimal server side command\n\n     After the cleaning up in previous commits, it's now time to\n     radically change the way it works by stopping it to do ref\n     updates, to update the index and worktree, to consider HEAD as\n     special. Instead just make it output commands that should be\n     passed to `git update-ref --stdin`.\n\n* 11/15 replay: use standard revision ranges\n\n     Start addind new interesting features and also documentation and\n     tests, as the interface of the command is cristalizing into its\n     final form.\n\n* 12/15 replay: disallow revision specific options and pathspecs\n\n     For now disallow revision specific options and pathspecs that are\n     allowed and eaten by setup_revisions(), as it's not clear if all\n     of these extra features are really needed, and anyway they would\n     require tests and doc. So we leave them for future improvements.\n     (In v3 a typo was fixed in the commit message and a code comment\n     has been improved as suggested by Elijah.)\n\n* - 13/15 replay: add --advance or 'cherry-pick' mode\n  - 14/15 replay: add --contained to rebase contained branches\n\n    Add new option and features to the command. (In v3, in commit\n    14/15, the synopsys of the command was improved and a sentence\n    about git rebase in its doc was removed as suggested by Elijah.)\n\n* 15/15 replay: stop assuming replayed branches do not diverge\n\n      This adds another interesting feature, as well as related\n      documentation and tests. (In v3 a typo in the commit message was\n      fixed as suggested by Elijah.)\n\n# Notes about `fast-rebase`, tests and documentation\n\nThe `fast-rebase` test-tool helper was developed by Elijah to\nexperiment with a rebasing tool that would be developed from scratch\nbased on his merge-ort work, could be used to test that merge-ort\nwork, and would not have the speed and interface limitations of `git\nrebase` or `git cherry-pick`.\n\nThis `fast-rebase` helper was used before this series in:\n\nt6429-merge-sequence-rename-caching.sh\n\nSo when `git replay` is created from `fast-rebase` in patch 2/15, the\nt6429 test script is also converted to use `git replay`. This ensures\nthat `git replay` doesn't break too badly during the first 10 patches\nin this patch series.\n\nTests and documentation are introduced specifically for `git replay`\nonly in 11/15 and later patches as it doesn't make much sense to\ndocument and test behavior that we know is going to change soon. So\nit's only when the command is crystalizing towards its final form that\nwe start documenting and testing it.\n\n# Possibly controversial issues \n\n* bare or not bare: this series works towards a plumbing command with\n  the end goal of it being usable and used first on bare repos,\n  contrary to existing commands like `git rebase` and `git\n  cherry-pick`. The tests check that the command works on both bare\n  and non-bare repo though.\n\n* exit status: a successful, non-conflicted replay exits with code\n  0. When the replay has conflicts, the exit status is 1. If the\n  replay is not able to complete (or start) due to some kind of error,\n  the exit status is something other than 0 or 1. There are a few\n  tests checking that. It has been suggested in an internal review\n  that conflicts might want to get a more specific error code as an\n  error code of 1 might be quite easy to return by accident. It\n  doesn't seem to me from their docs (which might want to be improved,\n  I didn't look at the code) that other commands like `git merge` and\n  `git rebase` exit with a special error code in case of conflict.\n\n* make worktree and index changes optional: commit 10/15 stops\n  updating the index and worktree, but it might be better especially\n  for cli users to make that optional. The issue is that this would\n  make the command more complex while we are developing a number of\n  important features so that the command can be used on bare repos. It\n  seems that this should rather be done in an iterative improvement\n  after the important features have landed.\n\n* when and where to add tests and docs: although t6429 has tests that\n  are changed to use the new command instead of the fast-rebase\n  test-tool command as soon as the former is introduced, there is no\n  specific test script and no doc for the new command until commit\n  11/15 when standard revision ranges are used. This is done to avoid\n  churn in tests and docs while the final form of the command hasn't\n  crystalized enough. Adding tests and doc at this point makes this\n  commit quite big and possibly more difficult to review than if they\n  were in separate commits though. On the other hand when tests and\n  docs are added in specific commits some reviewers say it would be\n  better to introduce them when the related changes are made.\n\n* --advance and --contained: these two advanced options might not\n  belong to this first series and could perhaps be added in a followup\n  series in separate commits. On the other hand the code for\n  --contained seems involved with the code of --advance and it's nice\n  to see soon that git replay can indeed do cherry-picking and rebase\n  many refs at once, and this way fullfil these parts of its promise.\n\n* replaying diverging branches: 15/15 the last patch in the series,\n  which allow replaying diverging branches, can be seen as a\n  fundamental fix or alternatively as adding an interesting\n  feature. So it's debatable if it should be in its own patch along\n  with its own tests as in this series, or if it should be merged into\n  a previous patch and which one.\n\n* only 2 patches: this patch series can be seen from a high level\n  point of view as 1) introducing the new `git replay` command, and 2)\n  using `git replay` to replace, and get rid of, the fast-rebase\n  test-tool command. The fact that not much of the original\n  fast-rebase code and interface is left would agree with that point\n  of view. On the other hand, fast-rebase can also be seen as a first\n  iteration towards `git replay`. So it can also make sense to see how\n  `git replay` evolved from it.\n\n# Changes between v3 and v4\n\nThanks to Toon, Junio and Dscho for their suggestions on the previous\nversion! The very few and minor changes compared to v3 are:\n\n* The patch series was rebased onto master at d814540bb7 (The fifth\n  batch, 2023-09-01). This is to fix a few header related conflicts as\n  can be seen in the range-diff.\n\n* In patch 10/15 (replay: make it a minimal server side command) a /*\n  Cleanup */ code comment has been removed as suggested by Toon.\n\n* In patch 11/15 (replay: use standard revision ranges) the git-replay\n  documentation related to --onto has been improved to better explain\n  which branches will be updated by the update-ref command(s) in the\n  output as suggested by Junio.\n\n* In patch 12/15 (replay: disallow revision specific options and\n  pathspecs) an error message has been improved as suggested by Junio.\n\n* In patch 13/15 (replay: add --advance or 'cherry-pick' mode) the\n  commit message and the git-replay documentation have been improved\n  to better explain that --advance only works when the revision range\n  passed has a single tip as suggested by Junio.\n\n* Also in patch 13/15 (replay: add --advance or 'cherry-pick' mode) an\n  error message has been improved, and a few tests have been added to\n  check that `git replay` fails when it's passed both --advance and\n  --onto and when it's passed none of these options, as suggested by\n  Toon.\n\n# Range-diff between v3 and v4\n\n 1:  51fa1c7aea =  1:  1eaca9b788 t6429: remove switching aspects of fast-rebase\n 2:  19f8cf1b2e !  2:  5ac4beb1ae replay: introduce new builtin\n    @@ t/helper/test-fast-rebase.c => builtin/replay.c\n      \n      #define USE_THE_INDEX_VARIABLE\n     -#include \"test-tool.h\"\n    --#include \"cache.h\"\n     +#include \"git-compat-util.h\"\n     +\n     +#include \"builtin.h\"\n 3:  295e876db6 !  3:  299381aa9b replay: start using parse_options API\n    @@ builtin/replay.c\n      #include \"lockfile.h\"\n      #include \"merge-ort.h\"\n      #include \"object-name.h\"\n    +-#include \"read-cache-ll.h\"\n     +#include \"parse-options.h\"\n      #include \"refs.h\"\n      #include \"revision.h\"\n 4:  6ece7d3751 =  4:  3b825c9be0 replay: die() instead of failing assert()\n 5:  9b4bc659fb =  5:  b1e890745d replay: introduce pick_regular_commit()\n 6:  9ab68d50ab =  6:  ec51351889 replay: don't simplify history\n 7:  37e93faafa =  7:  cd4ed07d2d replay: add an important FIXME comment about gpg signing\n 8:  03036781ed =  8:  e45a55917c replay: remove progress and info output\n 9:  4ea289952e =  9:  0587a76cbb replay: remove HEAD related sanity check\n10:  fba98eda07 ! 10:  d10368e87a replay: make it a minimal server side command\n    @@ builtin/replay.c\n     -#include \"commit.h\"\n      #include \"environment.h\"\n     -#include \"gettext.h\"\n    +-#include \"hash.h\"\n      #include \"hex.h\"\n      #include \"lockfile.h\"\n      #include \"merge-ort.h\"\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n     +          }\n        }\n      \n    -+  /* Cleanup */\n        merge_finalize(&merge_opt, &result);\n     +  ret = result.clean;\n      \n11:  03f9f20f6e ! 11:  4e09572c43 replay: use standard revision ranges\n    @@ Documentation/git-replay.txt (new)\n     +  Starting point at which to create the new commits.  May be any\n     +  valid commit, and not just an existing branch name.\n     ++\n    -+The update-ref commands in the output will update the branch(es)\n    -+in the revision range to point at the new commits (in other\n    -+words, this mimics a rebase operation).\n    ++The update-ref command(s) in the output will update the branch(es) in\n    ++the revision range to point at the new commits, similar to the way how\n    ++`git rebase --update-refs` updates multiple branches in the affected\n    ++range.\n     +\n     +<revision-range>::\n     +  Range of commits to replay; see \"Specifying Ranges\" in\n    @@ Documentation/git-replay.txt (new)\n     +  update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n     +  update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n     +\n    -+where the number of refs updated depend on the arguments passed.\n    ++where the number of refs updated depends on the arguments passed and\n    ++the shape of the history being replayed.\n     +\n     +EXIT STATUS\n     +-----------\n12:  e651250ac7 ! 12:  64b803f1cf replay: disallow revision specific options and pathspecs\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n     +   * them, after adding related tests and doc though.\n     +   */\n     +  if (revs.prune_data.nr) {\n    -+          error(_(\"invalid pathspec: %s\"), revs.prune_data.items[0].match);\n    ++          error(_(\"no pathspec is allowed: '%s'\"), revs.prune_data.items[0].match);\n     +          usage_with_options(replay_usage, replay_options);\n     +  }\n     +\n13:  56e5416dad ! 13:  04f27d81ab replay: add --advance or 'cherry-pick' mode\n    @@ Commit message\n         'cherry-pick' mode with `--advance`. This new mode will make the target\n         branch advance as we replay commits onto it.\n     \n    +    The replayed commits should have a single tip, so that it's clear where\n    +    the target branch should be advanced. If they have more than one tip,\n    +    this new mode will error out.\n    +\n         Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Elijah Newren <newren@gmail.com>\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n    @@ Documentation/git-replay.txt: OPTIONS\n        Starting point at which to create the new commits.  May be any\n        valid commit, and not just an existing branch name.\n      +\n    --The update-ref commands in the output will update the branch(es)\n    --in the revision range to point at the new commits (in other\n    --words, this mimics a rebase operation).\n    +-The update-ref command(s) in the output will update the branch(es) in\n    +-the revision range to point at the new commits, similar to the way how\n    +-`git rebase --update-refs` updates multiple branches in the affected\n    +-range.\n     +When `--onto` is specified, the update-ref command(s) in the output will\n     +update the branch(es) in the revision range to point at the new\n    -+commits (in other words, this mimics a rebase operation).\n    ++commits, similar to the way how `git rebase --update-refs` updates\n    ++multiple branches in the affected range.\n     +\n     +--advance <branch>::\n     +  Starting point at which to create the new commits; must be a\n    @@ Documentation/git-replay.txt: OPTIONS\n     +the new commits (in other words, this mimics a cherry-pick operation).\n      \n      <revision-range>::\n    -   Range of commits to replay; see \"Specifying Ranges\" in\n    +-  Range of commits to replay; see \"Specifying Ranges\" in\n    +-  linkgit:git-rev-parse.\n    ++  Range of commits to replay. More than one <revision-range> can\n    ++  be passed, but in `--advance <branch>` mode, they should have\n    ++  a single tip, so that it's clear where <branch> should point\n    ++  to. See \"Specifying Ranges\" in linkgit:git-rev-parse.\n    + \n    + OUTPUT\n    + ------\n     @@ Documentation/git-replay.txt: input to `git update-ref --stdin`.  It is basically of the form:\n    -   update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n        update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n      \n    --where the number of refs updated depend on the arguments passed.\n    -+where the number of refs updated depend on the arguments passed.  When\n    -+using `--advance`, the number of refs updated is always one, but for\n    -+`--onto`, it can be one or more (rebasing multiple branches\n    -+simultaneously is supported).\n    + where the number of refs updated depends on the arguments passed and\n    +-the shape of the history being replayed.\n    ++the shape of the history being replayed.  When using `--advance`, the\n    ++number of refs updated is always one, but for `--onto`, it can be one\n    ++or more (rebasing multiple branches simultaneously is supported).\n      \n      EXIT STATUS\n      -----------\n    @@ builtin/replay.c: static struct commit *create_commit(struct tree *tree,\n     +                  die(_(\"argument to --advance must be a reference\"));\n     +          }\n     +          if (rinfo.positive_refexprs > 1)\n    -+                  die(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n    ++                  die(_(\"cannot advance target with multiple sources because ordering would be ill-defined\"));\n     +  } else {\n     +          int positive_refs_complete = (\n     +                  rinfo.positive_refexprs ==\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n     +                 oid_to_hex(&onto->object.oid));\n     +  }\n     +\n    -   /* Cleanup */\n        merge_finalize(&merge_opt, &result);\n        ret = result.clean;\n      \n    @@ t/t3650-replay-basics.sh: test_expect_success 'using replay on bare repo with di\n     +  git -C bare replay --advance main topic1..topic2 >result-bare &&\n     +  test_cmp expect result-bare\n     +'\n    ++\n    ++test_expect_success 'replay on bare repo fails with both --advance and --onto' '\n    ++  test_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare\n    ++'\n    ++\n    ++test_expect_success 'replay fails when both --advance and --onto are omitted' '\n    ++  test_must_fail git replay topic1..topic2 >result\n    ++'\n     +\n      test_done\n14:  2cc17dfdc7 ! 14:  9ed0919ad5 replay: add --contained to rebase contained branches\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n                                       oid_to_hex(&last_commit->object.oid),\n     \n      ## t/t3650-replay-basics.sh ##\n    -@@ t/t3650-replay-basics.sh: test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n    -   test_cmp expect result-bare\n    +@@ t/t3650-replay-basics.sh: test_expect_success 'replay fails when both --advance and --onto are omitted' '\n    +   test_must_fail git replay topic1..topic2 >result\n      '\n      \n     +test_expect_success 'using replay to also rebase a contained branch' '\n15:  a6d88fc8f0 ! 15:  cf8c984877 replay: stop assuming replayed branches do not diverge\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n                if (advance_name)\n                        continue;\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    +   }\n      \n    -   /* Cleanup */\n        merge_finalize(&merge_opt, &result);\n     -  ret = result.clean;\n     -\n\n\nChristian Couder (1):\n  replay: disallow revision specific options and pathspecs\n\nElijah Newren (14):\n  t6429: remove switching aspects of fast-rebase\n  replay: introduce new builtin\n  replay: start using parse_options API\n  replay: die() instead of failing assert()\n  replay: introduce pick_regular_commit()\n  replay: don't simplify history\n  replay: add an important FIXME comment about gpg signing\n  replay: remove progress and info output\n  replay: remove HEAD related sanity check\n  replay: make it a minimal server side command\n  replay: use standard revision ranges\n  replay: add --advance or 'cherry-pick' mode\n  replay: add --contained to rebase contained branches\n  replay: stop assuming replayed branches do not diverge\n\n .gitignore                               |   1 +\n Documentation/git-replay.txt             | 124 +++++++\n Makefile                                 |   2 +-\n builtin.h                                |   1 +\n builtin/replay.c                         | 431 +++++++++++++++++++++++\n command-list.txt                         |   1 +\n git.c                                    |   1 +\n t/helper/test-fast-rebase.c              | 241 -------------\n t/helper/test-tool.c                     |   1 -\n t/helper/test-tool.h                     |   1 -\n t/t3650-replay-basics.sh                 | 214 +++++++++++\n t/t6429-merge-sequence-rename-caching.sh |  45 +--\n 12 files changed, 799 insertions(+), 264 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100644 builtin/replay.c\n delete mode 100644 t/helper/test-fast-rebase.c\n create mode 100755 t/t3650-replay-basics.sh\n\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481484","messageId":"20230907092521.733746-9-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 08/15] replay: remove progress and info output","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:14Z","receivedAt":"2023-09-07T17:13:31Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command will be changed in a follow up commit, so that it\nwill not update refs directly, but instead it will print on stdout a\nlist of commands that can be consumed by `git update-ref --stdin`.\n\nWe don't want this output to be polluted by its current low value\noutput, so let's just remove the latter.\n\nIn the future, when the command gets an option to update refs by\nitself, it will make a lot of sense to display a progress meter, but\nwe are not there yet.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 +------\n 1 file changed, 1 insertion(+), 6 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 47d695df93..b5c854c686 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -195,7 +195,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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.show_rename_progress = 0;\n \tmerge_opt.branch1 = \"HEAD\";\n \thead_tree = repo_get_commit_tree(the_repository, onto);\n \tresult.tree = head_tree;\n@@ -203,9 +203,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \twhile ((commit = get_revision(&revs))) {\n \t\tstruct commit *pick;\n \n-\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n-\t\t\toid_to_hex(&commit->object.oid));\n-\n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n@@ -224,7 +221,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\texit(128);\n \n \tif (result.clean) {\n-\t\tfprintf(stderr, \"\\nDone.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n \t\t\t    oid_to_hex(&last_commit->object.oid));\n@@ -238,7 +234,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t} else {\n-\t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481485","messageId":"20230907092521.733746-4-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 03/15] replay: start using parse_options API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:09Z","receivedAt":"2023-09-07T17:18:45Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of manually parsing arguments, let's start using the parse_options\nAPI. This way this new builtin will look more standard, and in some\nupcoming commits will more easily be able to handle more command line\noptions.\n\nNote that we plan to later use standard revision ranges instead of\nhardcoded \"<oldbase> <branch>\" arguments. When we will use standard\nrevision ranges, it will be easier to check if there are no spurious\narguments if we keep ARGV[0], so let's call parse_options() with\nPARSE_OPT_KEEP_ARGV0 even if we don't need ARGV[0] right now to avoid\nsome useless code churn.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 45 ++++++++++++++++++++++++++++++++-------------\n 1 file changed, 32 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex e102749ab6..d6dec7c866 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -15,7 +15,7 @@\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n #include \"object-name.h\"\n-#include \"read-cache-ll.h\"\n+#include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n #include \"sequencer.h\"\n@@ -92,6 +92,7 @@ static struct commit *create_commit(struct tree *tree,\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n+\tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n \tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n@@ -105,16 +106,32 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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+\tconst char * const replay_usage[] = {\n+\t\tN_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tNULL\n+\t};\n+\tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"onto\", &onto_name,\n+\t\t\t   N_(\"revision\"),\n+\t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n+\n+\tif (!onto_name) {\n+\t\terror(_(\"option --onto is mandatory\"));\n+\t\tusage_with_options(replay_usage, replay_options);\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+\tif (argc != 3) {\n+\t\terror(_(\"bad number of arguments\"));\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n \n-\tonto = peel_committish(argv[2]);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\tonto = peel_committish(onto_name);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \t/* Sanity check */\n \tif (repo_get_oid(the_repository, \"HEAD\", &head))\n@@ -126,6 +143,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tBUG(\"Could not read index\");\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n+\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\n@@ -134,7 +152,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n@@ -197,8 +216,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &last_picked_commit->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -210,8 +229,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &head,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t}\n \tret = (result.clean == 0);\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481487","messageId":"20230907092521.733746-10-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 09/15] replay: remove HEAD related sanity check","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:15Z","receivedAt":"2023-09-07T17:25:12Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want replay to be a command that can be used on the server side on\nany branch, not just the current one, so we are going to stop updating\nHEAD in a future commit.\n\nA \"sanity check\" that makes sure we are replaying the current branch\ndoesn't make sense anymore. Let's remove it.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 8 +-------\n 1 file changed, 1 insertion(+), 7 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex b5c854c686..a2636fbdcc 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -123,7 +123,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct strvec rev_walk_args = STRVEC_INIT;\n \tstruct rev_info revs;\n@@ -162,11 +161,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\t/* Sanity check */\n-\tif (repo_get_oid(the_repository, \"HEAD\", &head))\n-\t\tdie(_(\"Cannot read HEAD\"));\n-\tassert(oideq(&onto->object.oid, &head));\n-\n \trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n@@ -238,7 +232,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n \t\t\t       &last_commit->object.oid,\n-\t\t\t       &head,\n+\t\t\t       &onto->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n \t\t\terror(_(\"could not update %s\"), argv[2]);\n \t\t\tdie(\"Failed to update %s\", argv[2]);\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481488","messageId":"CAP8UFD2KMikjJJQ0FGXO1EYYHRNWHH2n70UATU_o61PJ411f2Q@mail.gmail.com","threadId":"59560","inReplyTo":"f74fb509-0e1a-9542-d80c-0bec2a1e6740@gmx.de","subject":"Re: [PATCH 11/14] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T08:39:36Z","receivedAt":"2023-09-07T17:26:16Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Dscho,\n\nOn Sun, Sep 3, 2023 at 5:47 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah & Stolee,\n>\n> On Sat, 29 Apr 2023, Elijah Newren wrote:\n>\n> > On Mon, Apr 24, 2023 at 8:23 AM Derrick Stolee <derrickstolee@github.com> wrote:\n\n> > > Basically, I'm not super thrilled about exposing options that are\n> > > unlikely to be valuable to users and instead are more likely to cause\n> > > confusion due to changes that won't successfully apply.\n> >\n> > Oh, I got thrown by the \"right now\" portion of your comment; I\n> > couldn't see how time or future changes would affect anything to make\n> > it less (or more) confusing for users.\n> >\n> > Quick clarification, though: while you correctly point out the type of\n> > confusion the user would experience without my overriding, my\n> > overriding of rev.reverse (after setup_revisions() returns, not before\n> > it is called) precludes that experience.  The override means none of\n> > the above happens, and they would instead just wonder why their option\n> > is being ignored.\n>\n> FWIW here is my view on the matter: `git replay`, at least in its current\n> incarnation, is a really low-level tool. As such, I actually do not want\n> to worry much about protecting users from nonsensical invocations.\n>\n> In that light, I would like to see that code rejecting all revision\n> options except `--diff-algorithm` be dropped. Should we ever decide to add\n> a non-low-level mode to `git replay`, we can easily add some user-friendly\n> sanity check of the options then, and only for that non-low-level code.\n> For now, I feel that it's just complicating things, and `git replay` is in\n> the experimental phase anyway.\n\nI would be Ok with removing the patch (called \"replay: disallow\nrevision specific options and pathspecs\")\nthat rejects all revision options and pathspecs if there is a\nconsensus for that. It might not simplify things too much if there is\nstill an exception for `--diff-algorithm` though. Also it's not clear\nif you are Ok with allowing pathspecs or not.\n\nThe idea with disallowing all of them was to later add back those that\nmake sense along with tests and maybe docs to explain them in the\ncontext of this command. It was not to disallow them permanently. So I\nwould think the best path forward would be a patch series on top of\nthis one that would revert the patch disallowing these options and\nmaybe pathspecs, and instead allow most of them and document and test\nthings a bit.\n\n> And further, I would even like to see that `--reverse` override go, and\n> turn it into `revs.reverse = !revs.reverse` instead. (And yes, I can\n> easily think of instances where I would have wanted to reverse a series of\n> patches...).\n\nI think this might deserve docs and tests too, so it might want to be\npart of a separate patch series once the existing one has graduated.\n\nAt this point I don't think it's worth delaying this patch series for\nrelatively small issues like this. There are many different ways this\nnew command can be polished and improved. The important thing is that\nit looks like we all agree that the new command makes sense and should\nhave roughly the basic set of features that Elijah originally\nimplemented, so let's go with this, and then we can improve and\niterate on top of this.\n"},{"id":"481491","messageId":"d07487c2-4ee6-3ffa-014d-418793a5a584@gmx.de","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"Re: [PATCH v4 00/15] Introduce new `git replay` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-09-07T10:25:55Z","receivedAt":"2023-09-07T17:50:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nhope you had a restful vacation!\n\nOn Thu, 7 Sep 2023, Christian Couder wrote:\n\n> # Changes between v3 and v4\n>\n> Thanks to Toon, Junio and Dscho for their suggestions on the previous\n> version! The very few and minor changes compared to v3 are:\n>\n> * The patch series was rebased onto master at d814540bb7 (The fifth\n>   batch, 2023-09-01). This is to fix a few header related conflicts as\n>   can be seen in the range-diff.\n>\n> * In patch 10/15 (replay: make it a minimal server side command) a /*\n>   Cleanup */ code comment has been removed as suggested by Toon.\n>\n> * In patch 11/15 (replay: use standard revision ranges) the git-replay\n>   documentation related to --onto has been improved to better explain\n>   which branches will be updated by the update-ref command(s) in the\n>   output as suggested by Junio.\n>\n> * In patch 12/15 (replay: disallow revision specific options and\n>   pathspecs) an error message has been improved as suggested by Junio.\n>\n> * In patch 13/15 (replay: add --advance or 'cherry-pick' mode) the\n>   commit message and the git-replay documentation have been improved\n>   to better explain that --advance only works when the revision range\n>   passed has a single tip as suggested by Junio.\n>\n> * Also in patch 13/15 (replay: add --advance or 'cherry-pick' mode) an\n>   error message has been improved, and a few tests have been added to\n>   check that `git replay` fails when it's passed both --advance and\n>   --onto and when it's passed none of these options, as suggested by\n>   Toon.\n\nI left a bit of feedback and think that once my concerns are addressed, a\nv5 will be ready for `next`.\n\nCiao,\nJohannes\n"},{"id":"481492","messageId":"CAP8UFD0iUGutbN7eLV2kmztXn5SjXMv47B7PTScE5d6sQoKgHw@mail.gmail.com","threadId":"59560","inReplyTo":"xmqqy1j3itdw.fsf@gitster.g","subject":"Re: [PATCH v3 12/15] replay: disallow revision specific options and pathspecs","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T08:33:19Z","receivedAt":"2023-09-07T17:51:33Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Jul 25, 2023 at 11:16 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > +     /*\n> > +      * Reject any pathspec. (They are allowed and eaten by\n> > +      * setup_revisions() above.) In the future we might accept\n> > +      * them, after adding related tests and doc though.\n> > +      */\n> > +     if (revs.prune_data.nr) {\n> > +             error(_(\"invalid pathspec: %s\"), revs.prune_data.items[0].match);\n>\n> This made me waste a few minutes wondering if and how I misspelt my\n> pathspec elements.  If we mean \"no pathspec is allowed\", we should\n> say so instead.\n\nYeah, right. I have changed this to:\n\n            error(_(\"no pathspec is allowed: '%s'\"),\nrevs.prune_data.items[0].match);\n\n> > +             usage_with_options(replay_usage, replay_options);\n> > +     }\n"},{"id":"481493","messageId":"03460733-0219-c648-5757-db1958f8042e@gmx.de","threadId":"59560","inReplyTo":"20230907092521.733746-12-christian.couder@gmail.com","subject":"Re: [PATCH v4 11/15] replay: use standard revision ranges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-09-07T10:24:04Z","receivedAt":"2023-09-07T17:53:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nIt is a bit surprising to see the manual page added in _this_ patch, in\nthe middle of the series... I can live with it, though.\n\nOn Thu, 7 Sep 2023, Christian Couder wrote:\n\n> diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\n> new file mode 100644\n> index 0000000000..9a2087b01a\n> --- /dev/null\n> +++ b/Documentation/git-replay.txt\n> @@ -0,0 +1,90 @@\n> +git-replay(1)\n> +=============\n> +\n> +NAME\n> +----\n> +git-replay - Replay commits on a different base, without touching working tree\n> +\n> +\n> +SYNOPSIS\n> +--------\n> +[verse]\n> +'git replay' --onto <newbase> <revision-range>...\n\nWe need to make it clear here, already in the SYNOPSIS, that this is\nexperimental. Let's add an `(EXPERIMENTAL!)` prefix here.\n\n> [...]\n> diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\n> new file mode 100755\n\nJust like the manual page, I would have expected this test to be\nintroduced earlier, and not piggy-backed onto one of the handful \"let's\nturn fast-rebase into replay\" patches.\n\nCiao,\nJohannes\n"},{"id":"481495","messageId":"20230907092521.733746-6-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 05/15] replay: introduce pick_regular_commit()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:11Z","receivedAt":"2023-09-07T18:08:16Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's refactor the code to handle a regular commit (a commit that is\nneither a root commit nor a merge commit) into a single function instead\nof keeping it inside cmd_replay().\n\nThis is good for separation of concerns, and this will help further work\nin the future to replay merge commits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 54 ++++++++++++++++++++++++++++++------------------\n 1 file changed, 34 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex f3fdbe48c9..c66888679b 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -89,6 +89,35 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+static struct commit *pick_regular_commit(struct commit *pickme,\n+\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  struct merge_options *merge_opt,\n+\t\t\t\t\t  struct merge_result *result)\n+{\n+\tstruct commit *base;\n+\tstruct tree *pickme_tree, *base_tree;\n+\n+\tbase = pickme->parents->item;\n+\n+\tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n+\tbase_tree = repo_get_commit_tree(the_repository, base);\n+\n+\tmerge_opt->branch2 = short_commit_name(pickme);\n+\tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n+\n+\tmerge_incore_nonrecursive(merge_opt,\n+\t\t\t\t  base_tree,\n+\t\t\t\t  result->tree,\n+\t\t\t\t  pickme_tree,\n+\t\t\t\t  result);\n+\n+\tfree((char*)merge_opt->ancestor);\n+\tmerge_opt->ancestor = NULL;\n+\tif (!result->clean)\n+\t\treturn NULL;\n+\treturn create_commit(result->tree, pickme, last_commit);\n+}\n+\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n@@ -100,7 +129,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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 tree *head_tree;\n \tstruct merge_result result;\n \tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n@@ -175,7 +204,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tresult.tree = head_tree;\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *base;\n+\t\tstruct commit *pick;\n \n \t\tfprintf(stderr, \"Rebasing %s...\\r\",\n \t\t\toid_to_hex(&commit->object.oid));\n@@ -185,26 +214,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tbase = commit->parents->item;\n-\n-\t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-\t\tbase_tree = repo_get_commit_tree(the_repository, 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\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!pick)\n \t\t\tbreak;\n+\t\tlast_commit = pick;\n \t\tlast_picked_commit = commit;\n-\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481496","messageId":"20230907092521.733746-3-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 02/15] replay: introduce new builtin","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:08Z","receivedAt":"2023-09-07T18:17:14Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor now, this is just a rename from `t/helper/test-fast-rebase.c` into\n`builtin/replay.c` with minimal changes to make it build appropriately.\n\nSubsequent commits will flesh out its capabilities and make it a more\nstandard regular builtin.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n .gitignore                                    |  1 +\n Makefile                                      |  2 +-\n builtin.h                                     |  1 +\n .../test-fast-rebase.c => builtin/replay.c    | 27 ++++++-------------\n command-list.txt                              |  1 +\n git.c                                         |  1 +\n t/helper/test-tool.c                          |  1 -\n t/helper/test-tool.h                          |  1 -\n t/t6429-merge-sequence-rename-caching.sh      | 27 +++++++------------\n 9 files changed, 22 insertions(+), 40 deletions(-)\n rename t/helper/test-fast-rebase.c => builtin/replay.c (89%)\n\ndiff --git a/.gitignore b/.gitignore\nindex 5e56e471b3..612c0f6a0f 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -135,6 +135,7 @@\n /git-remote-ext\n /git-repack\n /git-replace\n+/git-replay\n /git-request-pull\n /git-rerere\n /git-reset\ndiff --git a/Makefile b/Makefile\nindex 5776309365..05a504dc28 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -799,7 +799,6 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o\n TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n TEST_BUILTINS_OBJS += test-env-helper.o\n TEST_BUILTINS_OBJS += test-example-decorate.o\n-TEST_BUILTINS_OBJS += test-fast-rebase.o\n TEST_BUILTINS_OBJS += test-fsmonitor-client.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n TEST_BUILTINS_OBJS += test-genzeros.o\n@@ -1287,6 +1286,7 @@ BUILTIN_OBJS += builtin/remote-fd.o\n BUILTIN_OBJS += builtin/remote.o\n BUILTIN_OBJS += builtin/repack.o\n BUILTIN_OBJS += builtin/replace.o\n+BUILTIN_OBJS += builtin/replay.o\n BUILTIN_OBJS += builtin/rerere.o\n BUILTIN_OBJS += builtin/reset.o\n BUILTIN_OBJS += builtin/rev-list.o\ndiff --git a/builtin.h b/builtin.h\nindex d560baa661..28280636da 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -211,6 +211,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix);\n int cmd_remote_ext(int argc, const char **argv, const char *prefix);\n int cmd_remote_fd(int argc, const char **argv, const char *prefix);\n int cmd_repack(int argc, const char **argv, const char *prefix);\n+int cmd_replay(int argc, const char **argv, const char *prefix);\n int cmd_rerere(int argc, const char **argv, const char *prefix);\n int cmd_reset(int argc, const char **argv, const char *prefix);\n int cmd_restore(int argc, const char **argv, const char *prefix);\ndiff --git a/t/helper/test-fast-rebase.c b/builtin/replay.c\nsimilarity index 89%\nrename from t/helper/test-fast-rebase.c\nrename to builtin/replay.c\nindex 2bfab66b1b..e102749ab6 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/builtin/replay.c\n@@ -1,17 +1,11 @@\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+ * \"git replay\" builtin command\n  */\n \n #define USE_THE_INDEX_VARIABLE\n-#include \"test-tool.h\"\n+#include \"git-compat-util.h\"\n+\n+#include \"builtin.h\"\n #include \"cache-tree.h\"\n #include \"commit.h\"\n #include \"environment.h\"\n@@ -27,7 +21,8 @@\n #include \"sequencer.h\"\n #include \"setup.h\"\n #include \"strvec.h\"\n-#include \"tree.h\"\n+#include <oidset.h>\n+#include <tree.h>\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -94,7 +89,7 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n-int cmd__fast_rebase(int argc, const char **argv)\n+int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n@@ -110,12 +105,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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@@ -136,7 +125,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n \n-\trepo_init_revisions(the_repository, &revs, NULL);\n+\trepo_init_revisions(the_repository, &revs, prefix);\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\ndiff --git a/command-list.txt b/command-list.txt\nindex 54b2a50f5f..d74836ab21 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -160,6 +160,7 @@ git-reflog                              ancillarymanipulators           complete\n git-remote                              ancillarymanipulators           complete\n git-repack                              ancillarymanipulators           complete\n git-replace                             ancillarymanipulators           complete\n+git-replay                              mainporcelain           history\n git-request-pull                        foreignscminterface             complete\n git-rerere                              ancillaryinterrogators\n git-reset                               mainporcelain           history\ndiff --git a/git.c b/git.c\nindex c67e44dd82..7068a184b0 100644\n--- a/git.c\n+++ b/git.c\n@@ -594,6 +594,7 @@ static struct cmd_struct commands[] = {\n \t{ \"remote-fd\", cmd_remote_fd, NO_PARSEOPT },\n \t{ \"repack\", cmd_repack, RUN_SETUP },\n \t{ \"replace\", cmd_replace, RUN_SETUP },\n+\t{ \"replay\", cmd_replay, RUN_SETUP },\n \t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t{ \"reset\", cmd_reset, RUN_SETUP },\n \t{ \"restore\", cmd_restore, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex abe8a785eb..9ca1586de7 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -30,7 +30,6 @@ static struct test_cmd cmds[] = {\n \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n \t{ \"env-helper\", cmd__env_helper },\n \t{ \"example-decorate\", cmd__example_decorate },\n-\t{ \"fast-rebase\", cmd__fast_rebase },\n \t{ \"fsmonitor-client\", cmd__fsmonitor_client },\n \t{ \"genrandom\", cmd__genrandom },\n \t{ \"genzeros\", cmd__genzeros },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ea2672436c..a03bbfc6b2 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -24,7 +24,6 @@ int cmd__dump_untracked_cache(int argc, const char **argv);\n int cmd__dump_reftable(int argc, const char **argv);\n int cmd__env_helper(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__fsmonitor_client(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n int cmd__genzeros(int argc, const char **argv);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 75d3fd2dba..7670b72008 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,9 +71,8 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -141,8 +140,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -200,9 +198,8 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -278,9 +275,8 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -356,8 +352,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,9 +451,8 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -523,9 +517,8 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -626,9 +619,8 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -685,9 +677,8 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481498","messageId":"20230907092521.733746-16-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v4 15/15] replay: stop assuming replayed branches do not diverge","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-09-07T09:25:21Z","receivedAt":"2023-09-07T18:20:50Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command is able to replay multiple branches but when some of\nthem are based on other replayed branches, their commit should be\nreplayed onto already replayed commits.\n\nFor this purpose, let's store the replayed commit and its original\ncommit in a key value store, so that we can easily find and reuse a\nreplayed commit instead of the original one.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 44 ++++++++++++++++++++++++++--------\n t/t3650-replay-basics.sh | 52 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 86 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex a7d36a639c..5479406863 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -223,20 +223,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n \tstrset_clear(&rinfo.positive_refs);\n }\n \n+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,\n+\t\t\t\t    struct commit *commit,\n+\t\t\t\t    struct commit *fallback)\n+{\n+\tkhint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);\n+\tif (pos == kh_end(replayed_commits))\n+\t\treturn fallback;\n+\treturn kh_value(replayed_commits, pos);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n-\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  kh_oid_map_t *replayed_commits,\n+\t\t\t\t\t  struct commit *onto,\n \t\t\t\t\t  struct merge_options *merge_opt,\n \t\t\t\t\t  struct merge_result *result)\n {\n-\tstruct commit *base;\n+\tstruct commit *base, *replayed_base;\n \tstruct tree *pickme_tree, *base_tree;\n \n \tbase = pickme->parents->item;\n+\treplayed_base = mapped_commit(replayed_commits, base, onto);\n \n+\tresult->tree = repo_get_commit_tree(the_repository, replayed_base);\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n-\tmerge_opt->branch1 = short_commit_name(last_commit);\n+\tmerge_opt->branch1 = short_commit_name(replayed_base);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -250,7 +263,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tmerge_opt->ancestor = NULL;\n \tif (!result->clean)\n \t\treturn NULL;\n-\treturn create_commit(result->tree, pickme, last_commit);\n+\treturn create_commit(result->tree, pickme, replayed_base);\n }\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n@@ -266,6 +279,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n \tstruct strset *update_refs = NULL;\n+\tkh_oid_map_t *replayed_commits;\n \tint ret = 0, i;\n \n \tconst char * const replay_usage[] = {\n@@ -348,21 +362,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\n-\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n+\treplayed_commits = kh_init_oid_map();\n \twhile ((commit = get_revision(&revs))) {\n \t\tconst struct name_decoration *decoration;\n+\t\tkhint_t pos;\n+\t\tint hr;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tlast_commit = pick_regular_commit(commit, replayed_commits, onto,\n+\t\t\t\t\t\t  &merge_opt, &result);\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Record commit -> last_commit mapping */\n+\t\tpos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);\n+\t\tif (hr == 0)\n+\t\t\tBUG(\"Duplicate rewritten commit: %s\\n\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tkh_value(replayed_commits, pos) = last_commit;\n+\n \t\t/* Update any necessary branches */\n \t\tif (advance_name)\n \t\t\tcontinue;\n@@ -391,13 +414,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-\tret = result.clean;\n-\n-cleanup:\n+\tkh_destroy_oid_map(replayed_commits);\n \tif (update_refs) {\n \t\tstrset_clear(update_refs);\n \t\tfree(update_refs);\n \t}\n+\tret = result.clean;\n+\n+cleanup:\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 57d2ef9ea4..fe5ae0391d 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -159,4 +159,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to rebase multiple divergent branches' '\n+\tgit replay --onto main ^topic1 topic2 topic4 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines J I M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\tprintf \"update refs/heads/topic4 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic4 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '\n+\tgit -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&\n+\n+\ttest_line_count = 4 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\t>expect &&\n+\tfor i in 2 1 3 4\n+\tdo\n+\t\tprintf \"update refs/heads/topic$i \" >>expect &&\n+\t\tprintf \"%s \" $(grep topic$i result | cut -f 3 -d \" \") >>expect &&\n+\t\tgit -C bare rev-parse topic$i >>expect || return 1\n+\tdone &&\n+\n+\ttest_cmp expect result &&\n+\n+\ttest_write_lines F C M L B A >expect1 &&\n+\ttest_write_lines E D C M L B A >expect2 &&\n+\ttest_write_lines H G F C M L B A >expect3 &&\n+\ttest_write_lines J I M L B A >expect4 &&\n+\n+\tfor i in 1 2 3 4\n+\tdo\n+\t\tgit -C bare log --format=%s $(grep topic$i result | cut -f 3 -d \" \") >actual &&\n+\t\ttest_cmp expect$i actual || return 1\n+\tdone\n+'\n+\n test_done\n-- \n2.42.0.126.gcf8c984877\n\n"},{"id":"481521","messageId":"52277471-4ddd-b2e0-62ca-c2a5b59ae418@gmx.de","threadId":"59560","inReplyTo":"20230907092521.733746-3-christian.couder@gmail.com","subject":"Re: [PATCH v4 02/15] replay: introduce new builtin","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-09-07T10:23:04Z","receivedAt":"2023-09-07T20:55:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian & Elijah,\n\nOn Thu, 7 Sep 2023, Christian Couder wrote:\n\n> diff --git a/command-list.txt b/command-list.txt\n> index 54b2a50f5f..d74836ab21 100644\n> --- a/command-list.txt\n> +++ b/command-list.txt\n> @@ -160,6 +160,7 @@ git-reflog                              ancillarymanipulators           complete\n>  git-remote                              ancillarymanipulators           complete\n>  git-repack                              ancillarymanipulators           complete\n>  git-replace                             ancillarymanipulators           complete\n> +git-replay                              mainporcelain           history\n\nI recall this having come up before, but in light of the still active\ndiscussion revolving around command-line parameter validation, I would\nstrongly advise removing `git replay` from the main page and instead go\nfor plumbingmanipulators.\n\nCiao,\nJohannes\n"},{"id":"481522","messageId":"f412f62bf830b38a296b59ac3470099a@manjaro.org","threadId":"59560","inReplyTo":"CAP8UFD1_67rNke1y-GV7qX46GXQkNJzw7fXfK=kkUjc-jb5S_g@mail.gmail.com","subject":"Re: [PATCH v3 11/15] replay: use standard revision ranges","fromName":"Dragan Simic","fromEmail":"dsimic@manjaro.org","sentAt":"2023-09-07T21:02:07Z","receivedAt":"2023-09-07T21:02:16Z","isPatch":true,"sender":{"key":"dsimic@manjaro.org","avatar":null},"body":"On 2023-09-07 10:32, Christian Couder wrote:\n> On Thu, Jun 22, 2023 at 12:03 PM Toon Claes <toon@iotcl.com> wrote:\n>> \n>> Christian Couder <christian.couder@gmail.com> writes:\n>> \n>> > +DESCRIPTION\n>> > +-----------\n>> > +\n>> > +Takes a range of commits, and replays them onto a new location.  Does\n>> > +not touch the working tree or index, and does not update any\n>> > +references.  However, the output of this command is meant to be used\n>> \n>> Small suggestion here:\n>> \n>> Takes a range of commits, and replays them onto a new location.  Does\n>> neither touch the working tree nor index, and does not update any\n>> references.\n> \n> I am not a native speaker, so I am not sure what's best here. I find\n> your suggestion a bit less clear though, so until a native speaker\n> agrees with it or maybe finds something even better, I prefer to leave\n> it as-is.\n\nI'm also not a native English speaker, but I spent about 2.5 years \ncontributing a whole lot to English Wikipedia, so I'd dare to say I've \nhoned my English skills rather well.  Thus, here's my take on this:\n\n     Takes a range of commits and replays them onto a new location.\n     Leaves the working tree and the index untouched, and updates no\n     references.  The output of this command is to be used...\n\nThis is written in a concise yet slightly imperative way, which should \nbe suitable for the purpose.  I hope you agree.\n"},{"id":"481581","messageId":"owlyo7icl1g3.fsf@fine.c.googlers.com","threadId":"59560","inReplyTo":"20230907092521.733746-12-christian.couder@gmail.com","subject":"Re: [PATCH v4 11/15] replay: use standard revision ranges","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2023-09-08T22:55:40Z","receivedAt":"2023-09-08T22:55:45Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Hi Christian,\n\nI am only reviewing the docs. To assume the mindset of a Git user\nunfamiliar with this command, I purposely did not read the cover letter\nuntil after this review was done.\n\nChristian Couder <christian.couder@gmail.com> writes:\n\n> [...]\n>\n> diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\n> new file mode 100644\n> index 0000000000..9a2087b01a\n> --- /dev/null\n> +++ b/Documentation/git-replay.txt\n> @@ -0,0 +1,90 @@\n> +git-replay(1)\n> +=============\n> +\n> +NAME\n> +----\n> +git-replay - Replay commits on a different base, without touching working tree\n\nHow about using the same language (\"new location\") as in the DESCRIPTION\nheading? Also, the \"without touching working tree\" part is incomplete\nbecause as explained later on, the index and refs are also left alone.\nHow about just \"safely\"?\n\n    git-replay - Replay commits onto a new location, safely\n\n> +SYNOPSIS\n> +--------\n> +[verse]\n> +'git replay' --onto <newbase> <revision-range>...\n> +\n> +DESCRIPTION\n> +-----------\n> +\n> +Takes a range of commits, and replays them onto a new location.\n\nOK.\n\n> Does\n> +not touch the working tree or index, and does not update any\n> +references.\n\nHow about this version?\n\n    The new commits are created without modifying the working tree,\n    index, or any references.\n\nAlso, by \"references\" we mean the refs in \".git/refs/*\". In the\ngitrevisions man page we use the term \"refnames\" to refer to these bits,\nso maybe \"refnames\" is better than \"references\"? The simpler \"branches\"\nis another option.\n\n> However, the output of this command is meant to be used\n> +as input to `git update-ref --stdin`, which would update the relevant\n> +branches.\n\nBefore we get to this sentence, it would be great to explain why this\ncommand is useful (what problem does it solve)?\n\nAlso, it would help to add \"(see OUTPUT section below)\" as a\nnavigational aid in case some readers are wondering what the output\nlooks like (and have not yet gotten to that section).\n\nI've noticed that you're using the phrase \"starting point\" in the\nOPTIONS section. I think this is better than \"location\" or \"base\" (FWIW\nwe started using \"starting point\" in 0a02ca2383 (SubmittingPatches:\nsimplify guidance for choosing a starting point, 2023-07-14)).\n\nThe following is a version of this section that attempts to address my\ncomments above (I've included the other bits already reviewed earlier to\nmake it easier to read):\n\n    Takes a range of commits, and replays them onto a new starting\n    point. The new commits are created without modifying the working\n    tree, index, or any branches. If there are branches that point to\n    the commits in <revision-range>, list them in the output with both\n    the original commit hash and the corresponding (replayed) commit\n    hash (see OUTPUT section below) .\n\n    This command is like linkgit:git-rebase[1], but notably does not\n    require a working tree or index. This means you can run this command\n    in a bare repo (useful for server-side environments). And because\n    nothing is modified (only new commits are created), it's like a \"dry\n    run\" rebase.\n\n    By combining this command with `git update-ref --stdin`, the\n    relevant branches can be updated. That is, the branches that were\n    pointing originally to the commits given in the <revision-range>\n    will be updated to point to the replayed commits. This is similar to\n    the way how `git rebase --update-refs` updates multiple branches in\n    the affected range.\n\n> +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n> +\n> +OPTIONS\n> +-------\n> +\n> +--onto <newbase>::\n\nHow about 'starting-point' instead of 'newbase'?\n\n> +\tStarting point at which to create the new commits.  May be any\n> +\tvalid commit, and not just an existing branch name.\n\nAdd \"See linkgit:gitrevisions[7].\" at the end?\n\n> +The update-ref command(s) in the output will update the branch(es) in\n> +the revision range to point at the new commits, similar to the way how\n> +`git rebase --update-refs` updates multiple branches in the affected\n> +range.\n\nAh, good example. I've moved this to my larger example above, so I don't\nthink this paragraph is needed here any more (it probably didn't belong\nin OPTIONS anyway).\n\n> +<revision-range>::\n> +\tRange of commits to replay; see \"Specifying Ranges\" in\n> +\tlinkgit:git-rev-parse.\n\nOK.\n\n> +OUTPUT\n> +------\n> +\n> +When there are no conflicts, the output of this command is usable as\n> +input to `git update-ref --stdin`.\n\nWhat happens if there are conflicts? Probably good to mention in the\nDISCUSSION section. Some questions you may want to answer for the\nreader:\n\n(1) Is git-replay an all-or-nothing operation? That is, if there are any\nconflicts, is the output always empty (or do we still output those\nbranches that *can* be updated without conflicts)?\n\n(2) What is meant by \"conflict\" for git-replay? Is it the same meaning\nas the case for git-rebase?\n\nFor (1), in your cover letter under \"# Important limitations\" you say\n\"No resumability\" but I am not sure if this means git-replay will output\n*something* before exiting with an error, or simply nothing at all.\n\nSpeaking of the limitations section, perhaps it's worth pointing those\nout under DISCUSSION as well?\n\n> It is basically of the form:\n\nWhy \"basically\"? Are there cases where the output can be different than\nthe example given below? If not, then perhaps drop the word \"basically\"?\n\n> +\tupdate refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n> +\tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n> +\tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n> +\n> +where the number of refs updated depends on the arguments passed and\n> +the shape of the history being replayed.\n\nLet's use \"number of branches\" instead of \"number of refs\" here to be\nconsistent with the language elsewhere.\n\n> +EXIT STATUS\n> +-----------\n> +\n> +For a successful, non-conflicted replay, the exit status is 0.  When\n> +the replay has conflicts, the exit status is 1.\n\nOK.\n\n> If the replay is not\n> +able to complete (or start) due to some kind of error, the exit status\n> +is something other than 0 or 1.\n\nNot sure how useful \"due to some kind of error\" is here --- presumably\nthe inability to replay is always due to some kind of error.\n\nWould it be worth including a brief explanation about why git-replay\nmight be unable to complete or start (is this behavior in practice a\ncommon-enough thing to document here)?\n\n> +EXAMPLES\n> +--------\n> +\n> +To simply rebase mybranch onto target:\n\nLooking at the CLI arguments, I think this phrase should be:\n\n    Replay the commits in `origin/main..mybranch` onto `target`:\n\n> +------------\n> +$ git replay --onto target origin/main..mybranch\n> +update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n> +------------\n> +\n> +When calling `git replay`, one does not need to specify a range of\n> +commits to replay using the syntax `A..B`; any range expression will\n> +do:\n> +\n> +------------\n> +$ git replay --onto origin/main ^base branch1 branch2 branch3\n\nInstead of `base`, how about `olderbranch`?\n\n> +update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n> +update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n> +update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n> +------------\n> +\n> +This will simultaneously rebase branch1, branch2, and branch3 -- all\n> +commits they have since base, playing them on top of origin/main.\n\nHow about\n\n    This will rebase the commits in `branch1`, `branch2`, and `branch3`\n    (excluding those in `base`), preplaying them on top of `origin/main`.\n\n> +These three branches may have commits on top of base that they have in\n> +common, but that does not need to be the case.\n\ns/base/`base`\n\n> +GIT\n> +---\n> +Part of the linkgit:git[1] suite\n\nOne question I do have is what happens if you run git-replay twice\n(successfully)? Does the second invocation create another set of (new)\nsuccessfully replayed commits? I ask because I'm interested in informing\nthe readers of our docs about any potential pitfalls from abusing this\ncommand by mistake.\n"},{"id":"481641","messageId":"owlyledelnnd.fsf@fine.c.googlers.com","threadId":"59560","inReplyTo":"owlyo7icl1g3.fsf@fine.c.googlers.com","subject":"Re: [PATCH v4 11/15] replay: use standard revision ranges","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2023-09-10T03:20:38Z","receivedAt":"2023-09-10T03:20:54Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Linus Arver <linusa@google.com> writes:\n\n>> +update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n>> +update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n>> +update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n>> +------------\n>> +\n>> +This will simultaneously rebase branch1, branch2, and branch3 -- all\n>> +commits they have since base, playing them on top of origin/main.\n>\n> How about\n>\n>     This will rebase the commits in `branch1`, `branch2`, and `branch3`\n>     (excluding those in `base`), preplaying them on top of `origin/main`.\n\nOops, I meant \"replaying\" not \"preplaying\". But also, perhaps the\nfollowing is simpler?\n\n    This will replay the commits in `branch1`, `branch2`, and `branch3`\n    (excluding those in `base`), on top of `origin/main`.\n"},{"id":"482958","messageId":"20231010123847.2777056-2-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 01/14] t6429: remove switching aspects of fast-rebase","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:34Z","receivedAt":"2023-10-10T12:39:11Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAt the time t6429 was written, merge-ort was still under development,\ndid not have quite as many tests, and certainly was not widely deployed.\nSince t6429 was exercising some codepaths just a little differently, we\nthought having them also test the \"merge_switch_to_result()\" bits of\nmerge-ort was useful even though they weren't intrinsic to the real\npoint of these tests.\n\nHowever, the value provided by doing extra testing of the\n\"merge_switch_to_result()\" bits has decreased a bit over time, and it's\nactively making it harder to refactor `test-tool fast-rebase` into `git\nreplay`, which we are going to do in following commits.  Dispense with\nthese bits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/helper/test-fast-rebase.c              | 9 +--------\n t/t6429-merge-sequence-rename-caching.sh | 9 +++++++--\n 2 files changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c\nindex cac20a72b3..2bfab66b1b 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/t/helper/test-fast-rebase.c\n@@ -194,7 +194,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n-\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\tmerge_finalize(&merge_opt, &result);\n \n \tif (result.clean < 0)\n \t\texit(128);\n@@ -213,9 +213,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n-\n-\t\tprime_cache_tree(the_repository, the_repository->index,\n-\t\t\t\t result.tree);\n \t} else {\n \t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n@@ -228,10 +225,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t\tdie(\"Failed to update %s\", argv[4]);\n \t\t}\n \t}\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-\n \tret = (result.clean == 0);\n cleanup:\n \tstrbuf_release(&reflog_msg);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex d02fa16614..75d3fd2dba 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -72,6 +72,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \t\tgit switch upstream &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n@@ -200,6 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -277,6 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -356,8 +359,6 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n \t\t#git cherry-pick upstream..topic &&\n \n-\t\tgrep CONFLICT..rename/rename output &&\n-\n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n \t)\n@@ -456,6 +457,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -522,6 +524,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -624,6 +627,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -682,6 +686,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482959","messageId":"20231010123847.2777056-1-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20230907092521.733746-1-christian.couder@gmail.com","subject":"[PATCH v5 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:33Z","receivedAt":"2023-10-10T12:39:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"# Intro\n\n`git replay` has initially been developed entirely by Elijah Newren\nmostly last year (2022) at:\n\nhttps://github.com/newren/git/commits/replay\n\nI took over this year to polish and upstream it as GitLab is\ninterested in replacing libgit2, and for that purpose needs a command\nto do server side (so without using a worktree) rebases, cherry-picks\nand reverts.\n\nI reduced the number of commits and features in this first patch\nseries, compared to what Elijah already developed. Especially I\nstopped short of replaying merge commits and replaying\ninteractively. These and other features might be upstreamed in the\nfuture after this patch series has graduated.\n\nThe focus in this series is to make it a good plumbing command that\ncan already be used server side and that replaces the \"fast-rebase\"\ntest-tool command. So things to make it easier to use on the command\nline, and more advanced features (like replaying merges) are left out.\n\nIt looks like GitHub has actually already been using version 3 of this\npatch series in production with good results. See:\n\nhttps://github.blog/2023-07-27-scaling-merge-ort-across-github/\nhttps://lore.kernel.org/git/304f2a49-5e05-7655-9f87-2011606df5db@gmx.de/\n\n# Content of this cover letter\n\nThe \"Quick Overview\" and \"Reasons for diverging from cherry-pick &\nrebase\" sections just below are describing the purpose of the new\ncommand in the big scheme of things. They are taken from Elijah's\ndesign notes\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt)\nand describe what we want this command to become and the reasons for\nthat, not what the command is after only this patch series. Also these\ndesign notes were written at least one year ago, so parts of those 2\nsections are not true anymore. I have added Phillip Wood's or Felipe\nContreras' notes (thanks to them) where that's the case, but some now\nflawed parts may have missed.\n\nAfter these two sections, starting with the \"Important limitations\"\nsection, you will find sections describing what is actually in this\npatch series.\n\nMore interesting material is available in Elijah's design notes like\nan \"Intro via examples\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L37-L132),\na discussion about \"Preserving topology, replaying merges\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L264-L341)\nand a \"Current status\" section describing Elijah's work\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L344-L392)\nbefore I started working on upstreaming it.\n\nI have not included this material here though, as the documentation\nadded by this patch series for the `git replay` command already\nincludes an \"EXAMPLES\" section, and other sections of Elijah's design\nnotes might not be interesting for now. Also this cover letter is\nalready pretty long.  But reviewers can refer to the links above if\nthey think it can help.\n\n# Quick Overview (from Elijah's design notes)\n\n`git replay`, at a basic level, can perhaps be thought of as a\n\"default-to-dry-run rebase\" -- meaning no updates to the working tree,\nor to the index, or to any references.  However, it differs from\nrebase in that it:\n\n  * Works for branches that aren't checked out\n\n  * Works in a bare repository\n\n  * Can replay multiple branches simultaneously (with or without common\n    history in the range being replayed)\n\n  * Preserves relative topology by default (merges are replayed too in\n    Elijah's original work, not in this series)\n\n  * Focuses on performance\n\n  * Has several altered defaults as a result of the above\n\nI sometimes think of `git replay` as \"fast-replay\", a patch-based\nanalogue to the snapshot-based fast-export & fast-import tools.\n\n# Reasons for diverging from cherry-pick & rebase (from Elijah's\n  design notes)\n\nThere are multiple reasons to diverge from the defaults in cherry-pick and\nrebase.\n\n* Server side needs\n\n  * Both cherry-pick and rebase, via the sequencer, are heavily tied\n    to updating the working tree, index, some refs, and a lot of\n    control files with every commit replayed, and invoke a mess of\n    hooks[1] that might be hard to avoid for backward compatibility\n    reasons (at least, that's been brought up a few times on the\n    list).\n\n  * cherry-pick and rebase both fork various subprocesses\n    unnecessarily, but somewhat intrinsically in part to ensure the\n    same hooks are called that old scripted implementations would have\n    called.\n\n    Note: since 356ee4659bb (sequencer: try to commit without forking\n    'git commit', 2017-11-24) cherry-pick and rebase do not fork\n    subprocesses other than hooks for the cases covered by this patch\n    series (i.e. they do not fork \"git commit\" for simple picks).\n\n  * \"Dry run\" behavior, where there are no updates to worktree, index,\n    or even refs might be important.\n\n  * Should not assume users only want to operate on HEAD (see next\n    section)\n\n* Decapitate HEAD-centric assumptions\n\n  * cherry-pick forces commits to be played on top of HEAD;\n    inflexible.\n\n  * rebase assumes the range of commits to be replayed is\n    upstream..HEAD by default, though it allows one to replay\n    upstream..otherbranch -- but it still forcibly and needlessly\n    checks out 'otherbranch' before starting to replay things.\n\n    Note: since 767a9c417eb (rebase -i: stop checking out the tip of\n    the branch to rebase, 2020-01-24) it's not true that rebase\n    forcibly and needlessly checks out 'otherbranch'.\n\n  * Assuming HEAD is involved severely limits replaying multiple\n    (possibly divergent) branches.\n\n    Note: since 89fc0b53fdb (rebase: update refs from 'update-ref'\n    commands, 2022-07-19) the sequencer can update multiple\n    branches. The issue with divergent branch is with command line\n    arguments and the todo list generation rather than the\n    capabilities of the sequencer.\n\n  * Once you stop assuming HEAD has a certain meaning, there's not\n    much reason to have two separate commands anymore (except for the\n    funny extra not-necessarily-compatible options both have gained\n    over time).\n\n  * (Micro issue: Assuming HEAD is involved also makes it harder for\n    new users to learn what rebase means and does; it makes command\n    lines hard to parse.  Not sure I want to harp on this too much, as\n    I have a suspicion I might be creating a tool for experts with\n    complicated use cases, but it's a minor quibble.)\n\n* Performance\n\n  * jj is slaughtering us on rebase speed[2].  I would like us to become\n    competitive.  (I dropped a few comments in the link at [2] about why\n    git is currently so bad.)\n\n  * From [3], there was a simple 4-patch series in linux.git that took\n    53 seconds to rebase.  Switching to ort dropped it to 16 seconds.\n    While that sounds great, only 11 *milliseconds* were needed to do\n    the actual merges.  That means almost *all* the time (>99%) was\n    overhead!  Big offenders:\n\n    * --reapply-cherry-picks should be the default\n\n    * can_fast_forward() should be ripped out, and perhaps other extraneous\n      revision walks\n\n      Note: d42c9ffa0f (rebase: factor out branch_base calculation,\n      2022-10-17) might already deal with that (according to Felipe\n      Contreras).\n\n    * avoid updating working tree, index, refs, reflogs, and control\n      structures except when needed (e.g. hitting a conflict, or operation\n      finished)\n\n  * Other performance ideas (mostly for future work, not in this\n    series)\n\n    * single-file control structures instead of directory of files\n      (when doing interactive things which is in Elijah's original\n      work, but not in this series)\n\n    * avoid forking subprocesses unless explicitly requested (e.g.\n      --exec, --strategy, --run-hooks).  For example, definitely do not\n      invoke `git commit` or `git merge`.\n\n    * Sanitize hooks:\n\n      * dispense with all per-commit hooks for sure (pre-commit,\n        post-commit, post-checkout).\n\n      * pre-rebase also seems to assume exactly 1 ref is written, and\n        invoking it repeatedly would be stupid.  Plus, it's specific\n        to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n        rebase probably broke the pre-rebase assumptions already...)\n\n      * post-rewrite hook might make sense, but fast-import got\n        exempted, and I think of replay like a patch-based analogue\n        to the snapshot-based fast-import.\n\n    * When not running server side, resolve conflicts in a sparse-cone\n      sparse-index worktree to reduce number of files written to a\n      working tree.  (See below as well.)\n\n    * [High risk of possible premature optimization] Avoid large\n      numbers of newly created loose objects, when replaying large\n      numbers of commits.  Two possibilities: (1) Consider using\n      tmp-objdir and pack objects from the tmp-objdir at end of\n      exercise, (2) Lift code from git-fast-import to immediately\n      stuff new objects into a pack?\n\n* Multiple branches and non-checked out branches\n\n  * The ability to operate on non-checked out branches also implies\n    that we should generally be able to replay when in a dirty working\n    tree (exception being when we expect to update HEAD and any of the\n    dirty files is one that needs to be updated by the replay).\n\n  * Also, if we are operating locally on a non-checked out branch and\n    hit a conflict, we should have a way to resolve the conflict\n    without messing with the user's work on their current\n    branch. (This is not is this patch series though.)\n\n    * Idea: new worktree with sparse cone + sparse index checkout,\n      containing only files in the root directory, and whatever is\n      necessary to get the conflicts\n\n    * Companion to above idea: control structures should be written to\n      $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n      replay sessions, and so we know which worktrees are associated\n      with which replay operations.\n\n  - [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/\n  - [2] https://github.com/martinvonz/jj/discussions/49\n  - [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/\n\n# Important limitations\n\n* The code exits with code 1 if there are any conflict. No\n  resumability. No nice output. No interactivity. No special exit code\n  depending on the reason.\n\n* When a commit becomes empty as it is replayed, it is still replayed\n  as an empty commit, instead of being dropped.\n\n* No replaying merges, nor root commits. Only regular commits.\n\n* Signed commits are not properly handled. It's not clear what to do\n  to such commits when replaying on the server side.\n\n* Notes associated with replayed commits are not updated nor\n  duplicated. (Thanks to Phillip Wood for noticing.)\n\n# Commit overview\n\n* 1/14 t6429: remove switching aspects of fast-rebase\n\n    Preparatory commit to make it easier to later replace the\n    fast-rebase test-tool by `git replay` without breaking existing\n    tests.\n\n* 2/14 replay: introduce new builtin\n\n    This creates a minimal `git replay` command by moving the code\n    from the `fast-rebase` test helper from `t/helper/` into\n    `builtin/` and doing some renames and a few other needed changes.\n\n* - 3/14 replay: start using parse_options API\n  - 4/14 replay: die() instead of failing assert()\n  - 5/14 replay: introduce pick_regular_commit()\n  - 6/14 replay: change rev walking options\n  - 7/14 replay: add an important FIXME comment about gpg signing\n  - 8/14 replay: remove progress and info output\n  - 9/14 replay: remove HEAD related sanity check\n\n    These slowly change the command to make it behave more like\n    regular commands and to start cleaning up its output.\n\n* 10/14 replay: make it a minimal server side command\n\n    After the cleaning up in previous commits, it's now time to\n    radically change the way it works by stopping it to do ref\n    updates, to update the index and worktree, to consider HEAD as\n    special. Instead just make it output commands that should be\n    passed to `git update-ref --stdin`.\n\n* 11/14 replay: use standard revision ranges\n\n    Start addind new interesting features and also documentation and\n    tests, as the interface of the command is cristalizing into its\n    final form.\n\n* - 12/14 replay: add --advance or 'cherry-pick' mode\n  - 13/14 replay: add --contained to rebase contained branches\n\n    Add new options and features to the command.\n\n* 14/14 replay: stop assuming replayed branches do not diverge\n\n    This adds another interesting feature, as well as related\n    documentation and tests.\n\n# Notes about `fast-rebase`, tests and documentation\n\nThe `fast-rebase` test-tool helper was developed by Elijah to\nexperiment with a rebasing tool that would be developed from scratch\nbased on his merge-ort work, could be used to test that merge-ort\nwork, and would not have the speed and interface limitations of `git\nrebase` or `git cherry-pick`.\n\nThis `fast-rebase` helper was used before this series in:\n\nt6429-merge-sequence-rename-caching.sh\n\nSo when `git replay` is created from `fast-rebase` in patch 2/14, the\nt6429 test script is also converted to use `git replay`. This ensures\nthat `git replay` doesn't break too badly during the first 10 patches\nin this patch series.\n\nTests and documentation are introduced specifically for `git replay`\nonly in 11/14 and later patches as it doesn't make much sense to\ndocument and test behavior that we know is going to change soon. So\nit's only when the command is crystalizing towards its final form that\nwe start documenting and testing it.\n\n# Possibly controversial issues \n\n* bare or not bare: this series works towards a plumbing command with\n  the end goal of it being usable and used first on bare repos,\n  contrary to existing commands like `git rebase` and `git\n  cherry-pick`. The tests check that the command works on both bare\n  and non-bare repo though.\n\n* exit status: a successful, non-conflicted replay exits with code\n  0. When the replay has conflicts, the exit status is 1. If the\n  replay is not able to complete (or start) due to some kind of error,\n  the exit status is something other than 0 or 1. There are a few\n  tests checking that. It has been suggested in an internal review\n  that conflicts might want to get a more specific error code as an\n  error code of 1 might be quite easy to return by accident. It\n  doesn't seem to me from their docs (which might want to be improved,\n  I didn't look at the code) that other commands like `git merge` and\n  `git rebase` exit with a special error code in case of conflict.\n\n* make worktree and index changes optional: commit 10/14 stops\n  updating the index and worktree, but it might be better especially\n  for cli users to make that optional. The issue is that this would\n  make the command more complex while we are developing a number of\n  important features so that the command can be used on bare repos. It\n  seems that this should rather be done in an iterative improvement\n  after the important features have landed.\n\n* when and where to add tests and docs: although t6429 has tests that\n  are changed to use the new command instead of the fast-rebase\n  test-tool command as soon as the former is introduced, there is no\n  specific test script and no doc for the new command until commit\n  11/14 when standard revision ranges are used. This is done to avoid\n  churn in tests and docs while the final form of the command hasn't\n  crystalized enough. Adding tests and doc at this point makes this\n  commit quite big and possibly more difficult to review than if they\n  were in separate commits though. On the other hand when tests and\n  docs are added in specific commits some reviewers say it would be\n  better to introduce them when the related changes are made.\n\n* --advance and --contained: these two advanced options might not\n  belong to this first series and could perhaps be added in a followup\n  series in separate commits. On the other hand the code for\n  --contained seems involved with the code of --advance and it's nice\n  to see soon that git replay can indeed do cherry-picking and rebase\n  many refs at once, and this way fullfil these parts of its promise.\n\n* replaying diverging branches: 14/14 the last patch in the series,\n  which allow replaying diverging branches, can be seen as a\n  fundamental fix or alternatively as adding an interesting\n  feature. So it's debatable if it should be in its own patch along\n  with its own tests as in this series, or if it should be merged into\n  a previous patch and which one.\n\n* only 2 patches: this patch series can be seen from a high level\n  point of view as 1) introducing the new `git replay` command, and 2)\n  using `git replay` to replace, and get rid of, the fast-rebase\n  test-tool command. The fact that not much of the original\n  fast-rebase code and interface is left would agree with that point\n  of view. On the other hand, fast-rebase can also be seen as a first\n  iteration towards `git replay`. So it can also make sense to see how\n  `git replay` evolved from it.\n\n# Changes between v4 and v5\n\nThanks to Dscho, Linus Arver and Dragan Simic for their suggestions on\nthe previous version! The few changes compared to v4 are:\n\n* The patch series was rebased onto master at 3a06386e31 (The\n  fifteenth batch, 2023-10-04). This is to fix possible conflicts\n  especially related to header changes.\n\n* Patch 12/15 (replay: disallow revision specific options and\n  pathspecs) in version 4 has been removed, so there are now only 14\n  patches instead of 15 in the series. This follows a suggestion by\n  Dscho, and goes in the direction Elijah initially wanted before\n  Derrick Stolee argued for disallowing revision specific options and\n  pathspecs.\n\n* In patch 2/14 (replay: introduce new builtin) the command is now in\n  the \"plumbingmanipulators\" category instead of the \"mainporcelain\"\n  category. This is more in line with the goal of introducing it as a\n  plumbing command for now. Thanks to a suggestion from Dscho.\n\n* In patch 6/14 (replay: change rev walking options) the commit\n  message has been improved, including its subject, to better match\n  and explain what the patch does. Also instead of forcing reverse\n  order we use the reverse order by default but allow it to be changed\n  using `--reverse`. Thanks to Dscho.\n\n* In patch 11/14 (replay: use standard revision ranges) the commit\n  message has been improved to discuss a bit the new options that are\n  now accepted and eaten by setup_revisions().\n\n* In patch 11/14 too, the documentation has been improved thanks to\n  suggestions by Linus Arver and Dragan Simic. It also now includes\n  `rev-list-options.txt` to document the new rev walking options\n  accepted by setup_revisions().\n\nAll CI tests seem to pass according to:\n\nhttps://github.com/chriscool/git/actions/runs/6468200080\n\n# Range-diff between v4 and v5\n\n 1:  1eaca9b788 =  1:  72c34a0eba t6429: remove switching aspects of fast-rebase\n 2:  5ac4beb1ae !  2:  f85e6c823c replay: introduce new builtin\n    @@ Commit message\n         Subsequent commits will flesh out its capabilities and make it a more\n         standard regular builtin.\n     \n    +    Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n         Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Elijah Newren <newren@gmail.com>\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n    @@ command-list.txt: git-reflog                              ancillarymanipulators\n      git-remote                              ancillarymanipulators           complete\n      git-repack                              ancillarymanipulators           complete\n      git-replace                             ancillarymanipulators           complete\n    -+git-replay                              mainporcelain           history\n    ++git-replay                              plumbingmanipulators\n      git-request-pull                        foreignscminterface             complete\n      git-rerere                              ancillaryinterrogators\n      git-reset                               mainporcelain           history\n 3:  299381aa9b =  3:  11abb9d120 replay: start using parse_options API\n 4:  3b825c9be0 =  4:  9e568eae84 replay: die() instead of failing assert()\n 5:  b1e890745d =  5:  e7ebf3c5ef replay: introduce pick_regular_commit()\n 6:  ec51351889 !  6:  37d545d5d6 replay: don't simplify history\n    @@ Metadata\n     Author: Elijah Newren <newren@gmail.com>\n     \n      ## Commit message ##\n    -    replay: don't simplify history\n    +    replay: change rev walking options\n     \n         Let's set the rev walking options we need after calling\n    -    setup_revisions() instead of before. This makes it clearer which options\n    -    we need.\n    +    setup_revisions() instead of before. This enforces options we always\n    +    want.\n    +\n    +    We want the command to work from older commits to newer ones by default,\n    +    but we are Ok with letting users reverse that, using --reverse, if that's\n    +    what they really want.\n     \n         Also we don't want history simplification, as we want to deal with all\n         the commits in the affected range.\n     \n    +    Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n         Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Elijah Newren <newren@gmail.com>\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        }\n      \n     +  /* requirements/overrides for revs */\n    -+  revs.reverse = 1;\n    ++  revs.reverse = !revs.reverse;\n     +  revs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n     +  revs.topo_order = 1;\n     +  revs.simplify_history = 0;\n 7:  cd4ed07d2d =  7:  2943f08926 replay: add an important FIXME comment about gpg signing\n 8:  e45a55917c =  8:  f81962ba41 replay: remove progress and info output\n 9:  0587a76cbb =  9:  236747497e replay: remove HEAD related sanity check\n10:  d10368e87a = 10:  3374d5be23 replay: make it a minimal server side command\n11:  4e09572c43 ! 11:  197d076a93 replay: use standard revision ranges\n    @@ Commit message\n         way as many other Git commands. This makes its interface more\n         standard and more flexible.\n     \n    +    This also enables many revision related options accepted and\n    +    eaten by setup_revisions(). If the replay command was a high level\n    +    one or had a high level mode, it would make sense to restrict some\n    +    of the possible options, like those generating non-contiguous\n    +    history, as they could be confusing for most users.\n    +\n         Also as the interface of the command is now mostly finalized,\n         we can add some documentation as well as testcases to make sure\n         the command will continue to work as designed in the future.\n     \n    +    We only document the rev-list related options among all the\n    +    revision related options that are now accepted, as the rev-list\n    +    related ones are probably the most useful for now.\n    +\n    +    Helped-by: Dragan Simic <dsimic@manjaro.org>\n    +    Helped-by: Linus Arver <linusa@google.com>\n         Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Elijah Newren <newren@gmail.com>\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n    @@ Documentation/git-replay.txt (new)\n     +\n     +NAME\n     +----\n    -+git-replay - Replay commits on a different base, without touching working tree\n    ++git-replay - Replay commits on a new base, works on bare repos too\n     +\n     +\n     +SYNOPSIS\n    @@ Documentation/git-replay.txt (new)\n     +DESCRIPTION\n     +-----------\n     +\n    -+Takes a range of commits, and replays them onto a new location.  Does\n    -+not touch the working tree or index, and does not update any\n    -+references.  However, the output of this command is meant to be used\n    -+as input to `git update-ref --stdin`, which would update the relevant\n    -+branches.\n    ++Takes a range of commits and replays them onto a new location. Leaves\n    ++the working tree and the index untouched, and updates no\n    ++references. The output of this command is meant to be used as input to\n    ++`git update-ref --stdin`, which would update the relevant branches\n    ++(see the OUTPUT section below).\n     +\n     +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n     +\n    @@ Documentation/git-replay.txt (new)\n     +\n     +<revision-range>::\n     +  Range of commits to replay; see \"Specifying Ranges\" in\n    -+  linkgit:git-rev-parse.\n    ++  linkgit:git-rev-parse and the \"Commit Limiting\" options below.\n    ++\n    ++include::rev-list-options.txt[]\n     +\n     +OUTPUT\n     +------\n     +\n     +When there are no conflicts, the output of this command is usable as\n    -+input to `git update-ref --stdin`.  It is basically of the form:\n    ++input to `git update-ref --stdin`.  It is of the form:\n     +\n     +  update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n     +  update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n    @@ Documentation/git-replay.txt (new)\n     +EXAMPLES\n     +--------\n     +\n    -+To simply rebase mybranch onto target:\n    ++To simply rebase `mybranch` onto `target`:\n     +\n     +------------\n     +$ git replay --onto target origin/main..mybranch\n    @@ Documentation/git-replay.txt (new)\n     +update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n     +------------\n     +\n    -+This will simultaneously rebase branch1, branch2, and branch3 -- all\n    -+commits they have since base, playing them on top of origin/main.\n    -+These three branches may have commits on top of base that they have in\n    -+common, but that does not need to be the case.\n    ++This will simultaneously rebase `branch1`, `branch2`, and `branch3`,\n    ++all commits they have since `base`, playing them on top of\n    ++`origin/main`. These three branches may have commits on top of `base`\n    ++that they have in common, but that does not need to be the case.\n     +\n     +GIT\n     +---\n12:  64b803f1cf <  -:  ---------- replay: disallow revision specific options and pathspecs\n13:  04f27d81ab ! 12:  e52d8b961c replay: add --advance or 'cherry-pick' mode\n    @@ Commit message\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n      ## Documentation/git-replay.txt ##\n    -@@ Documentation/git-replay.txt: git-replay - Replay commits on a different base, without touching working tree\n    +@@ Documentation/git-replay.txt: git-replay - Replay commits on a new base, works on bare repos too\n      SYNOPSIS\n      --------\n      [verse]\n    @@ Documentation/git-replay.txt: OPTIONS\n      \n      <revision-range>::\n     -  Range of commits to replay; see \"Specifying Ranges\" in\n    --  linkgit:git-rev-parse.\n    +-  linkgit:git-rev-parse and the \"Commit Limiting\" options below.\n     +  Range of commits to replay. More than one <revision-range> can\n     +  be passed, but in `--advance <branch>` mode, they should have\n     +  a single tip, so that it's clear where <branch> should point\n    -+  to. See \"Specifying Ranges\" in linkgit:git-rev-parse.\n    ++  to. See \"Specifying Ranges\" in linkgit:git-rev-parse and the\n    ++  \"Commit Limiting\" options below.\n      \n    - OUTPUT\n    - ------\n    -@@ Documentation/git-replay.txt: input to `git update-ref --stdin`.  It is basically of the form:\n    + include::rev-list-options.txt[]\n    + \n    +@@ Documentation/git-replay.txt: input to `git update-ref --stdin`.  It is of the form:\n        update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n      \n      where the number of refs updated depends on the arguments passed and\n    @@ builtin/replay.c: static struct commit *pick_regular_commit(struct commit *pickm\n        struct merge_options merge_opt;\n        struct merge_result result;\n     +  struct strset *update_refs = NULL;\n    -   int ret = 0, i;\n    +   int ret = 0;\n      \n        const char * const replay_usage[] = {\n     -          N_(\"git replay --onto <newbase> <revision-range>...\"),\n    @@ builtin/replay.c: static struct commit *pick_regular_commit(struct commit *pickm\n                           N_(\"revision\"),\n                           N_(\"replay onto given commit\")),\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    -                   usage_with_options(replay_usage, replay_options);\n    -           }\n    +   argc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n    +                        PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n      \n     -  if (!onto_name) {\n     -          error(_(\"option --onto is mandatory\"));\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        /* Return */\n     \n      ## t/t3650-replay-basics.sh ##\n    -@@ t/t3650-replay-basics.sh: test_expect_success 'using replay on bare repo with disallowed pathspec' '\n    -   test_must_fail git -C bare replay --onto main topic1..topic2 -- A.t\n    +@@ t/t3650-replay-basics.sh: test_expect_success 'using replay on bare repo to rebase with a conflict' '\n    +   test_expect_code 1 git -C bare replay --onto topic1 B..conflict\n      '\n      \n     +test_expect_success 'using replay to perform basic cherry-pick' '\n14:  9ed0919ad5 ! 13:  fc79a930b5 replay: add --contained to rebase contained branches\n    @@ Commit message\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n      ## Documentation/git-replay.txt ##\n    -@@ Documentation/git-replay.txt: git-replay - Replay commits on a different base, without touching working tree\n    +@@ Documentation/git-replay.txt: git-replay - Replay commits on a new base, works on bare repos too\n      SYNOPSIS\n      --------\n      [verse]\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        struct rev_info revs;\n        struct commit *last_commit = NULL;\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    -   int ret = 0, i;\n    +   int ret = 0;\n      \n        const char * const replay_usage[] = {\n     -          N_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n15:  cf8c984877 ! 14:  1160ff54e6 replay: stop assuming replayed branches do not diverge\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        struct merge_result result;\n        struct strset *update_refs = NULL;\n     +  kh_oid_map_t *replayed_commits;\n    -   int ret = 0, i;\n    +   int ret = 0;\n      \n        const char * const replay_usage[] = {\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n\n\nElijah Newren (14):\n  t6429: remove switching aspects of fast-rebase\n  replay: introduce new builtin\n  replay: start using parse_options API\n  replay: die() instead of failing assert()\n  replay: introduce pick_regular_commit()\n  replay: change rev walking options\n  replay: add an important FIXME comment about gpg signing\n  replay: remove progress and info output\n  replay: remove HEAD related sanity check\n  replay: make it a minimal server side command\n  replay: use standard revision ranges\n  replay: add --advance or 'cherry-pick' mode\n  replay: add --contained to rebase contained branches\n  replay: stop assuming replayed branches do not diverge\n\n .gitignore                               |   1 +\n Documentation/git-replay.txt             | 127 +++++++\n Makefile                                 |   2 +-\n builtin.h                                |   1 +\n builtin/replay.c                         | 407 +++++++++++++++++++++++\n command-list.txt                         |   1 +\n git.c                                    |   1 +\n t/helper/test-fast-rebase.c              | 241 --------------\n t/helper/test-tool.c                     |   1 -\n t/helper/test-tool.h                     |   1 -\n t/t3650-replay-basics.sh                 | 198 +++++++++++\n t/t6429-merge-sequence-rename-caching.sh |  45 +--\n 12 files changed, 762 insertions(+), 264 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100644 builtin/replay.c\n delete mode 100644 t/helper/test-fast-rebase.c\n create mode 100755 t/t3650-replay-basics.sh\n\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482960","messageId":"20231010123847.2777056-4-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 03/14] replay: start using parse_options API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:36Z","receivedAt":"2023-10-10T12:39:17Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of manually parsing arguments, let's start using the parse_options\nAPI. This way this new builtin will look more standard, and in some\nupcoming commits will more easily be able to handle more command line\noptions.\n\nNote that we plan to later use standard revision ranges instead of\nhardcoded \"<oldbase> <branch>\" arguments. When we will use standard\nrevision ranges, it will be easier to check if there are no spurious\narguments if we keep ARGV[0], so let's call parse_options() with\nPARSE_OPT_KEEP_ARGV0 even if we don't need ARGV[0] right now to avoid\nsome useless code churn.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 45 ++++++++++++++++++++++++++++++++-------------\n 1 file changed, 32 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex e102749ab6..d6dec7c866 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -15,7 +15,7 @@\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n #include \"object-name.h\"\n-#include \"read-cache-ll.h\"\n+#include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n #include \"sequencer.h\"\n@@ -92,6 +92,7 @@ static struct commit *create_commit(struct tree *tree,\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n+\tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n \tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n@@ -105,16 +106,32 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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+\tconst char * const replay_usage[] = {\n+\t\tN_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tNULL\n+\t};\n+\tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"onto\", &onto_name,\n+\t\t\t   N_(\"revision\"),\n+\t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n+\n+\tif (!onto_name) {\n+\t\terror(_(\"option --onto is mandatory\"));\n+\t\tusage_with_options(replay_usage, replay_options);\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+\tif (argc != 3) {\n+\t\terror(_(\"bad number of arguments\"));\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n \n-\tonto = peel_committish(argv[2]);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\tonto = peel_committish(onto_name);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \t/* Sanity check */\n \tif (repo_get_oid(the_repository, \"HEAD\", &head))\n@@ -126,6 +143,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tBUG(\"Could not read index\");\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n+\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\n@@ -134,7 +152,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n@@ -197,8 +216,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &last_picked_commit->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -210,8 +229,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &head,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t}\n \tret = (result.clean == 0);\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482961","messageId":"20231010123847.2777056-3-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 02/14] replay: introduce new builtin","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:35Z","receivedAt":"2023-10-10T12:39:20Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor now, this is just a rename from `t/helper/test-fast-rebase.c` into\n`builtin/replay.c` with minimal changes to make it build appropriately.\n\nSubsequent commits will flesh out its capabilities and make it a more\nstandard regular builtin.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n .gitignore                                    |  1 +\n Makefile                                      |  2 +-\n builtin.h                                     |  1 +\n .../test-fast-rebase.c => builtin/replay.c    | 27 ++++++-------------\n command-list.txt                              |  1 +\n git.c                                         |  1 +\n t/helper/test-tool.c                          |  1 -\n t/helper/test-tool.h                          |  1 -\n t/t6429-merge-sequence-rename-caching.sh      | 27 +++++++------------\n 9 files changed, 22 insertions(+), 40 deletions(-)\n rename t/helper/test-fast-rebase.c => builtin/replay.c (89%)\n\ndiff --git a/.gitignore b/.gitignore\nindex 5e56e471b3..612c0f6a0f 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -135,6 +135,7 @@\n /git-remote-ext\n /git-repack\n /git-replace\n+/git-replay\n /git-request-pull\n /git-rerere\n /git-reset\ndiff --git a/Makefile b/Makefile\nindex 003e63b792..b5e3843d7f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -799,7 +799,6 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o\n TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n TEST_BUILTINS_OBJS += test-env-helper.o\n TEST_BUILTINS_OBJS += test-example-decorate.o\n-TEST_BUILTINS_OBJS += test-fast-rebase.o\n TEST_BUILTINS_OBJS += test-fsmonitor-client.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n TEST_BUILTINS_OBJS += test-genzeros.o\n@@ -1286,6 +1285,7 @@ BUILTIN_OBJS += builtin/remote-fd.o\n BUILTIN_OBJS += builtin/remote.o\n BUILTIN_OBJS += builtin/repack.o\n BUILTIN_OBJS += builtin/replace.o\n+BUILTIN_OBJS += builtin/replay.o\n BUILTIN_OBJS += builtin/rerere.o\n BUILTIN_OBJS += builtin/reset.o\n BUILTIN_OBJS += builtin/rev-list.o\ndiff --git a/builtin.h b/builtin.h\nindex d560baa661..28280636da 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -211,6 +211,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix);\n int cmd_remote_ext(int argc, const char **argv, const char *prefix);\n int cmd_remote_fd(int argc, const char **argv, const char *prefix);\n int cmd_repack(int argc, const char **argv, const char *prefix);\n+int cmd_replay(int argc, const char **argv, const char *prefix);\n int cmd_rerere(int argc, const char **argv, const char *prefix);\n int cmd_reset(int argc, const char **argv, const char *prefix);\n int cmd_restore(int argc, const char **argv, const char *prefix);\ndiff --git a/t/helper/test-fast-rebase.c b/builtin/replay.c\nsimilarity index 89%\nrename from t/helper/test-fast-rebase.c\nrename to builtin/replay.c\nindex 2bfab66b1b..e102749ab6 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/builtin/replay.c\n@@ -1,17 +1,11 @@\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+ * \"git replay\" builtin command\n  */\n \n #define USE_THE_INDEX_VARIABLE\n-#include \"test-tool.h\"\n+#include \"git-compat-util.h\"\n+\n+#include \"builtin.h\"\n #include \"cache-tree.h\"\n #include \"commit.h\"\n #include \"environment.h\"\n@@ -27,7 +21,8 @@\n #include \"sequencer.h\"\n #include \"setup.h\"\n #include \"strvec.h\"\n-#include \"tree.h\"\n+#include <oidset.h>\n+#include <tree.h>\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -94,7 +89,7 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n-int cmd__fast_rebase(int argc, const char **argv)\n+int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n@@ -110,12 +105,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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@@ -136,7 +125,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n \n-\trepo_init_revisions(the_repository, &revs, NULL);\n+\trepo_init_revisions(the_repository, &revs, prefix);\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\ndiff --git a/command-list.txt b/command-list.txt\nindex 54b2a50f5f..c4cd0f352b 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -160,6 +160,7 @@ git-reflog                              ancillarymanipulators           complete\n git-remote                              ancillarymanipulators           complete\n git-repack                              ancillarymanipulators           complete\n git-replace                             ancillarymanipulators           complete\n+git-replay                              plumbingmanipulators\n git-request-pull                        foreignscminterface             complete\n git-rerere                              ancillaryinterrogators\n git-reset                               mainporcelain           history\ndiff --git a/git.c b/git.c\nindex c67e44dd82..7068a184b0 100644\n--- a/git.c\n+++ b/git.c\n@@ -594,6 +594,7 @@ static struct cmd_struct commands[] = {\n \t{ \"remote-fd\", cmd_remote_fd, NO_PARSEOPT },\n \t{ \"repack\", cmd_repack, RUN_SETUP },\n \t{ \"replace\", cmd_replace, RUN_SETUP },\n+\t{ \"replay\", cmd_replay, RUN_SETUP },\n \t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t{ \"reset\", cmd_reset, RUN_SETUP },\n \t{ \"restore\", cmd_restore, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 621ac3dd10..3109ba5c7a 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -30,7 +30,6 @@ static struct test_cmd cmds[] = {\n \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n \t{ \"env-helper\", cmd__env_helper },\n \t{ \"example-decorate\", cmd__example_decorate },\n-\t{ \"fast-rebase\", cmd__fast_rebase },\n \t{ \"fsmonitor-client\", cmd__fsmonitor_client },\n \t{ \"genrandom\", cmd__genrandom },\n \t{ \"genzeros\", cmd__genzeros },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex a641c3a81d..d3e55c3f4e 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -24,7 +24,6 @@ int cmd__dump_untracked_cache(int argc, const char **argv);\n int cmd__dump_reftable(int argc, const char **argv);\n int cmd__env_helper(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__fsmonitor_client(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n int cmd__genzeros(int argc, const char **argv);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 75d3fd2dba..7670b72008 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,9 +71,8 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -141,8 +140,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -200,9 +198,8 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -278,9 +275,8 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -356,8 +352,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,9 +451,8 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -523,9 +517,8 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -626,9 +619,8 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -685,9 +677,8 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482962","messageId":"20231010123847.2777056-5-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 04/14] replay: die() instead of failing assert()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:37Z","receivedAt":"2023-10-10T12:39:29Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIt's not a good idea for regular Git commands to use an assert() to\ncheck for things that could happen but are not supported.\n\nLet's die() with an explanation of the issue instead.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex d6dec7c866..f3fdbe48c9 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -179,7 +179,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\n+\t\tif (!commit->parents)\n+\t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n+\t\tif (commit->parents->next)\n+\t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n+\n \t\tbase = commit->parents->item;\n \n \t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482963","messageId":"20231010123847.2777056-6-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 05/14] replay: introduce pick_regular_commit()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:38Z","receivedAt":"2023-10-10T12:39:31Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's refactor the code to handle a regular commit (a commit that is\nneither a root commit nor a merge commit) into a single function instead\nof keeping it inside cmd_replay().\n\nThis is good for separation of concerns, and this will help further work\nin the future to replay merge commits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 54 ++++++++++++++++++++++++++++++------------------\n 1 file changed, 34 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex f3fdbe48c9..c66888679b 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -89,6 +89,35 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+static struct commit *pick_regular_commit(struct commit *pickme,\n+\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  struct merge_options *merge_opt,\n+\t\t\t\t\t  struct merge_result *result)\n+{\n+\tstruct commit *base;\n+\tstruct tree *pickme_tree, *base_tree;\n+\n+\tbase = pickme->parents->item;\n+\n+\tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n+\tbase_tree = repo_get_commit_tree(the_repository, base);\n+\n+\tmerge_opt->branch2 = short_commit_name(pickme);\n+\tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n+\n+\tmerge_incore_nonrecursive(merge_opt,\n+\t\t\t\t  base_tree,\n+\t\t\t\t  result->tree,\n+\t\t\t\t  pickme_tree,\n+\t\t\t\t  result);\n+\n+\tfree((char*)merge_opt->ancestor);\n+\tmerge_opt->ancestor = NULL;\n+\tif (!result->clean)\n+\t\treturn NULL;\n+\treturn create_commit(result->tree, pickme, last_commit);\n+}\n+\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n@@ -100,7 +129,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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 tree *head_tree;\n \tstruct merge_result result;\n \tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n@@ -175,7 +204,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tresult.tree = head_tree;\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *base;\n+\t\tstruct commit *pick;\n \n \t\tfprintf(stderr, \"Rebasing %s...\\r\",\n \t\t\toid_to_hex(&commit->object.oid));\n@@ -185,26 +214,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tbase = commit->parents->item;\n-\n-\t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-\t\tbase_tree = repo_get_commit_tree(the_repository, 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\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!pick)\n \t\t\tbreak;\n+\t\tlast_commit = pick;\n \t\tlast_picked_commit = commit;\n-\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482964","messageId":"20231010123847.2777056-8-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 07/14] replay: add an important FIXME comment about gpg signing","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:40Z","receivedAt":"2023-10-10T12:39:32Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want to be able to handle signed commits in some way in the future,\nbut we are not ready to do it now. So for the time being let's just add\na FIXME comment to remind us about it.\n\nThese are different ways we could handle them:\n\n  - in case of a cli user and if there was an interactive mode, we could\n    perhaps ask if the user wants to sign again\n  - we could add an option to just fail if there are signed commits\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 5bab89857f..dbadf3b9b4 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -62,7 +62,7 @@ static struct commit *create_commit(struct tree *tree,\n \tstruct object *obj;\n \tstruct commit_list *parents = NULL;\n \tchar *author;\n-\tchar *sign_commit = NULL;\n+\tchar *sign_commit = NULL; /* FIXME: cli users might want to sign again */\n \tstruct commit_extra_header *extra;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *out_enc = get_commit_output_encoding();\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482965","messageId":"20231010123847.2777056-7-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 06/14] replay: change rev walking options","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:39Z","receivedAt":"2023-10-10T12:39:34Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's set the rev walking options we need after calling\nsetup_revisions() instead of before. This enforces options we always\nwant.\n\nWe want the command to work from older commits to newer ones by default,\nbut we are Ok with letting users reverse that, using --reverse, if that's\nwhat they really want.\n\nAlso we don't want history simplification, as we want to deal with all\nthe commits in the affected range.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 15 ++++++---------\n 1 file changed, 6 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex c66888679b..5bab89857f 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -173,15 +173,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \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-\n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n@@ -189,6 +180,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \n+\t/* requirements/overrides for revs */\n+\trevs.reverse = !revs.reverse;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\trevs.simplify_history = 0;\n+\n \tstrvec_clear(&rev_walk_args);\n \n \tif (prepare_revision_walk(&revs) < 0) {\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482966","messageId":"20231010123847.2777056-11-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 10/14] replay: make it a minimal server side command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:43Z","receivedAt":"2023-10-10T12:39:36Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want this command to be a minimal command that just does server side\npicking of commits, displaying the results on stdout for higher level\nscripts to consume.\n\nSo let's simplify it:\n  * remove the worktree and index reading/writing,\n  * remove the ref (and reflog) updating,\n  * remove the assumptions tying us to HEAD, since (a) this is not a\n    rebase and (b) we want to be able to pick commits in a bare repo,\n    i.e. to/from branches that are not checked out and not the main\n    branch,\n  * remove unneeded includes,\n  * handle rebasing multiple branches by printing on stdout the update\n    ref commands that should be performed.\n\nThe output can be piped into `git update-ref --stdin` for the ref\nupdates to happen.\n\nIn the future to make it easier for users to use this command\ndirectly maybe an option can be added to automatically pipe its output\ninto `git update-ref`.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c                         | 78 ++++++++----------------\n t/t6429-merge-sequence-rename-caching.sh | 39 +++++++-----\n 2 files changed, 50 insertions(+), 67 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 87fe655c6f..ace9ac6df0 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -6,11 +6,7 @@\n #include \"git-compat-util.h\"\n \n #include \"builtin.h\"\n-#include \"cache-tree.h\"\n-#include \"commit.h\"\n #include \"environment.h\"\n-#include \"gettext.h\"\n-#include \"hash.h\"\n #include \"hex.h\"\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n@@ -18,8 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"sequencer.h\"\n-#include \"setup.h\"\n #include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n@@ -102,6 +96,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n+\tmerge_opt->branch1 = short_commit_name(last_commit);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -122,15 +117,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct lock_file lock = LOCK_INIT;\n+\tstruct commit *last_commit = NULL;\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 *head_tree;\n \tstruct merge_result result;\n-\tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n@@ -161,10 +153,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index(the_repository) < 0)\n-\t\tBUG(\"Could not read index\");\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n@@ -190,58 +178,44 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\tmerge_opt.branch1 = \"HEAD\";\n-\thead_tree = repo_get_commit_tree(the_repository, onto);\n-\tresult.tree = head_tree;\n+\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *pick;\n+\t\tconst struct name_decoration *decoration;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n-\t\tif (!pick)\n+\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!last_commit)\n \t\t\tbreak;\n-\t\tlast_commit = pick;\n-\t\tlast_picked_commit = commit;\n+\n+\t\tdecoration = get_name_decoration(&commit->object);\n+\t\tif (!decoration)\n+\t\t\tcontinue;\n+\n+\t\twhile (decoration) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\t\tprintf(\"update %s %s %s\\n\",\n+\t\t\t\t       decoration->name,\n+\t\t\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t\t\t       oid_to_hex(&commit->object.oid));\n+\t\t\t}\n+\t\t\tdecoration = decoration->next;\n+\t\t}\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n+\tret = result.clean;\n \n-\tif (result.clean < 0)\n-\t\texit(128);\n-\n-\tif (result.clean) {\n-\t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n-\t\t\t    oid_to_hex(&last_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, branch_name.buf,\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &last_picked_commit->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n-\t\t\tdie(_(\"unable to update HEAD\"));\n-\t} else {\n-\t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &onto->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t}\n-\tret = (result.clean == 0);\n cleanup:\n-\tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n-\treturn ret;\n+\n+\t/* Return */\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret ? 0 : 1;\n }\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 7670b72008..099aefeffc 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,8 +71,9 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -140,7 +141,9 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -198,8 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -275,8 +279,9 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -451,8 +456,9 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -517,8 +523,9 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -619,8 +626,9 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -677,8 +685,9 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482967","messageId":"20231010123847.2777056-9-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 08/14] replay: remove progress and info output","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:41Z","receivedAt":"2023-10-10T12:39:37Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command will be changed in a follow up commit, so that it\nwill not update refs directly, but instead it will print on stdout a\nlist of commands that can be consumed by `git update-ref --stdin`.\n\nWe don't want this output to be polluted by its current low value\noutput, so let's just remove the latter.\n\nIn the future, when the command gets an option to update refs by\nitself, it will make a lot of sense to display a progress meter, but\nwe are not there yet.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 +------\n 1 file changed, 1 insertion(+), 6 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex dbadf3b9b4..08a7f68420 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -195,7 +195,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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.show_rename_progress = 0;\n \tmerge_opt.branch1 = \"HEAD\";\n \thead_tree = repo_get_commit_tree(the_repository, onto);\n \tresult.tree = head_tree;\n@@ -203,9 +203,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \twhile ((commit = get_revision(&revs))) {\n \t\tstruct commit *pick;\n \n-\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n-\t\t\toid_to_hex(&commit->object.oid));\n-\n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n@@ -224,7 +221,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\texit(128);\n \n \tif (result.clean) {\n-\t\tfprintf(stderr, \"\\nDone.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n \t\t\t    oid_to_hex(&last_commit->object.oid));\n@@ -238,7 +234,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t} else {\n-\t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482968","messageId":"20231010123847.2777056-10-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 09/14] replay: remove HEAD related sanity check","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:42Z","receivedAt":"2023-10-10T12:39:39Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want replay to be a command that can be used on the server side on\nany branch, not just the current one, so we are going to stop updating\nHEAD in a future commit.\n\nA \"sanity check\" that makes sure we are replaying the current branch\ndoesn't make sense anymore. Let's remove it.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 8 +-------\n 1 file changed, 1 insertion(+), 7 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 08a7f68420..87fe655c6f 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -123,7 +123,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct strvec rev_walk_args = STRVEC_INIT;\n \tstruct rev_info revs;\n@@ -162,11 +161,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\t/* Sanity check */\n-\tif (repo_get_oid(the_repository, \"HEAD\", &head))\n-\t\tdie(_(\"Cannot read HEAD\"));\n-\tassert(oideq(&onto->object.oid, &head));\n-\n \trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n@@ -238,7 +232,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n \t\t\t       &last_commit->object.oid,\n-\t\t\t       &head,\n+\t\t\t       &onto->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n \t\t\terror(_(\"could not update %s\"), argv[2]);\n \t\t\tdie(\"Failed to update %s\", argv[2]);\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482969","messageId":"20231010123847.2777056-12-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 11/14] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:44Z","receivedAt":"2023-10-10T12:39:41Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of the fixed \"<oldbase> <branch>\" arguments, the replay\ncommand now accepts \"<revision-range>...\" arguments in a similar\nway as many other Git commands. This makes its interface more\nstandard and more flexible.\n\nThis also enables many revision related options accepted and\neaten by setup_revisions(). If the replay command was a high level\none or had a high level mode, it would make sense to restrict some\nof the possible options, like those generating non-contiguous\nhistory, as they could be confusing for most users.\n\nAlso as the interface of the command is now mostly finalized,\nwe can add some documentation as well as testcases to make sure\nthe command will continue to work as designed in the future.\n\nWe only document the rev-list related options among all the\nrevision related options that are now accepted, as the rev-list\nrelated ones are probably the most useful for now.\n\nHelped-by: Dragan Simic <dsimic@manjaro.org>\nHelped-by: Linus Arver <linusa@google.com>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             | 92 ++++++++++++++++++++++++\n builtin/replay.c                         | 21 ++----\n t/t3650-replay-basics.sh                 | 83 +++++++++++++++++++++\n t/t6429-merge-sequence-rename-caching.sh | 18 ++---\n 4 files changed, 188 insertions(+), 26 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100755 t/t3650-replay-basics.sh\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nnew file mode 100644\nindex 0000000000..86be363f9b\n--- /dev/null\n+++ b/Documentation/git-replay.txt\n@@ -0,0 +1,92 @@\n+git-replay(1)\n+=============\n+\n+NAME\n+----\n+git-replay - Replay commits on a new base, works on bare repos too\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git replay' --onto <newbase> <revision-range>...\n+\n+DESCRIPTION\n+-----------\n+\n+Takes a range of commits and replays them onto a new location. Leaves\n+the working tree and the index untouched, and updates no\n+references. The output of this command is meant to be used as input to\n+`git update-ref --stdin`, which would update the relevant branches\n+(see the OUTPUT section below).\n+\n+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n+\n+OPTIONS\n+-------\n+\n+--onto <newbase>::\n+\tStarting point at which to create the new commits.  May be any\n+\tvalid commit, and not just an existing branch name.\n++\n+The update-ref command(s) in the output will update the branch(es) in\n+the revision range to point at the new commits, similar to the way how\n+`git rebase --update-refs` updates multiple branches in the affected\n+range.\n+\n+<revision-range>::\n+\tRange of commits to replay; see \"Specifying Ranges\" in\n+\tlinkgit:git-rev-parse and the \"Commit Limiting\" options below.\n+\n+include::rev-list-options.txt[]\n+\n+OUTPUT\n+------\n+\n+When there are no conflicts, the output of this command is usable as\n+input to `git update-ref --stdin`.  It is of the form:\n+\n+\tupdate refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+\tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+\tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+\n+where the number of refs updated depends on the arguments passed and\n+the shape of the history being replayed.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted replay, the exit status is 0.  When\n+the replay has conflicts, the exit status is 1.  If the replay is not\n+able to complete (or start) due to some kind of error, the exit status\n+is something other than 0 or 1.\n+\n+EXAMPLES\n+--------\n+\n+To simply rebase `mybranch` onto `target`:\n+\n+------------\n+$ git replay --onto target origin/main..mybranch\n+update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n+------------\n+\n+When calling `git replay`, one does not need to specify a range of\n+commits to replay using the syntax `A..B`; any range expression will\n+do:\n+\n+------------\n+$ git replay --onto origin/main ^base branch1 branch2 branch3\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+------------\n+\n+This will simultaneously rebase `branch1`, `branch2`, and `branch3`,\n+all commits they have since `base`, playing them on top of\n+`origin/main`. These three branches may have commits on top of `base`\n+that they have in common, but that does not need to be the case.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex ace9ac6df0..213ac2764e 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,7 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -118,16 +117,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL;\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 merge_result result;\n-\tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -145,20 +142,13 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tif (argc != 3) {\n-\t\terror(_(\"bad number of arguments\"));\n-\t\tusage_with_options(replay_usage, replay_options);\n-\t}\n-\n \tonto = peel_committish(onto_name);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n-\n-\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n-\t\tret = error(_(\"unhandled options\"));\n+\targc = setup_revisions(argc, argv, &revs, NULL);\n+\tif (argc > 1) {\n+\t\tret = error(_(\"unrecognized argument: %s\"), argv[1]);\n \t\tgoto cleanup;\n \t}\n \n@@ -168,8 +158,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n-\tstrvec_clear(&rev_walk_args);\n-\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -211,7 +199,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tret = result.clean;\n \n cleanup:\n-\tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nnew file mode 100755\nindex 0000000000..a1da4f9ef9\n--- /dev/null\n+++ b/t/t3650-replay-basics.sh\n@@ -0,0 +1,83 @@\n+#!/bin/sh\n+\n+test_description='basic git replay tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+GIT_AUTHOR_NAME=author@name\n+GIT_AUTHOR_EMAIL=bogus@email@address\n+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\n+\tgit switch -c topic1 &&\n+\ttest_commit C &&\n+\tgit switch -c topic2 &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit switch topic1 &&\n+\ttest_commit F &&\n+\tgit switch -c topic3 &&\n+\ttest_commit G &&\n+\ttest_commit H &&\n+\tgit switch -c topic4 main &&\n+\ttest_commit I &&\n+\ttest_commit J &&\n+\n+\tgit switch -c next main &&\n+\ttest_commit K &&\n+\tgit merge -m \"Merge topic1\" topic1 &&\n+\tgit merge -m \"Merge topic2\" topic2 &&\n+\tgit merge -m \"Merge topic3\" topic3 &&\n+\t>evil &&\n+\tgit add evil &&\n+\tgit commit --amend &&\n+\tgit merge -m \"Merge topic4\" topic4 &&\n+\n+\tgit switch main &&\n+\ttest_commit L &&\n+\ttest_commit M &&\n+\n+\tgit switch -c conflict B &&\n+\ttest_commit C.conflict C.t conflict\n+'\n+\n+test_expect_success 'setup bare' '\n+\tgit clone --bare . bare\n+'\n+\n+test_expect_success 'using replay to rebase two branches, one on top of other' '\n+\tgit replay --onto main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n+\tgit -C bare replay --onto main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n+test_expect_success 'using replay to rebase with a conflict' '\n+\ttest_expect_code 1 git replay --onto topic1 B..conflict\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase with a conflict' '\n+\ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n+'\n+\n+test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 099aefeffc..0f39ed0d08 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,7 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -201,7 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -279,7 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -357,7 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1..topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,7 +456,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -523,7 +523,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -626,7 +626,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -685,7 +685,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482970","messageId":"20231010123847.2777056-13-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 12/14] replay: add --advance or 'cherry-pick' mode","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:45Z","receivedAt":"2023-10-10T12:39:43Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere is already a 'rebase' mode with `--onto`. Let's add an 'advance' or\n'cherry-pick' mode with `--advance`. This new mode will make the target\nbranch advance as we replay commits onto it.\n\nThe replayed commits should have a single tip, so that it's clear where\nthe target branch should be advanced. If they have more than one tip,\nthis new mode will error out.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt |  41 ++++++--\n builtin/replay.c             | 185 +++++++++++++++++++++++++++++++++--\n t/t3650-replay-basics.sh     |  34 +++++++\n 3 files changed, 243 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 86be363f9b..2c09232c8f 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - Replay commits on a new base, works on bare repos too\n SYNOPSIS\n --------\n [verse]\n-'git replay' --onto <newbase> <revision-range>...\n+'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -29,14 +29,25 @@ OPTIONS\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n +\n-The update-ref command(s) in the output will update the branch(es) in\n-the revision range to point at the new commits, similar to the way how\n-`git rebase --update-refs` updates multiple branches in the affected\n-range.\n+When `--onto` is specified, the update-ref command(s) in the output will\n+update the branch(es) in the revision range to point at the new\n+commits, similar to the way how `git rebase --update-refs` updates\n+multiple branches in the affected range.\n+\n+--advance <branch>::\n+\tStarting point at which to create the new commits; must be a\n+\tbranch name.\n++\n+When `--advance` is specified, the update-ref command(s) in the output\n+will update the branch passed as an argument to `--advance` to point at\n+the new commits (in other words, this mimics a cherry-pick operation).\n \n <revision-range>::\n-\tRange of commits to replay; see \"Specifying Ranges\" in\n-\tlinkgit:git-rev-parse and the \"Commit Limiting\" options below.\n+\tRange of commits to replay. More than one <revision-range> can\n+\tbe passed, but in `--advance <branch>` mode, they should have\n+\ta single tip, so that it's clear where <branch> should point\n+\tto. See \"Specifying Ranges\" in linkgit:git-rev-parse and the\n+\t\"Commit Limiting\" options below.\n \n include::rev-list-options.txt[]\n \n@@ -51,7 +62,9 @@ input to `git update-ref --stdin`.  It is of the form:\n \tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n \n where the number of refs updated depends on the arguments passed and\n-the shape of the history being replayed.\n+the shape of the history being replayed.  When using `--advance`, the\n+number of refs updated is always one, but for `--onto`, it can be one\n+or more (rebasing multiple branches simultaneously is supported).\n \n EXIT STATUS\n -----------\n@@ -71,6 +84,18 @@ $ git replay --onto target origin/main..mybranch\n update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n ------------\n \n+To cherry-pick the commits from mybranch onto target:\n+\n+------------\n+$ git replay --advance target origin/main..mybranch\n+update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH}\n+------------\n+\n+Note that the first two examples replay the exact same commits and on\n+top of the exact same new base, they only differ in that the first\n+provides instructions to make mybranch point at the new commits and\n+the second provides instructions to make target point at them.\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 213ac2764e..d458837e9c 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,6 +14,7 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"strmap.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -82,6 +83,146 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+struct ref_info {\n+\tstruct commit *onto;\n+\tstruct strset positive_refs;\n+\tstruct strset negative_refs;\n+\tint positive_refexprs;\n+\tint negative_refexprs;\n+};\n+\n+static void get_ref_information(struct rev_cmdline_info *cmd_info,\n+\t\t\t\tstruct ref_info *ref_info)\n+{\n+\tint i;\n+\n+\tref_info->onto = NULL;\n+\tstrset_init(&ref_info->positive_refs);\n+\tstrset_init(&ref_info->negative_refs);\n+\tref_info->positive_refexprs = 0;\n+\tref_info->negative_refexprs = 0;\n+\n+\t/*\n+\t * When the user specifies e.g.\n+\t *   git replay origin/main..mybranch\n+\t *   git replay ^origin/next mybranch1 mybranch2\n+\t * we want to be able to determine where to replay the commits.  In\n+\t * these examples, the branches are probably based on an old version\n+\t * of either origin/main or origin/next, so we want to replay on the\n+\t * newest version of that branch.  In contrast we would want to error\n+\t * out if they ran\n+\t *   git replay ^origin/master ^origin/next mybranch\n+\t *   git replay mybranch~2..mybranch\n+\t * the first of those because there's no unique base to choose, and\n+\t * the second because they'd likely just be replaying commits on top\n+\t * of the same commit and not making any difference.\n+\t */\n+\tfor (i = 0; i < cmd_info->nr; i++) {\n+\t\tstruct rev_cmdline_entry *e = cmd_info->rev + i;\n+\t\tstruct object_id oid;\n+\t\tconst char *refexpr = e->name;\n+\t\tchar *fullname = NULL;\n+\t\tint can_uniquely_dwim = 1;\n+\n+\t\tif (*refexpr == '^')\n+\t\t\trefexpr++;\n+\t\tif (repo_dwim_ref(the_repository, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)\n+\t\t\tcan_uniquely_dwim = 0;\n+\n+\t\tif (e->flags & BOTTOM) {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->negative_refs, fullname);\n+\t\t\tif (!ref_info->negative_refexprs)\n+\t\t\t\tref_info->onto = lookup_commit_reference_gently(the_repository,\n+\t\t\t\t\t\t\t\t\t\t&e->item->oid, 1);\n+\t\t\tref_info->negative_refexprs++;\n+\t\t} else {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->positive_refs, fullname);\n+\t\t\tref_info->positive_refexprs++;\n+\t\t}\n+\n+\t\tfree(fullname);\n+\t}\n+}\n+\n+static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n+\t\t\t\t  const char *onto_name,\n+\t\t\t\t  const char **advance_name,\n+\t\t\t\t  struct commit **onto,\n+\t\t\t\t  struct strset **update_refs)\n+{\n+\tstruct ref_info rinfo;\n+\n+\tget_ref_information(cmd_info, &rinfo);\n+\tif (!rinfo.positive_refexprs)\n+\t\tdie(_(\"need some commits to replay\"));\n+\tif (onto_name && *advance_name)\n+\t\tdie(_(\"--onto and --advance are incompatible\"));\n+\telse if (onto_name) {\n+\t\t*onto = peel_committish(onto_name);\n+\t\tif (rinfo.positive_refexprs <\n+\t\t    strset_get_size(&rinfo.positive_refs))\n+\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t} else if (*advance_name) {\n+\t\tstruct object_id oid;\n+\t\tchar *fullname = NULL;\n+\n+\t\t*onto = peel_committish(*advance_name);\n+\t\tif (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name),\n+\t\t\t     &oid, &fullname, 0) == 1) {\n+\t\t\t*advance_name = fullname;\n+\t\t} else {\n+\t\t\tdie(_(\"argument to --advance must be a reference\"));\n+\t\t}\n+\t\tif (rinfo.positive_refexprs > 1)\n+\t\t\tdie(_(\"cannot advance target with multiple sources because ordering would be ill-defined\"));\n+\t} else {\n+\t\tint positive_refs_complete = (\n+\t\t\trinfo.positive_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.positive_refs));\n+\t\tint negative_refs_complete = (\n+\t\t\trinfo.negative_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.negative_refs));\n+\t\t/*\n+\t\t * We need either positive_refs_complete or\n+\t\t * negative_refs_complete, but not both.\n+\t\t */\n+\t\tif (rinfo.negative_refexprs > 0 &&\n+\t\t    positive_refs_complete == negative_refs_complete)\n+\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\tif (negative_refs_complete) {\n+\t\t\tstruct hashmap_iter iter;\n+\t\t\tstruct strmap_entry *entry;\n+\n+\t\t\tif (rinfo.negative_refexprs == 0)\n+\t\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t\t\telse if (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\t\telse if (rinfo.positive_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\n+\t\t\t/* Only one entry, but we have to loop to get it */\n+\t\t\tstrset_for_each_entry(&rinfo.negative_refs,\n+\t\t\t\t\t      &iter, entry) {\n+\t\t\t\t*advance_name = entry->key;\n+\t\t\t}\n+\t\t} else { /* positive_refs_complete */\n+\t\t\tif (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine correct base for --onto\"));\n+\t\t\tif (rinfo.negative_refexprs == 1)\n+\t\t\t\t*onto = rinfo.onto;\n+\t\t}\n+\t}\n+\tif (!*advance_name) {\n+\t\t*update_refs = xcalloc(1, sizeof(**update_refs));\n+\t\t**update_refs = rinfo.positive_refs;\n+\t\tmemset(&rinfo.positive_refs, 0, sizeof(**update_refs));\n+\t}\n+\tstrset_clear(&rinfo.negative_refs);\n+\tstrset_clear(&rinfo.positive_refs);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n \t\t\t\t\t  struct commit *last_commit,\n \t\t\t\t\t  struct merge_options *merge_opt,\n@@ -114,20 +255,26 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n-\tstruct commit *onto;\n+\tconst char *advance_name = NULL;\n+\tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL;\n+\n \tstruct rev_info revs;\n+\tstruct commit *last_commit = NULL;\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n+\tstruct strset *update_refs = NULL;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <revision-range>...\"),\n+\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"advance\", &advance_name,\n+\t\t\t   N_(\"branch\"),\n+\t\t\t   N_(\"make replay advance given branch\")),\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n@@ -137,13 +284,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n \t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n \n-\tif (!onto_name) {\n-\t\terror(_(\"option --onto is mandatory\"));\n+\tif (!onto_name && !advance_name) {\n+\t\terror(_(\"option --onto or --advance is mandatory\"));\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tonto = peel_committish(onto_name);\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \targc = setup_revisions(argc, argv, &revs, NULL);\n@@ -158,6 +303,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n+\tdetermine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n+\t\t\t      &onto, &update_refs);\n+\n+\tif (!onto) /* FIXME: Should handle replaying down to root commit */\n+\t\tdie(\"Replaying down to root commit is not supported yet!\");\n+\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -166,6 +317,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n+\n \tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n@@ -180,12 +332,15 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Update any necessary branches */\n+\t\tif (advance_name)\n+\t\t\tcontinue;\n \t\tdecoration = get_name_decoration(&commit->object);\n \t\tif (!decoration)\n \t\t\tcontinue;\n-\n \t\twhile (decoration) {\n-\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n+\t\t\t    strset_contains(update_refs, decoration->name)) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\n@@ -195,10 +350,22 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\t/* In --advance mode, advance the target ref */\n+\tif (result.clean == 1 && advance_name) {\n+\t\tprintf(\"update %s %s %s\\n\",\n+\t\t       advance_name,\n+\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t       oid_to_hex(&onto->object.oid));\n+\t}\n+\n \tmerge_finalize(&merge_opt, &result);\n \tret = result.clean;\n \n cleanup:\n+\tif (update_refs) {\n+\t\tstrset_clear(update_refs);\n+\t\tfree(update_refs);\n+\t}\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex a1da4f9ef9..68a87e7803 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -80,4 +80,38 @@ test_expect_success 'using replay on bare repo to rebase with a conflict' '\n \ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n '\n \n+test_expect_success 'using replay to perform basic cherry-pick' '\n+\t# The differences between this test and previous ones are:\n+\t#   --advance vs --onto\n+\t# 2nd field of result is refs/heads/main vs. refs/heads/topic2\n+\t# 4th field of result is hash for main instead of hash for topic2\n+\n+\tgit replay --advance main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/main \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse main >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n+\tgit -C bare replay --advance main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n+test_expect_success 'replay on bare repo fails with both --advance and --onto' '\n+\ttest_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare\n+'\n+\n+test_expect_success 'replay fails when both --advance and --onto are omitted' '\n+\ttest_must_fail git replay topic1..topic2 >result\n+'\n+\n test_done\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482971","messageId":"20231010123847.2777056-14-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 13/14] replay: add --contained to rebase contained branches","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:46Z","receivedAt":"2023-10-10T12:39:46Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's add a `--contained` option that can be used along with\n`--onto` to rebase all the branches contained in the <revision-range>\nargument.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt | 12 +++++++++++-\n builtin/replay.c             | 12 ++++++++++--\n t/t3650-replay-basics.sh     | 29 +++++++++++++++++++++++++++++\n 3 files changed, 50 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 2c09232c8f..e9697fcfb6 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - Replay commits on a new base, works on bare repos too\n SYNOPSIS\n --------\n [verse]\n-'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n+'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -96,6 +96,16 @@ top of the exact same new base, they only differ in that the first\n provides instructions to make mybranch point at the new commits and\n the second provides instructions to make target point at them.\n \n+What if you have a stack of branches, one depending upon another, and\n+you'd really like to rebase the whole set?\n+\n+------------\n+$ git replay --contained --onto origin/main origin/main..tipbranch\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}\n+------------\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex d458837e9c..12689f1c89 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -258,6 +258,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tconst char *advance_name = NULL;\n \tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n+\tint contained = 0;\n \n \tstruct rev_info revs;\n \tstruct commit *last_commit = NULL;\n@@ -268,7 +269,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n+\t\tN_(\"git replay ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -278,6 +279,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_BOOL(0, \"contained\", &contained,\n+\t\t\t N_(\"advance all branches contained in revision-range\")),\n \t\tOPT_END()\n \t};\n \n@@ -289,6 +292,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n+\tif (advance_name && contained)\n+\t\tdie(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t    \"--advance\", \"--contained\");\n+\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \targc = setup_revisions(argc, argv, &revs, NULL);\n@@ -340,7 +347,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tcontinue;\n \t\twhile (decoration) {\n \t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n-\t\t\t    strset_contains(update_refs, decoration->name)) {\n+\t\t\t    (contained || strset_contains(update_refs,\n+\t\t\t\t\t\t\t  decoration->name))) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 68a87e7803..d6286f9580 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -114,4 +114,33 @@ test_expect_success 'replay fails when both --advance and --onto are omitted' '\n \ttest_must_fail git replay topic1..topic2 >result\n '\n \n+test_expect_success 'using replay to also rebase a contained branch' '\n+\tgit replay --contained --onto main main..topic3 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines H G F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic1 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic1 >>expect &&\n+\tprintf \"update refs/heads/topic3 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic3 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to also rebase a contained branch' '\n+\tgit -C bare replay --contained --onto main main..topic3 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n test_done\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482972","messageId":"20231010123847.2777056-15-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v5 14/14] replay: stop assuming replayed branches do not diverge","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:38:47Z","receivedAt":"2023-10-10T12:39:49Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command is able to replay multiple branches but when some of\nthem are based on other replayed branches, their commit should be\nreplayed onto already replayed commits.\n\nFor this purpose, let's store the replayed commit and its original\ncommit in a key value store, so that we can easily find and reuse a\nreplayed commit instead of the original one.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 44 ++++++++++++++++++++++++++--------\n t/t3650-replay-basics.sh | 52 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 86 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 12689f1c89..f74261b75e 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -223,20 +223,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n \tstrset_clear(&rinfo.positive_refs);\n }\n \n+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,\n+\t\t\t\t    struct commit *commit,\n+\t\t\t\t    struct commit *fallback)\n+{\n+\tkhint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);\n+\tif (pos == kh_end(replayed_commits))\n+\t\treturn fallback;\n+\treturn kh_value(replayed_commits, pos);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n-\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  kh_oid_map_t *replayed_commits,\n+\t\t\t\t\t  struct commit *onto,\n \t\t\t\t\t  struct merge_options *merge_opt,\n \t\t\t\t\t  struct merge_result *result)\n {\n-\tstruct commit *base;\n+\tstruct commit *base, *replayed_base;\n \tstruct tree *pickme_tree, *base_tree;\n \n \tbase = pickme->parents->item;\n+\treplayed_base = mapped_commit(replayed_commits, base, onto);\n \n+\tresult->tree = repo_get_commit_tree(the_repository, replayed_base);\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n-\tmerge_opt->branch1 = short_commit_name(last_commit);\n+\tmerge_opt->branch1 = short_commit_name(replayed_base);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -250,7 +263,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tmerge_opt->ancestor = NULL;\n \tif (!result->clean)\n \t\treturn NULL;\n-\treturn create_commit(result->tree, pickme, last_commit);\n+\treturn create_commit(result->tree, pickme, replayed_base);\n }\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n@@ -266,6 +279,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n \tstruct strset *update_refs = NULL;\n+\tkh_oid_map_t *replayed_commits;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n@@ -324,21 +338,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\n-\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n+\treplayed_commits = kh_init_oid_map();\n \twhile ((commit = get_revision(&revs))) {\n \t\tconst struct name_decoration *decoration;\n+\t\tkhint_t pos;\n+\t\tint hr;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tlast_commit = pick_regular_commit(commit, replayed_commits, onto,\n+\t\t\t\t\t\t  &merge_opt, &result);\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Record commit -> last_commit mapping */\n+\t\tpos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);\n+\t\tif (hr == 0)\n+\t\t\tBUG(\"Duplicate rewritten commit: %s\\n\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tkh_value(replayed_commits, pos) = last_commit;\n+\n \t\t/* Update any necessary branches */\n \t\tif (advance_name)\n \t\t\tcontinue;\n@@ -367,13 +390,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-\tret = result.clean;\n-\n-cleanup:\n+\tkh_destroy_oid_map(replayed_commits);\n \tif (update_refs) {\n \t\tstrset_clear(update_refs);\n \t\tfree(update_refs);\n \t}\n+\tret = result.clean;\n+\n+cleanup:\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex d6286f9580..389670262e 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -143,4 +143,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to rebase multiple divergent branches' '\n+\tgit replay --onto main ^topic1 topic2 topic4 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines J I M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\tprintf \"update refs/heads/topic4 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic4 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '\n+\tgit -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&\n+\n+\ttest_line_count = 4 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\t>expect &&\n+\tfor i in 2 1 3 4\n+\tdo\n+\t\tprintf \"update refs/heads/topic$i \" >>expect &&\n+\t\tprintf \"%s \" $(grep topic$i result | cut -f 3 -d \" \") >>expect &&\n+\t\tgit -C bare rev-parse topic$i >>expect || return 1\n+\tdone &&\n+\n+\ttest_cmp expect result &&\n+\n+\ttest_write_lines F C M L B A >expect1 &&\n+\ttest_write_lines E D C M L B A >expect2 &&\n+\ttest_write_lines H G F C M L B A >expect3 &&\n+\ttest_write_lines J I M L B A >expect4 &&\n+\n+\tfor i in 1 2 3 4\n+\tdo\n+\t\tgit -C bare log --format=%s $(grep topic$i result | cut -f 3 -d \" \") >actual &&\n+\t\ttest_cmp expect$i actual || return 1\n+\tdone\n+'\n+\n test_done\n-- \n2.42.0.339.g663cbc8ab1\n\n"},{"id":"482973","messageId":"CAP8UFD2USo_Lg14kVgqXpyt1iLfAhoW7w2EMpbqQRvvFkxudSQ@mail.gmail.com","threadId":"59560","inReplyTo":"52277471-4ddd-b2e0-62ca-c2a5b59ae418@gmx.de","subject":"Re: [PATCH v4 02/15] replay: introduce new builtin","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:42:22Z","receivedAt":"2023-10-10T12:42:40Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Dscho,\n\nOn Thu, Sep 7, 2023 at 1:01 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Christian & Elijah,\n>\n> On Thu, 7 Sep 2023, Christian Couder wrote:\n>\n> > diff --git a/command-list.txt b/command-list.txt\n> > index 54b2a50f5f..d74836ab21 100644\n> > --- a/command-list.txt\n> > +++ b/command-list.txt\n> > @@ -160,6 +160,7 @@ git-reflog                              ancillarymanipulators           complete\n> >  git-remote                              ancillarymanipulators           complete\n> >  git-repack                              ancillarymanipulators           complete\n> >  git-replace                             ancillarymanipulators           complete\n> > +git-replay                              mainporcelain           history\n>\n> I recall this having come up before, but in light of the still active\n> discussion revolving around command-line parameter validation, I would\n> strongly advise removing `git replay` from the main page and instead go\n> for plumbingmanipulators.\n\nOk, I have done that in the version 5 I just sent.\n"},{"id":"482974","messageId":"CAP8UFD0QhNzWims9i2tz1XsDjQEApecXZEfC-vmEHopqZun81w@mail.gmail.com","threadId":"59560","inReplyTo":"58daa706-7efb-51dd-9061-202ef650b96a@gmx.de","subject":"Re: [PATCH v4 06/15] replay: don't simplify history","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:43:54Z","receivedAt":"2023-10-10T12:44:11Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Dscho,\n\nOn Thu, Sep 7, 2023 at 1:02 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Christian & Elijah,\n>\n> On Thu, 7 Sep 2023, Christian Couder wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Let's set the rev walking options we need after calling\n> > setup_revisions() instead of before. This makes it clearer which options\n> > we need.\n>\n> In light of the currently open issue about command-line validation, this\n> change does more than this paragraph lets on: It hardcodes certain\n> settings, overriding (silently) any rev-list options the user might have\n> passed.\n>\n> Is there any chance that we can avoid this change?\n\nIn the version 5 I just sent, I have changed the commit message and the code.\n\nThe commit messages says:\n\n\"\n   replay: change rev walking options\n\n   Let's set the rev walking options we need after calling\n   setup_revisions() instead of before. This enforces options we always\n   want.\n\n   We want the command to work from older commits to newer ones by default,\n   but we are Ok with letting users reverse that, using --reverse, if that's\n   what they really want.\n\n   Also we don't want history simplification, as we want to deal with all\n   the commits in the affected range.\n\"\n\nSo about \"revs.reverse\" the code is now:\n\n  revs.reverse = !revs.reverse;\n\nwhich seems to be what you suggested elsewhere.\n\nApart from that I think it's fair to enforce some values for a few\noptions. This way we can make the command work the way we want by\ndefault, get consistent behavior and avoid users shooting themselves\nin the foot for now. If more flexibility is needed and useful in the\nfuture, then we might allow it in future patches with proper\njustification, tests and docs. There is still a lot of flexibility\nleft especially as the patch that disallowed some rev walking options\nand pathspecs has been removed as you suggested.\n\n> > Also we don't want history simplification, as we want to deal with all\n> > the commits in the affected range.\n>\n> This, however, is a good change. It deserves to live in its own commit,\n> with its own commit message, in particular because it is not obvious from\n> the attribute names which ones we're talking about (I guess it's `limited`\n> and `simplify_history`, not just the latter.\n\nWe only enforce \"revs.sort_order\", \"revs.topo_order\" and yeah\n\"revs.simplify_history\".\n\nAlso I don't think it makes much sense to separate these changes in\ndifferent commits/patches. I am Ok to improve the commit message if\nyou think it's worth it, but the patch series is designed to make it\neasy to review the changes from the \"fast-rebase\" test-tool to a good\n\"replay\" plumbing command, and I don't think separating those related\nchanges would improve things much.\n"},{"id":"482975","messageId":"CAP8UFD213MwZmBuK0At5r0O8tHYDnxRTkw9AHak9RwDBdVVq_A@mail.gmail.com","threadId":"59560","inReplyTo":"f412f62bf830b38a296b59ac3470099a@manjaro.org","subject":"Re: [PATCH v3 11/15] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:44:35Z","receivedAt":"2023-10-10T12:44:55Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Sep 7, 2023 at 11:02 PM Dragan Simic <dsimic@manjaro.org> wrote:\n>\n> On 2023-09-07 10:32, Christian Couder wrote:\n> > On Thu, Jun 22, 2023 at 12:03 PM Toon Claes <toon@iotcl.com> wrote:\n> >>\n> >> Christian Couder <christian.couder@gmail.com> writes:\n> >>\n> >> > +DESCRIPTION\n> >> > +-----------\n> >> > +\n> >> > +Takes a range of commits, and replays them onto a new location.  Does\n> >> > +not touch the working tree or index, and does not update any\n> >> > +references.  However, the output of this command is meant to be used\n> >>\n> >> Small suggestion here:\n> >>\n> >> Takes a range of commits, and replays them onto a new location.  Does\n> >> neither touch the working tree nor index, and does not update any\n> >> references.\n> >\n> > I am not a native speaker, so I am not sure what's best here. I find\n> > your suggestion a bit less clear though, so until a native speaker\n> > agrees with it or maybe finds something even better, I prefer to leave\n> > it as-is.\n>\n> I'm also not a native English speaker, but I spent about 2.5 years\n> contributing a whole lot to English Wikipedia, so I'd dare to say I've\n> honed my English skills rather well.  Thus, here's my take on this:\n>\n>      Takes a range of commits and replays them onto a new location.\n>      Leaves the working tree and the index untouched, and updates no\n>      references.  The output of this command is to be used...\n>\n> This is written in a concise yet slightly imperative way, which should\n> be suitable for the purpose.  I hope you agree.\n\nI agree and I like it, so I have changed it to the above in version 5\nI just sent.\n\nThanks!\n"},{"id":"482977","messageId":"CAP8UFD3f94NnBgkzkezcALJxamsz+-oPfqKe8XGNMuJ+g--z6g@mail.gmail.com","threadId":"59560","inReplyTo":"owlyo7icl1g3.fsf@fine.c.googlers.com","subject":"Re: [PATCH v4 11/15] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:48:00Z","receivedAt":"2023-10-10T12:48:22Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Sep 9, 2023 at 12:55 AM Linus Arver <linusa@google.com> wrote:\n>\n> Hi Christian,\n>\n> I am only reviewing the docs. To assume the mindset of a Git user\n> unfamiliar with this command, I purposely did not read the cover letter\n> until after this review was done.\n\nOk, thanks!\n\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > [...]\n> >\n> > diff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\n> > new file mode 100644\n> > index 0000000000..9a2087b01a\n> > --- /dev/null\n> > +++ b/Documentation/git-replay.txt\n> > @@ -0,0 +1,90 @@\n> > +git-replay(1)\n> > +=============\n> > +\n> > +NAME\n> > +----\n> > +git-replay - Replay commits on a different base, without touching working tree\n>\n> How about using the same language (\"new location\") as in the DESCRIPTION\n> heading?\n\nI actually think that \"base\" is better than \"location\", also perhaps\nsaying things a bit differently than in the description can help\nbetter understand the command.\n\nI am Ok with replacing \"different\" with \"new\", mainly because it is\nshorter, though.\n\n> Also, the \"without touching working tree\" part is incomplete\n> because as explained later on, the index and refs are also left alone.\n\nI agree that it is incomplete.\n\n> How about just \"safely\"?\n\nI think that we want to say that the command can work on bare repos,\nand I don't think \"safely\" conveys that meaning well. So now it is:\n\n\"git-replay - Replay commits on a new base, works on bare repos too\"\n\n> > +SYNOPSIS\n> > +--------\n> > +[verse]\n> > +'git replay' --onto <newbase> <revision-range>...\n> > +\n> > +DESCRIPTION\n> > +-----------\n> > +\n> > +Takes a range of commits, and replays them onto a new location.\n>\n> OK.\n>\n> > Does\n> > +not touch the working tree or index, and does not update any\n> > +references.\n>\n> How about this version?\n>\n>     The new commits are created without modifying the working tree,\n>     index, or any references.\n\nI agree it's nicer, but I prefer Dragan Simic's version.\n\n> Also, by \"references\" we mean the refs in \".git/refs/*\". In the\n> gitrevisions man page we use the term \"refnames\" to refer to these bits,\n> so maybe \"refnames\" is better than \"references\"? The simpler \"branches\"\n> is another option.\n\n\"references\" seems to be used much more often in the docs than\n\"refnames\" (204 vs 18 occurrences). And deciding between \"refnames\"\nand \"references\" in the docs is a global issue clearly outside the\nscope of this series. So for now I will keep \"references\".\n\nI don't like \"branches\" as I think tags and other refs are concerned too.\n\n> > However, the output of this command is meant to be used\n> > +as input to `git update-ref --stdin`, which would update the relevant\n> > +branches.\n>\n> Before we get to this sentence, it would be great to explain why this\n> command is useful (what problem does it solve)?\n>\n> Also, it would help to add \"(see OUTPUT section below)\" as a\n> navigational aid in case some readers are wondering what the output\n> looks like (and have not yet gotten to that section).\n>\n> I've noticed that you're using the phrase \"starting point\" in the\n> OPTIONS section. I think this is better than \"location\" or \"base\" (FWIW\n> we started using \"starting point\" in 0a02ca2383 (SubmittingPatches:\n> simplify guidance for choosing a starting point, 2023-07-14)).\n>\n> The following is a version of this section that attempts to address my\n> comments above (I've included the other bits already reviewed earlier to\n> make it easier to read):\n>\n>     Takes a range of commits, and replays them onto a new starting\n>     point. The new commits are created without modifying the working\n>     tree, index, or any branches. If there are branches that point to\n>     the commits in <revision-range>, list them in the output with both\n>     the original commit hash and the corresponding (replayed) commit\n>     hash (see OUTPUT section below) .\n\nI like the \"(see OUTPUT section below)\" part, but I don't like the\nfact that passing the output as input to `git update-ref --stdin`\nisn't mentioned anymore.\n\nSo I have just added \"(see the OUTPUT section below)\".\n\n>     This command is like linkgit:git-rebase[1], but notably does not\n>     require a working tree or index.\n\nI don't quite like this as the command will later also be like\ncherry-pick (with the --advance mode).\n\n> This means you can run this command\n>     in a bare repo (useful for server-side environments). And because\n>     nothing is modified (only new commits are created), it's like a \"dry\n>     run\" rebase.\n\nBare repos are now mentioned in the title of the page, so I am not\nsure it's worth mentioning.\n\n>     By combining this command with `git update-ref --stdin`, the\n>     relevant branches can be updated. That is, the branches that were\n>     pointing originally to the commits given in the <revision-range>\n>     will be updated to point to the replayed commits. This is similar to\n>     the way how `git rebase --update-refs` updates multiple branches in\n>     the affected range.\n\nI am not sure this is very useful, also I'm afraid that with all the\nchanges you propose the description section would be a bit too long.\n\nMaybe you should propose some of the changes above in a follow up\npatch series after this patch series has been merged.\n\n> > +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n> > +\n> > +OPTIONS\n> > +-------\n> > +\n> > +--onto <newbase>::\n>\n> How about 'starting-point' instead of 'newbase'?\n\nI think \"newbase\" is very short and to the point. With --onto we\nclearly want something that can do a rebase, so I think using\nsomething with \"base\" in it is a good choice here.\n\n> > +     Starting point at which to create the new commits.  May be any\n> > +     valid commit, and not just an existing branch name.\n>\n> Add \"See linkgit:gitrevisions[7].\" at the end?\n\nI don't think it's worth it to mention \"linkgit:gitrevisions[7]\"\neverywhere we can pass a revision. I think it makes the doc heavier\nthan it should be, especially here where the command is a plumbing one\nfor now and users should be quite experienced with Git already.\n\n> > +The update-ref command(s) in the output will update the branch(es) in\n> > +the revision range to point at the new commits, similar to the way how\n> > +`git rebase --update-refs` updates multiple branches in the affected\n> > +range.\n>\n> Ah, good example. I've moved this to my larger example above, so I don't\n> think this paragraph is needed here any more (it probably didn't belong\n> in OPTIONS anyway).\n\nI think it's important here to help users understand that with this\noption they have something very similar to \"rebase\".\n\n> > +<revision-range>::\n> > +     Range of commits to replay; see \"Specifying Ranges\" in\n> > +     linkgit:git-rev-parse.\n>\n> OK.\n>\n> > +OUTPUT\n> > +------\n> > +\n> > +When there are no conflicts, the output of this command is usable as\n> > +input to `git update-ref --stdin`.\n>\n> What happens if there are conflicts? Probably good to mention in the\n> DISCUSSION section. Some questions you may want to answer for the\n> reader:\n>\n> (1) Is git-replay an all-or-nothing operation? That is, if there are any\n> conflicts, is the output always empty (or do we still output those\n> branches that *can* be updated without conflicts)?\n>\n> (2) What is meant by \"conflict\" for git-replay? Is it the same meaning\n> as the case for git-rebase?\n>\n> For (1), in your cover letter under \"# Important limitations\" you say\n> \"No resumability\" but I am not sure if this means git-replay will output\n> *something* before exiting with an error, or simply nothing at all.\n\nIn a follow up series we might add options to generate some output in\ncase of conflicts. When we do that we will discuss this. So I think\nfor now it should be Ok to not talk more about the output in case of\nconflicts.\n\n> Speaking of the limitations section, perhaps it's worth pointing those\n> out under DISCUSSION as well?\n\nI don't think it's worth officially discussing the limitations in the\ndocs when some of them might be lifted soon after this series\ngraduates. The command is experimental, so I think users can\nunderstand it if everything is not spelled out. Also if we spell out\nthings too much, users might rely on what we say when it could\nactually change soon.\n\n> > It is basically of the form:\n>\n> Why \"basically\"? Are there cases where the output can be different than\n> the example given below? If not, then perhaps drop the word \"basically\"?\n\nOk, I have dropped \"basically\".\n\n> > +     update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n> > +     update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n> > +     update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n> > +\n> > +where the number of refs updated depends on the arguments passed and\n> > +the shape of the history being replayed.\n>\n> Let's use \"number of branches\" instead of \"number of refs\" here to be\n> consistent with the language elsewhere.\n\nI prefer \"refs\" or \"references\" here.\n\n> > +EXIT STATUS\n> > +-----------\n> > +\n> > +For a successful, non-conflicted replay, the exit status is 0.  When\n> > +the replay has conflicts, the exit status is 1.\n>\n> OK.\n>\n> > If the replay is not\n> > +able to complete (or start) due to some kind of error, the exit status\n> > +is something other than 0 or 1.\n>\n> Not sure how useful \"due to some kind of error\" is here --- presumably\n> the inability to replay is always due to some kind of error.\n\nI think here \"due to some kind of error\" means something else than a\nconflict which could also prevent the reply from fully \"working\".\n\n> Would it be worth including a brief explanation about why git-replay\n> might be unable to complete or start (is this behavior in practice a\n> common-enough thing to document here)?\n\nI don't think it's worth it at this step for this new command.\n\n> > +EXAMPLES\n> > +--------\n> > +\n> > +To simply rebase mybranch onto target:\n>\n> Looking at the CLI arguments, I think this phrase should be:\n>\n>     Replay the commits in `origin/main..mybranch` onto `target`:\n\nWe suppose that users can understand that \"rebase mybranch onto\ntarget\" means the same thing as what you suggest. Also I think it's\nuseful to associate \"rebase\" with \"--onto\" by deliberately using\n\"rebase\" here.\n\nHowever to make it a bit clearer, I agree that quoting `mybranch` and\n`target` is better, so I have changed it to:\n\n\"To simply rebase `mybranch` onto `target`:\"\n\n> > +------------\n> > +$ git replay --onto target origin/main..mybranch\n> > +update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n> > +------------\n> > +\n> > +When calling `git replay`, one does not need to specify a range of\n> > +commits to replay using the syntax `A..B`; any range expression will\n> > +do:\n> > +\n> > +------------\n> > +$ git replay --onto origin/main ^base branch1 branch2 branch3\n>\n> Instead of `base`, how about `olderbranch`?\n\nSorry but like above, I like \"base\" here.\n\n> > +update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n> > +update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n> > +update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n> > +------------\n> > +\n> > +This will simultaneously rebase branch1, branch2, and branch3 -- all\n> > +commits they have since base, playing them on top of origin/main.\n>\n> How about\n>\n>     This will rebase the commits in `branch1`, `branch2`, and `branch3`\n>     (excluding those in `base`), preplaying them on top of `origin/main`.\n>\n> > +These three branches may have commits on top of base that they have in\n> > +common, but that does not need to be the case.\n>\n> s/base/`base`\n\nI am Ok with quoting `branch1`, `branch2`, `branch3`, `base` and\n`origin/main`, but otherwise I prefer to keep the original wording.\n\n> > +GIT\n> > +---\n> > +Part of the linkgit:git[1] suite\n>\n> One question I do have is what happens if you run git-replay twice\n> (successfully)? Does the second invocation create another set of (new)\n> successfully replayed commits?\n\nYes, I think it does that.\n\n> I ask because I'm interested in informing\n> the readers of our docs about any potential pitfalls from abusing this\n> command by mistake.\n\nI appreciate your desire to give high quality docs to our users, but I\ndon't think it's a big pitfall and I think that this command is still\nvery much \"in the works\" and is also designed for experienced users\nfor now, so I am not sure it's the right time to spend too much time\non this.\n"},{"id":"482978","messageId":"CAP8UFD02xcDFeiR3GOqUovcHB6fXPmvrLQ=9QX8ZwNNJUwP6Dw@mail.gmail.com","threadId":"59560","inReplyTo":"owlyledelnnd.fsf@fine.c.googlers.com","subject":"Re: [PATCH v4 11/15] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:48:30Z","receivedAt":"2023-10-10T12:48:46Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sun, Sep 10, 2023 at 5:20 AM Linus Arver <linusa@google.com> wrote:\n>\n> Linus Arver <linusa@google.com> writes:\n>\n> >> +update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n> >> +update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n> >> +update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n> >> +------------\n> >> +\n> >> +This will simultaneously rebase branch1, branch2, and branch3 -- all\n> >> +commits they have since base, playing them on top of origin/main.\n> >\n> > How about\n> >\n> >     This will rebase the commits in `branch1`, `branch2`, and `branch3`\n> >     (excluding those in `base`), preplaying them on top of `origin/main`.\n>\n> Oops, I meant \"replaying\" not \"preplaying\". But also, perhaps the\n> following is simpler?\n>\n>     This will replay the commits in `branch1`, `branch2`, and `branch3`\n>     (excluding those in `base`), on top of `origin/main`.\n\nHere also I prefer to keep \"rebase\".\n"},{"id":"482979","messageId":"CAP8UFD0ck9GMGZve1SYY9_4dLbMzkdYUOF+WXQ7pdDPk02Rtxw@mail.gmail.com","threadId":"59560","inReplyTo":"03460733-0219-c648-5757-db1958f8042e@gmx.de","subject":"Re: [PATCH v4 11/15] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:49:39Z","receivedAt":"2023-10-10T12:50:08Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Sep 7, 2023 at 1:02 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Christian,\n>\n> It is a bit surprising to see the manual page added in _this_ patch, in\n> the middle of the series... I can live with it, though.\n\nIt's explained in the cover letter and a bit in some commit messages.\n\nFor example in the \"Possibly controversial issues\" section of the\ncover letter, there is:\n\n\"\n* when and where to add tests and docs: although t6429 has tests that\n  are changed to use the new command instead of the fast-rebase\n  test-tool command as soon as the former is introduced, there is no\n  specific test script and no doc for the new command until commit\n  11/15 when standard revision ranges are used. This is done to avoid\n  churn in tests and docs while the final form of the command hasn't\n  crystalized enough. Adding tests and doc at this point makes this\n  commit quite big and possibly more difficult to review than if they\n  were in separate commits though. On the other hand when tests and\n  docs are added in specific commits, some reviewers say it would be\n  better to introduce them when the related changes are made.\n\"\n\n> On Thu, 7 Sep 2023, Christian Couder wrote:\n\n> > +SYNOPSIS\n> > +--------\n> > +[verse]\n> > +'git replay' --onto <newbase> <revision-range>...\n>\n> We need to make it clear here, already in the SYNOPSIS, that this is\n> experimental. Let's add an `(EXPERIMENTAL!)` prefix here.\n\nI haven't done that as other commands marked as EXPERIMENTAL don't\nhave that in their SYNOPSIS.\n\n> > [...]\n> > diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\n> > new file mode 100755\n>\n> Just like the manual page, I would have expected this test to be\n> introduced earlier, and not piggy-backed onto one of the handful \"let's\n> turn fast-rebase into replay\" patches.\n\nThis is discussed above.\n"},{"id":"482980","messageId":"CAP8UFD0EUF2s+X4adBmRxk-GWKeqH1C6rBd-Xgqv1XHNYWB_gA@mail.gmail.com","threadId":"59560","inReplyTo":"f0e75d47-c277-9fbb-7bcd-53e4e5686f3c@gmx.de","subject":"Re: [PATCH v4 12/15] replay: disallow revision specific options and pathspecs","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:49:55Z","receivedAt":"2023-10-10T12:50:17Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Dscho,\n\nOn Thu, Sep 7, 2023 at 1:03 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n\n> As pointed out elsewhere in this mail thread, I consider this patch to do\n> more harm than good. After switching the command to plumbingmanipulators\n> it should be possible to just forego all command-line validation and leave\n> that job to the caller.\n>\n> Therefore I would love to see this patch dropped.\n\nOk, I have dropped this patch in version 5.\n"},{"id":"482981","messageId":"CAP8UFD327jQwTQLTfVe8CDaDiYHDB0t+as=rjeWQpjmE-iZQEg@mail.gmail.com","threadId":"59560","inReplyTo":"d07487c2-4ee6-3ffa-014d-418793a5a584@gmx.de","subject":"Re: [PATCH v4 00/15] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-10-10T12:50:18Z","receivedAt":"2023-10-10T12:50:36Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Dscho,\n\nOn Thu, Sep 7, 2023 at 1:04 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Christian,\n>\n> hope you had a restful vacation!\n\nYes, thanks! I hope you had a good summer too.\n\n> I left a bit of feedback and think that once my concerns are addressed, a\n> v5 will be ready for `next`.\n\nThanks for your review!\n\nI think I have addressed most (if not all) of your concerns in version 5.\n"},{"id":"482983","messageId":"71ddf91938aee4d352f02826bec375e1@manjaro.org","threadId":"59560","inReplyTo":"CAP8UFD213MwZmBuK0At5r0O8tHYDnxRTkw9AHak9RwDBdVVq_A@mail.gmail.com","subject":"Re: [PATCH v3 11/15] replay: use standard revision ranges","fromName":"Dragan Simic","fromEmail":"dsimic@manjaro.org","sentAt":"2023-10-10T14:02:29Z","receivedAt":"2023-10-10T14:02:36Z","isPatch":true,"sender":{"key":"dsimic@manjaro.org","avatar":null},"body":"On 2023-10-10 14:44, Christian Couder wrote:\n> On Thu, Sep 7, 2023 at 11:02 PM Dragan Simic <dsimic@manjaro.org> \n> wrote:\n>> \n>> On 2023-09-07 10:32, Christian Couder wrote:\n>> > On Thu, Jun 22, 2023 at 12:03 PM Toon Claes <toon@iotcl.com> wrote:\n>> >>\n>> >> Christian Couder <christian.couder@gmail.com> writes:\n>> >>\n>> >> > +DESCRIPTION\n>> >> > +-----------\n>> >> > +\n>> >> > +Takes a range of commits, and replays them onto a new location.  Does\n>> >> > +not touch the working tree or index, and does not update any\n>> >> > +references.  However, the output of this command is meant to be used\n>> >>\n>> >> Small suggestion here:\n>> >>\n>> >> Takes a range of commits, and replays them onto a new location.  Does\n>> >> neither touch the working tree nor index, and does not update any\n>> >> references.\n>> >\n>> > I am not a native speaker, so I am not sure what's best here. I find\n>> > your suggestion a bit less clear though, so until a native speaker\n>> > agrees with it or maybe finds something even better, I prefer to leave\n>> > it as-is.\n>> \n>> I'm also not a native English speaker, but I spent about 2.5 years\n>> contributing a whole lot to English Wikipedia, so I'd dare to say I've\n>> honed my English skills rather well.  Thus, here's my take on this:\n>> \n>>      Takes a range of commits and replays them onto a new location.\n>>      Leaves the working tree and the index untouched, and updates no\n>>      references.  The output of this command is to be used...\n>> \n>> This is written in a concise yet slightly imperative way, which should\n>> be suitable for the purpose.  I hope you agree.\n> \n> I agree and I like it, so I have changed it to the above in version 5\n> I just sent.\n\nGreat, thanks.\n"},{"id":"483518","messageId":"owlysf66ququ.fsf@fine.c.googlers.com","threadId":"59560","inReplyTo":"CAP8UFD3f94NnBgkzkezcALJxamsz+-oPfqKe8XGNMuJ+g--z6g@mail.gmail.com","subject":"Re: [PATCH v4 11/15] replay: use standard revision ranges","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2023-10-19T19:26:17Z","receivedAt":"2023-10-19T19:26:22Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Sat, Sep 9, 2023 at 12:55 AM Linus Arver <linusa@google.com> wrote:\n>>\n>> Hi Christian,\n>>\n>> I am only reviewing the docs. To assume the mindset of a Git user\n>> unfamiliar with this command, I purposely did not read the cover letter\n>> until after this review was done.\n>\n> Ok, thanks!\n>\n> [...]\n>> > +     Starting point at which to create the new commits.  May be any\n>> > +     valid commit, and not just an existing branch name.\n>>\n>> Add \"See linkgit:gitrevisions[7].\" at the end?\n>\n> I don't think it's worth it to mention \"linkgit:gitrevisions[7]\"\n> everywhere we can pass a revision. I think it makes the doc heavier\n> than it should be, especially here where the command is a plumbing one\n> for now and users should be quite experienced with Git already.\n\nI agree that plumbing commands assume that users are more\nexperienced with Git already, so SGTM.\n\n>> I ask because I'm interested in informing\n>> the readers of our docs about any potential pitfalls from abusing this\n>> command by mistake.\n>\n> I appreciate your desire to give high quality docs to our users, but I\n> don't think it's a big pitfall and I think that this command is still\n> very much \"in the works\" and is also designed for experienced users\n> for now, so I am not sure it's the right time to spend too much time\n> on this.\n\nAlso sounds reasonable to me. Thank you for considering my suggestions,\nmuch appreciated!\n"},{"id":"483528","messageId":"owlypm1aqto0.fsf@fine.c.googlers.com","threadId":"59560","inReplyTo":"20231010123847.2777056-12-christian.couder@gmail.com","subject":"Re: [PATCH v5 11/14] replay: use standard revision ranges","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2023-10-19T19:49:35Z","receivedAt":"2023-10-19T19:49:39Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"This patch's documentation bits LGTM. The comments I have below are all\nnits and I don't think they are enough to require a re-roll. If v5 is\naccepted as-is, then this stuff could be #leftoverbits for a future\n(minor) cleanup.\n\nThanks!\n\nChristian Couder <christian.couder@gmail.com> writes:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> +DESCRIPTION\n> +-----------\n> +\n> +Takes a range of commits and replays them onto a new location. Leaves\n> +the working tree and the index untouched, and updates no\n> +references.\n\nNit: in v4 you had\n\n     Does\n    +not touch the working tree or index, and does not update any\n    +references. \n\nand the \"does not update any references\" sounds more natural than\n\"updates no references\".\n\n> The output of this command is meant to be used as input to\n> +`git update-ref --stdin`, which would update the relevant branches\n> +(see the OUTPUT section below).\n> +\n> +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n\nNit: add \"IN THE FUTURE\" at the end.\n\n> +EXAMPLES\n> +--------\n> +\n> +To simply rebase `mybranch` onto `target`:\n\nNit: s/To simply rebase/Rebase\n\nor, remove \"simply\" because it doesn't add much value.\n"},{"id":"483922","messageId":"bd872b81-80a9-5e4e-dcb6-faebc9671848@gmx.de","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-10-26T13:44:44Z","receivedAt":"2023-10-26T13:45:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Tue, 10 Oct 2023, Christian Couder wrote:\n\n>  6:  ec51351889 !  6:  37d545d5d6 replay: don't simplify history\n>     @@ Metadata\n>      Author: Elijah Newren <newren@gmail.com>\n>\n>       ## Commit message ##\n>     -    replay: don't simplify history\n>     +    replay: change rev walking options\n>\n>          Let's set the rev walking options we need after calling\n>     -    setup_revisions() instead of before. This makes it clearer which options\n>     -    we need.\n>     +    setup_revisions() instead of before. This enforces options we always\n>     +    want.\n>     +\n>     +    We want the command to work from older commits to newer ones by default,\n>     +    but we are Ok with letting users reverse that, using --reverse, if that's\n>     +    what they really want.\n>\n>          Also we don't want history simplification, as we want to deal with all\n>          the commits in the affected range.\n>\n>     +    Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n>          Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n>          Signed-off-by: Elijah Newren <newren@gmail.com>\n>          Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>         }\n>\n>      +  /* requirements/overrides for revs */\n>     -+  revs.reverse = 1;\n>     ++  revs.reverse = !revs.reverse;\n>      +  revs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n>      +  revs.topo_order = 1;\n>      +  revs.simplify_history = 0;\n\nThis still overrides a couple of command-line options, _silently_. I would\nprefer those three assignments to be moved just before the\n`setup_revisions()` call.\n\nLetting users override these settings may not make much sense, but it\nmakes even less sense to pretend to let them override the settings and\nthen just ignore them without warning. (See also\nhttps://en.wikipedia.org/wiki/Principle_of_least_astonishment.)\n\nMoving these three assignments before the `setup_revisions()` call would\nneatly remedy that.\n\n>  7:  cd4ed07d2d =  7:  2943f08926 replay: add an important FIXME comment about gpg signing\n>  8:  e45a55917c =  8:  f81962ba41 replay: remove progress and info output\n>  9:  0587a76cbb =  9:  236747497e replay: remove HEAD related sanity check\n> 10:  d10368e87a = 10:  3374d5be23 replay: make it a minimal server side command\n> 11:  4e09572c43 ! 11:  197d076a93 replay: use standard revision ranges\n>     @@ Commit message\n>          way as many other Git commands. This makes its interface more\n>          standard and more flexible.\n>\n>     +    This also enables many revision related options accepted and\n>     +    eaten by setup_revisions(). If the replay command was a high level\n>     +    one or had a high level mode, it would make sense to restrict some\n>     +    of the possible options, like those generating non-contiguous\n>     +    history, as they could be confusing for most users.\n>     +\n>          Also as the interface of the command is now mostly finalized,\n>          we can add some documentation as well as testcases to make sure\n>          the command will continue to work as designed in the future.\n>\n>     +    We only document the rev-list related options among all the\n>     +    revision related options that are now accepted, as the rev-list\n>     +    related ones are probably the most useful for now.\n>     +\n>     +    Helped-by: Dragan Simic <dsimic@manjaro.org>\n>     +    Helped-by: Linus Arver <linusa@google.com>\n>          Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n>          Signed-off-by: Elijah Newren <newren@gmail.com>\n>          Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n>     @@ Documentation/git-replay.txt (new)\n>      +\n>      +NAME\n>      +----\n>     -+git-replay - Replay commits on a different base, without touching working tree\n>     ++git-replay - Replay commits on a new base, works on bare repos too\n>      +\n>      +\n>      +SYNOPSIS\n\nAs mentioned in\nhttps://lore.kernel.org/git/03460733-0219-c648-5757-db1958f8042e@gmx.de/,\nI would like the `EXPERIMENTAL` label to be shown prominently here.\nProbably not only the `SYNOPSIS` as I had originally suggested but also in\nthe `NAME`.\n\nOtherwise we may end up with the same situation as with the (from my\nperspective, failed) `git switch`/`git restore` experiment, where we\nwanted to explore a better user experience than the overloaded `git\ncheckout` command, only to now be stuck with having to maintain\nbackward-compatibility for `git switch`/`git restore` command-line options\nthat were not meant to be set in stone but to be iterated on, instead. A\nreal-life demonstration of [Hyrum's Law](hyrumslaw.com/), if you like. Or,\nfrom a different angle, we re-enacted https://xkcd.com/927/ in a way.\n\nI'd like to suggest to learn from history and avoid this by tacking on a\nwarning label right at the top of the documentation. We may eventually\nmanage to iterate `git replay` to a point where it is totally capable to\nsupersede `git rebase`, by doing everything the latter does, except\nbetter, who knows? But we _do_ need the liberty to make sweeping changes\nto this new builtin if we want to have a prayer of doing that. And I fear\nthat not even mentioning the EXPERIMENTAL nature right at the top of the\nmanual page would just render us into that undesirable corner.\n\nIn addition, I am still a bit uneasy with introducing both the manual page\nand the test script in this commit (see my comments in\nhttps://lore.kernel.org/git/03460733-0219-c648-5757-db1958f8042e@gmx.de/).\nIt would be better to uphold our high standard and introduce scaffolds for\nboth files in the first commit, then populate the file contents\nincrementally in the same the patches that introduce the corresponding\noptions/features/changes.\n\nThe rest of the interdiff consists mostly of context changes intermixed\nwith a couple of changes that I like.\n\nCiao,\nJohannes\n"},{"id":"484040","messageId":"CABPp-BGCzxL-kpOvZzWRTJcx2v18QHm5ev8bFv7bm0dyNqhKug@mail.gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-10-29T06:00:20Z","receivedAt":"2023-10-29T06:00:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Tue, Oct 10, 2023 at 5:39 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n> * Patch 12/15 (replay: disallow revision specific options and\n>   pathspecs) in version 4 has been removed, so there are now only 14\n>   patches instead of 15 in the series. This follows a suggestion by\n>   Dscho, and goes in the direction Elijah initially wanted before\n>   Derrick Stolee argued for disallowing revision specific options and\n>   pathspecs.\n\nThat's too strongly worded; and may be misleading.  My primary goal in\nthat discussion was that setup_revisions() should not be a disallowed\nAPI for future consumers such as git-replay.  My secondary thought, at\nthe time, was that although I agreed that setup_revisions() was a\nproblematic API, I didn't think fixing it should be a prerequisite for\nnew features to make use of it.\n\nHowever, your paragraph here could easily be read that I think the\nsetup_revisions() API is fine.  I don't.  I actually think fixing the\nsetup_revisions() API and preventing not only git-replay but many\nother commands from accepting non-sensical flags, as suggested by\nStolee, is a very good idea.  I even brought up the example\n$ git stash show --graph --relative-date --min-parents=3\n     --simplify-merges --cherry --show-pulls --unpacked -v -t -8\n     --format=oneline --abbrev=12 --pretty=fuller --show-notes\n     --encode-email-headers --always --branches --indexed-objects stash@{0}\nin the thread[1] along with the comment \"guess which flags are ignored\nand which aren't\".  That's garbage.  This digging plus various things\nStolee said actually convinced me that perhaps prioritizing fixing the\nsetup_revisions() API over adding new consumers does make sense.\n\nBut, I don't feel nearly as strong about it as Stolee on\nprioritization, so I'm not going to object too strongly with this\npatch being tossed for now.  But I do want to note that I actually\nlike Stolee's suggestion that we should fix that API and tighten what\nmany commands accept.\n\n[1] https://lore.kernel.org/git/f5dd91a7-ba11-917a-39e2-2737829558cb@github.com/\n\n> * In patch 2/14 (replay: introduce new builtin) the command is now in\n>   the \"plumbingmanipulators\" category instead of the \"mainporcelain\"\n>   category. This is more in line with the goal of introducing it as a\n>   plumbing command for now. Thanks to a suggestion from Dscho.\n\nI do want to eventually make it a porcelain, but I think it's pretty\nfar from that in its current state, so this is a good change.\n\n> * In patch 6/14 (replay: change rev walking options) the commit\n>   message has been improved, including its subject, to better match\n>   and explain what the patch does.\n\nThis (and multiple other changes I elided) are good; thanks for\npushing forward on this.\n\n>   Also instead of forcing reverse\n>   order we use the reverse order by default but allow it to be changed\n>   using `--reverse`. Thanks to Dscho.\n\nI can see why this might sometimes be useful for exclusively linear\nhistory, but it seems to open a can of worms and possibly unfixable\ncorner cases for non-linear history.  I'd rather not do this, or at\nleast pull it out of this series and let us discuss it in some follow\nup series.  There are some other alternatives that might handle such\nusecases better.\n\n>  6:  ec51351889 !  6:  37d545d5d6 replay: don't simplify history\n...\n>     +    We want the command to work from older commits to newer ones by default,\n>     +    but we are Ok with letting users reverse that, using --reverse, if that's\n>     +    what they really want.\n\nAs noted above, _I_ am not ok with this yet.  Given the patch\nprominently bears my name, the \"we\" here at a minimum is wrong.  I\nwould rather leave this change out for now and discuss it for a\nfollow-on series.\n\n>     @@ Documentation/git-replay.txt (new)\n>      +\n>      +NAME\n>      +----\n>     -+git-replay - Replay commits on a different base, without touching working tree\n>     ++git-replay - Replay commits on a new base, works on bare repos too\n\nreally minor point: \"works on\" or \"works in\" or \"works with\" ?\n"},{"id":"484041","messageId":"CABPp-BFrVfGHOrBk7g=4TkGxDv=oSqF1FOkhp6WVbxUV-2yveQ@mail.gmail.com","threadId":"59560","inReplyTo":"bd872b81-80a9-5e4e-dcb6-faebc9671848@gmx.de","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-10-29T06:01:58Z","receivedAt":"2023-10-29T06:02:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Thu, Oct 26, 2023 at 6:44 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Christian,\n>\n> On Tue, 10 Oct 2023, Christian Couder wrote:\n>\n[...]\n> >      +  /* requirements/overrides for revs */\n> >     -+  revs.reverse = 1;\n> >     ++  revs.reverse = !revs.reverse;\n> >      +  revs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n> >      +  revs.topo_order = 1;\n> >      +  revs.simplify_history = 0;\n>\n> This still overrides a couple of command-line options, _silently_. I would\n> prefer those three assignments to be moved just before the\n> `setup_revisions()` call.\n>\n> Letting users override these settings may not make much sense, but it\n> makes even less sense to pretend to let them override the settings and\n> then just ignore them without warning. (See also\n> https://en.wikipedia.org/wiki/Principle_of_least_astonishment.)\n>\n> Moving these three assignments before the `setup_revisions()` call would\n> neatly remedy that.\n\nI agree that warnings or error messages would be better.\n\nBut if we're talking about something short of that, I'd actually argue\nthe opposite of what you do here.  I intentionally moved these\nassignments after setup_revisions(), and in my mind, the purpose in\ndoing so was to satisfy the Principle of Least Astonishment.  My\nexperience with git-fast-export, where some settings are made before\ncalling setup_revisions() and then can be overridden, and then do\ncompletely hideous things, was much worse to me than just admitting\nthe flags are bad given the various assumptions the tool makes.  I\nhave some patches sitting around to fix fast-export that I never got\naround to upstreaming, but when it came time to implement git-replay,\nI made sure to fix what I viewed as the bigger problem.\n\n[...]\n> >     @@ Documentation/git-replay.txt (new)\n> >      +\n> >      +NAME\n> >      +----\n> >     -+git-replay - Replay commits on a different base, without touching working tree\n> >     ++git-replay - Replay commits on a new base, works on bare repos too\n> >      +\n> >      +\n> >      +SYNOPSIS\n>\n> As mentioned in\n> https://lore.kernel.org/git/03460733-0219-c648-5757-db1958f8042e@gmx.de/,\n> I would like the `EXPERIMENTAL` label to be shown prominently here.\n> Probably not only the `SYNOPSIS` as I had originally suggested but also in\n> the `NAME`.\n>\n> Otherwise we may end up with the same situation as with the (from my\n> perspective, failed) `git switch`/`git restore` experiment, where we\n> wanted to explore a better user experience than the overloaded `git\n> checkout` command, only to now be stuck with having to maintain\n> backward-compatibility for `git switch`/`git restore` command-line options\n> that were not meant to be set in stone but to be iterated on, instead. A\n> real-life demonstration of [Hyrum's Law](hyrumslaw.com/), if you like. Or,\n> from a different angle, we re-enacted https://xkcd.com/927/ in a way.\n>\n> I'd like to suggest to learn from history and avoid this by tacking on a\n> warning label right at the top of the documentation. We may eventually\n> manage to iterate `git replay` to a point where it is totally capable to\n> supersede `git rebase`, by doing everything the latter does, except\n> better, who knows? But we _do_ need the liberty to make sweeping changes\n> to this new builtin if we want to have a prayer of doing that. And I fear\n> that not even mentioning the EXPERIMENTAL nature right at the top of the\n> manual page would just render us into that undesirable corner.\n\nI fully support this.  Absolutely, 100%.\n\nThanks both,\nElijah\n"},{"id":"484048","messageId":"e04cfbdc-fd28-c645-8f5d-132f7ceec6be@gmx.de","threadId":"59560","inReplyTo":"CABPp-BGCzxL-kpOvZzWRTJcx2v18QHm5ev8bFv7bm0dyNqhKug@mail.gmail.com","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-10-29T14:14:07Z","receivedAt":"2023-10-29T14:14:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 28 Oct 2023, Elijah Newren wrote:\n\n> On Tue, Oct 10, 2023 at 5:39 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n> > * Patch 12/15 (replay: disallow revision specific options and\n> >   pathspecs) in version 4 has been removed, so there are now only 14\n> >   patches instead of 15 in the series. This follows a suggestion by\n> >   Dscho, and goes in the direction Elijah initially wanted before\n> >   Derrick Stolee argued for disallowing revision specific options and\n> >   pathspecs.\n>\n> [... snipping many parts that I agree with...]\n>\n> >   Also instead of forcing reverse order we use the reverse order by\n> >   default but allow it to be changed using `--reverse`. Thanks to\n> >   Dscho.\n>\n> I can see why this might sometimes be useful for exclusively linear\n> history, but it seems to open a can of worms and possibly unfixable\n> corner cases for non-linear history.  I'd rather not do this, or at\n> least pull it out of this series and let us discuss it in some follow\n> up series.  There are some other alternatives that might handle such\n> usecases better.\n\nI find myself wishing for an easy way to reverse commits, if only to\nswitch around the latest two commits while stopped during a rebase.\n\nSo it would have been nice for me if there had been an easy, worktree-less\nway to make that happen.\n\nI guess this would be going in the direction of reordering commits,\nthough, something we deliberately left for later?\n\nCiao,\nJohannes\n"},{"id":"484131","messageId":"CABPp-BGfsda-8CK7_YPJfhGMfpLqdDeB8X6wnqPAGmhiY4KjDA@mail.gmail.com","threadId":"59560","inReplyTo":"e04cfbdc-fd28-c645-8f5d-132f7ceec6be@gmx.de","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-10-30T17:18:14Z","receivedAt":"2023-10-30T17:18:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Johannes,\n\nOn Sun, Oct 29, 2023 at 7:14 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Sat, 28 Oct 2023, Elijah Newren wrote:\n>\n> > On Tue, Oct 10, 2023 at 5:39 AM Christian Couder\n> > <christian.couder@gmail.com> wrote:\n> > > * Patch 12/15 (replay: disallow revision specific options and\n> > >   pathspecs) in version 4 has been removed, so there are now only 14\n> > >   patches instead of 15 in the series. This follows a suggestion by\n> > >   Dscho, and goes in the direction Elijah initially wanted before\n> > >   Derrick Stolee argued for disallowing revision specific options and\n> > >   pathspecs.\n> >\n> > [... snipping many parts that I agree with...]\n> >\n> > >   Also instead of forcing reverse order we use the reverse order by\n> > >   default but allow it to be changed using `--reverse`. Thanks to\n> > >   Dscho.\n> >\n> > I can see why this might sometimes be useful for exclusively linear\n> > history, but it seems to open a can of worms and possibly unfixable\n> > corner cases for non-linear history.  I'd rather not do this, or at\n> > least pull it out of this series and let us discuss it in some follow\n> > up series.  There are some other alternatives that might handle such\n> > usecases better.\n>\n> I find myself wishing for an easy way to reverse commits, if only to\n> switch around the latest two commits while stopped during a rebase.\n>\n> So it would have been nice for me if there had been an easy, worktree-less\n> way to make that happen.\n\nSeems reasonable; we'll definitely want to keep this in mind.\n\n> I guess this would be going in the direction of reordering commits,\n> though, something we deliberately left for later?\n\nYes, I think that's a good framing for it.\n\nThanks,\nElijah\n"},{"id":"484351","messageId":"20231102135151.843758-2-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 01/14] t6429: remove switching aspects of fast-rebase","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:38Z","receivedAt":"2023-11-02T13:52:17Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAt the time t6429 was written, merge-ort was still under development,\ndid not have quite as many tests, and certainly was not widely deployed.\nSince t6429 was exercising some codepaths just a little differently, we\nthought having them also test the \"merge_switch_to_result()\" bits of\nmerge-ort was useful even though they weren't intrinsic to the real\npoint of these tests.\n\nHowever, the value provided by doing extra testing of the\n\"merge_switch_to_result()\" bits has decreased a bit over time, and it's\nactively making it harder to refactor `test-tool fast-rebase` into `git\nreplay`, which we are going to do in following commits.  Dispense with\nthese bits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/helper/test-fast-rebase.c              | 9 +--------\n t/t6429-merge-sequence-rename-caching.sh | 9 +++++++--\n 2 files changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c\nindex cac20a72b3..2bfab66b1b 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/t/helper/test-fast-rebase.c\n@@ -194,7 +194,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n-\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\tmerge_finalize(&merge_opt, &result);\n \n \tif (result.clean < 0)\n \t\texit(128);\n@@ -213,9 +213,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n-\n-\t\tprime_cache_tree(the_repository, the_repository->index,\n-\t\t\t\t result.tree);\n \t} else {\n \t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n@@ -228,10 +225,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t\tdie(\"Failed to update %s\", argv[4]);\n \t\t}\n \t}\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-\n \tret = (result.clean == 0);\n cleanup:\n \tstrbuf_release(&reflog_msg);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex d02fa16614..75d3fd2dba 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -72,6 +72,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \t\tgit switch upstream &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n@@ -200,6 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -277,6 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -356,8 +359,6 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n \t\t#git cherry-pick upstream..topic &&\n \n-\t\tgrep CONFLICT..rename/rename output &&\n-\n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n \t)\n@@ -456,6 +457,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -522,6 +524,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -624,6 +627,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -682,6 +686,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484352","messageId":"20231102135151.843758-1-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231010123847.2777056-1-christian.couder@gmail.com","subject":"[PATCH v6 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:37Z","receivedAt":"2023-11-02T13:52:17Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"# Intro\n\n`git replay` has initially been developed entirely by Elijah Newren\nmostly last year (2022) at:\n\nhttps://github.com/newren/git/commits/replay\n\nI took over this year to polish and upstream it as GitLab is\ninterested in replacing libgit2, and for that purpose needs a command\nto do server side (so without using a worktree) rebases, cherry-picks\nand reverts.\n\nI reduced the number of commits and features in this patch series,\ncompared to what Elijah already developed. Especially I stopped short\nof replaying merge commits and replaying interactively. These and\nother features might be upstreamed in the future after this patch\nseries has graduated.\n\nThe focus in this series is to make it a good plumbing command that\ncan already be used server side and that replaces the \"fast-rebase\"\ntest-tool command. So things to make it easier to use on the command\nline, and more advanced features (like replaying merges) are left out.\n\nIt looks like GitHub has actually already been using version 3 of this\npatch series in production with good results. See:\n\nhttps://github.blog/2023-07-27-scaling-merge-ort-across-github/\nhttps://lore.kernel.org/git/304f2a49-5e05-7655-9f87-2011606df5db@gmx.de/\n\n# Content of this cover letter\n\nThe \"Quick Overview\" and \"Reasons for diverging from cherry-pick &\nrebase\" sections just below are describing the purpose of the new\ncommand in the big scheme of things. They are taken from Elijah's\ndesign notes\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt)\nand describe what we want this command to become and the reasons for\nthat, not what the command is after only this patch series. Also these\ndesign notes were written at least one year ago, so parts of those 2\nsections are not true anymore. I have added Phillip Wood's or Felipe\nContreras' notes (thanks to them) where that's the case, but some now\nflawed parts may have missed.\n\nAfter these two sections, starting with the \"Important limitations\"\nsection, you will find sections describing what is actually in this\npatch series.\n\nMore interesting material is available in Elijah's design notes like\nan \"Intro via examples\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L37-L132),\na discussion about \"Preserving topology, replaying merges\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L264-L341)\nand a \"Current status\" section describing Elijah's work\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L344-L392)\nbefore I started working on upstreaming it.\n\nI have not included this material here though, as the documentation\nadded by this patch series for the `git replay` command already\nincludes an \"EXAMPLES\" section, and other sections of Elijah's design\nnotes might not be interesting for now. Also this cover letter is\nalready pretty long.  But reviewers can refer to the links above if\nthey think it can help.\n\n# Quick Overview (from Elijah's design notes)\n\n`git replay`, at a basic level, can perhaps be thought of as a\n\"default-to-dry-run rebase\" -- meaning no updates to the working tree,\nor to the index, or to any references.  However, it differs from\nrebase in that it:\n\n  * Works for branches that aren't checked out\n\n  * Works in a bare repository\n\n  * Can replay multiple branches simultaneously (with or without common\n    history in the range being replayed)\n\n  * Preserves relative topology by default (merges are replayed too in\n    Elijah's original work, not in this series)\n\n  * Focuses on performance\n\n  * Has several altered defaults as a result of the above\n\nI sometimes think of `git replay` as \"fast-replay\", a patch-based\nanalogue to the snapshot-based fast-export & fast-import tools.\n\n# Reasons for diverging from cherry-pick & rebase (from Elijah's\n  design notes)\n\nThere are multiple reasons to diverge from the defaults in cherry-pick and\nrebase.\n\n* Server side needs\n\n  * Both cherry-pick and rebase, via the sequencer, are heavily tied\n    to updating the working tree, index, some refs, and a lot of\n    control files with every commit replayed, and invoke a mess of\n    hooks[1] that might be hard to avoid for backward compatibility\n    reasons (at least, that's been brought up a few times on the\n    list).\n\n  * cherry-pick and rebase both fork various subprocesses\n    unnecessarily, but somewhat intrinsically in part to ensure the\n    same hooks are called that old scripted implementations would have\n    called.\n\n    Note: since 356ee4659bb (sequencer: try to commit without forking\n    'git commit', 2017-11-24) cherry-pick and rebase do not fork\n    subprocesses other than hooks for the cases covered by this patch\n    series (i.e. they do not fork \"git commit\" for simple picks).\n\n  * \"Dry run\" behavior, where there are no updates to worktree, index,\n    or even refs might be important.\n\n  * Should not assume users only want to operate on HEAD (see next\n    section)\n\n* Decapitate HEAD-centric assumptions\n\n  * cherry-pick forces commits to be played on top of HEAD;\n    inflexible.\n\n  * rebase assumes the range of commits to be replayed is\n    upstream..HEAD by default, though it allows one to replay\n    upstream..otherbranch -- but it still forcibly and needlessly\n    checks out 'otherbranch' before starting to replay things.\n\n    Note: since 767a9c417eb (rebase -i: stop checking out the tip of\n    the branch to rebase, 2020-01-24) it's not true that rebase\n    forcibly and needlessly checks out 'otherbranch'.\n\n  * Assuming HEAD is involved severely limits replaying multiple\n    (possibly divergent) branches.\n\n    Note: since 89fc0b53fdb (rebase: update refs from 'update-ref'\n    commands, 2022-07-19) the sequencer can update multiple\n    branches. The issue with divergent branch is with command line\n    arguments and the todo list generation rather than the\n    capabilities of the sequencer.\n\n  * Once you stop assuming HEAD has a certain meaning, there's not\n    much reason to have two separate commands anymore (except for the\n    funny extra not-necessarily-compatible options both have gained\n    over time).\n\n  * (Micro issue: Assuming HEAD is involved also makes it harder for\n    new users to learn what rebase means and does; it makes command\n    lines hard to parse.  Not sure I want to harp on this too much, as\n    I have a suspicion I might be creating a tool for experts with\n    complicated use cases, but it's a minor quibble.)\n\n* Performance\n\n  * jj is slaughtering us on rebase speed[2].  I would like us to become\n    competitive.  (I dropped a few comments in the link at [2] about why\n    git is currently so bad.)\n\n  * From [3], there was a simple 4-patch series in linux.git that took\n    53 seconds to rebase.  Switching to ort dropped it to 16 seconds.\n    While that sounds great, only 11 *milliseconds* were needed to do\n    the actual merges.  That means almost *all* the time (>99%) was\n    overhead!  Big offenders:\n\n    * --reapply-cherry-picks should be the default\n\n    * can_fast_forward() should be ripped out, and perhaps other extraneous\n      revision walks\n\n      Note: d42c9ffa0f (rebase: factor out branch_base calculation,\n      2022-10-17) might already deal with that (according to Felipe\n      Contreras).\n\n    * avoid updating working tree, index, refs, reflogs, and control\n      structures except when needed (e.g. hitting a conflict, or operation\n      finished)\n\n  * Other performance ideas (mostly for future work, not in this\n    series)\n\n    * single-file control structures instead of directory of files\n      (when doing interactive things which is in Elijah's original\n      work, but not in this series)\n\n    * avoid forking subprocesses unless explicitly requested (e.g.\n      --exec, --strategy, --run-hooks).  For example, definitely do not\n      invoke `git commit` or `git merge`.\n\n    * Sanitize hooks:\n\n      * dispense with all per-commit hooks for sure (pre-commit,\n        post-commit, post-checkout).\n\n      * pre-rebase also seems to assume exactly 1 ref is written, and\n        invoking it repeatedly would be stupid.  Plus, it's specific\n        to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n        rebase probably broke the pre-rebase assumptions already...)\n\n      * post-rewrite hook might make sense, but fast-import got\n        exempted, and I think of replay like a patch-based analogue\n        to the snapshot-based fast-import.\n\n    * When not running server side, resolve conflicts in a sparse-cone\n      sparse-index worktree to reduce number of files written to a\n      working tree.  (See below as well.)\n\n    * [High risk of possible premature optimization] Avoid large\n      numbers of newly created loose objects, when replaying large\n      numbers of commits.  Two possibilities: (1) Consider using\n      tmp-objdir and pack objects from the tmp-objdir at end of\n      exercise, (2) Lift code from git-fast-import to immediately\n      stuff new objects into a pack?\n\n* Multiple branches and non-checked out branches\n\n  * The ability to operate on non-checked out branches also implies\n    that we should generally be able to replay when in a dirty working\n    tree (exception being when we expect to update HEAD and any of the\n    dirty files is one that needs to be updated by the replay).\n\n  * Also, if we are operating locally on a non-checked out branch and\n    hit a conflict, we should have a way to resolve the conflict\n    without messing with the user's work on their current\n    branch. (This is not is this patch series though.)\n\n    * Idea: new worktree with sparse cone + sparse index checkout,\n      containing only files in the root directory, and whatever is\n      necessary to get the conflicts\n\n    * Companion to above idea: control structures should be written to\n      $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n      replay sessions, and so we know which worktrees are associated\n      with which replay operations.\n\n  - [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/\n  - [2] https://github.com/martinvonz/jj/discussions/49\n  - [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/\n\n# Important limitations\n\n* The code exits with code 1 if there are any conflict. No\n  resumability. No nice output. No interactivity. No special exit code\n  depending on the reason.\n\n* When a commit becomes empty as it is replayed, it is still replayed\n  as an empty commit, instead of being dropped.\n\n* No replaying merges, nor root commits. Only regular commits.\n\n* Signed commits are not properly handled. It's not clear what to do\n  to such commits when replaying on the server side.\n\n* Notes associated with replayed commits are not updated nor\n  duplicated. (Thanks to Phillip Wood for noticing.)\n\n# Commit overview\n\n* 1/14 t6429: remove switching aspects of fast-rebase\n\n    Preparatory commit to make it easier to later replace the\n    fast-rebase test-tool by `git replay` without breaking existing\n    tests.\n\n* 2/14 replay: introduce new builtin\n\n    This creates a minimal `git replay` command by moving the code\n    from the `fast-rebase` test helper from `t/helper/` into\n    `builtin/` and doing some renames and a few other needed changes.\n    Since v5, this also introduces stub documentation and tests for\n    the new command as suggested by Dscho. Also in the doc,\n    \"EXPERIMENTAL\" has been added both to the name line and the\n    synopsis line of the command, which was also suggested by\n    Dscho. This required to also change the \"usage\" message of the\n    command.\n\n* - 3/14 replay: start using parse_options API\n  - 4/14 replay: die() instead of failing assert()\n  - 5/14 replay: introduce pick_regular_commit()\n  - 6/14 replay: change rev walking options\n  - 7/14 replay: add an important FIXME comment about gpg signing\n  - 8/14 replay: remove progress and info output\n  - 9/14 replay: remove HEAD related sanity check\n\n    These slowly change the command to make it behave more like\n    regular commands and to start cleaning up its output. In patch\n    6/14 (replay: change rev walking options) there are some changes\n    compared to v5 as suggested by Elijah and Dscho. First we are now\n    warning() in case a command line option would change some rev\n    options that we are going to override. Second we don't let the\n    `--reverse` option change the way the command works.\n\n* 10/14 replay: make it a minimal server side command\n\n    After the cleaning up in previous commits, it's now time to\n    radically change the way it works by stopping it to do ref\n    updates, to update the index and worktree, to consider HEAD as\n    special. Instead just make it output commands that should be\n    passed to `git update-ref --stdin`. A few test changes are new in\n    this commit since v5.\n\n* 11/14 replay: use standard revision ranges\n\n    Start addind new interesting features and also documentation and\n    tests, as the interface of the command is cristalizing into its\n    final form. Since v5 this patch is not introducing the\n    documentation and the tests of the new command anymore (as they\n    are introduced in patch 2/14).\n\n* - 12/14 replay: add --advance or 'cherry-pick' mode\n  - 13/14 replay: add --contained to rebase contained branches\n\n    Add new options and features to the command.\n\n* 14/14 replay: stop assuming replayed branches do not diverge\n\n    This adds another interesting feature, as well as related\n    documentation and tests.\n\n# Notes about `fast-rebase`, tests and documentation\n\nThe `fast-rebase` test-tool helper was developed by Elijah to\nexperiment with a rebasing tool that would be developed from scratch\nbased on his merge-ort work, could be used to test that merge-ort\nwork, and would not have the speed and interface limitations of `git\nrebase` or `git cherry-pick`.\n\nThis `fast-rebase` helper was used before this series in:\n\nt6429-merge-sequence-rename-caching.sh\n\nSo when `git replay` is created from `fast-rebase` in patch 2/14, the\nt6429 test script is also converted to use `git replay`. This ensures\nthat `git replay` doesn't break too badly during the first 10 patches\nin this patch series.\n\nTests and documentation are introduced specifically for `git replay`\nas soon as patch 2/14, but they are not much improved since around\n11/14 as it doesn't make much sense to document and test behavior that\nwe know is going to change soon.\n\n# Possibly controversial issues \n\n* bare or not bare: this series works towards a plumbing command with\n  the end goal of it being usable and used first on bare repos,\n  contrary to existing commands like `git rebase` and `git\n  cherry-pick`. The tests check that the command works on both bare\n  and non-bare repo though.\n\n* exit status: a successful, non-conflicted replay exits with code\n  0. When the replay has conflicts, the exit status is 1. If the\n  replay is not able to complete (or start) due to some kind of error,\n  the exit status is something other than 0 or 1. There are a few\n  tests checking that. It has been suggested in an internal review\n  that conflicts might want to get a more specific error code as an\n  error code of 1 might be quite easy to return by accident. It\n  doesn't seem to me from their docs (which might want to be improved,\n  I didn't look at the code) that other commands like `git merge` and\n  `git rebase` exit with a special error code in case of conflict.\n\n* make worktree and index changes optional: commit 10/14 stops\n  updating the index and worktree, but it might be better especially\n  for cli users to make that optional. The issue is that this would\n  make the command more complex while we are developing a number of\n  important features so that the command can be used on bare repos. It\n  seems that this should rather be done in an iterative improvement\n  after the important features have landed.\n\n* --advance and --contained: these two advanced options might not\n  belong to this first series and could perhaps be added in a followup\n  series in separate commits. On the other hand the code for\n  --contained seems involved with the code of --advance and it's nice\n  to see soon that git replay can indeed do cherry-picking and rebase\n  many refs at once, and this way fullfil these parts of its promise.\n\n* replaying diverging branches: 14/14 the last patch in the series,\n  which allow replaying diverging branches, can be seen as a\n  fundamental fix or alternatively as adding an interesting\n  feature. So it's debatable if it should be in its own patch along\n  with its own tests as in this series, or if it should be merged into\n  a previous patch and which one.\n\n* only 2 patches: this patch series can be seen from a high level\n  point of view as 1) introducing the new `git replay` command, and 2)\n  using `git replay` to replace, and get rid of, the fast-rebase\n  test-tool command. The fact that not much of the original\n  fast-rebase code and interface is left would agree with that point\n  of view. On the other hand, fast-rebase can also be seen as a first\n  iteration towards `git replay`. So it can also make sense to see how\n  `git replay` evolved from it.\n\n# Changes between v5 and v6\n\nThanks to Dscho, Linus Arver and Dragan Simic for their suggestions on\nthe previous version! The few changes compared to v5 are:\n\n* The patch series was rebased onto master at 2e8e77cbac (The\n  twenty-first batch, 2023-10-23). This is to fix small conflicts\n  with recently merged series, especially\n  cc/repack-sift-filtered-objects-to-separate-pack.\n\n* In patch 2/14 (replay: introduce new builtin), stub documentation\n  and tests have been introduced as suggested by Dscho. They were\n  introduced later (in patch 11/14) in v5.\n\n* Also in patch 2/14, \"EXPERIMENTAL\" has been added both to the name\n  line and the synopsis line of the command in the doc as suggested by\n  Dscho.\n\n* In patch 6/14 (replay: change rev walking options) we are now\n  warning() in case a command line option would change some rev\n  options that we are going to override. This follows discussions\n  between Dscho and Elijah where they agreed that properly taking care\n  of handling these command line options should be dealt with later in\n  future patch series.\n\n* Also in patch 6/14, we don't let the `--reverse` option change the\n  way the command works. This also follows discussions between Dscho\n  and Elijah about command line options related to rev walking.\n\n* In patch 9/14 (replay: remove HEAD related sanity check) we can\n  simplify a test a bit.\n\n* In patch 10/14 (replay: make it a minimal server side command) we\n  can further add tests.\n\n* In patch 11/14 (replay: use standard revision ranges), as\n  documentation and tests are not introduced by this commit anymore,\n  but earlier by patch 2/14, fewer doc and test changes are made in\n  this patch.\n\nCI tests seem to pass according to:\n\nhttps://github.com/chriscool/git/actions/runs/6732523160/\n\n(A \"Run ci/install-dependencies.sh\" test on osx-clang initially\nfailed, but it worked when I just re-ran it. Hopefully this will alow\ntests that couldn't be run to proceed and succeed, but I don't want to\nwait for that right now.)\n\n# Range-diff between v5 and v6\n\n(Sorry it's very long mostly due to doc and test changes over a number\nof patches.)\n\n 1:  72c34a0eba =  1:  fac0a9dff4 t6429: remove switching aspects of fast-rebase\n 2:  f85e6c823c !  2:  8a605ddef8 replay: introduce new builtin\n    @@ Commit message\n         For now, this is just a rename from `t/helper/test-fast-rebase.c` into\n         `builtin/replay.c` with minimal changes to make it build appropriately.\n     \n    +    There is a stub documentation and a stub test script though.\n    +\n         Subsequent commits will flesh out its capabilities and make it a more\n         standard regular builtin.\n     \n    @@ .gitignore\n      /git-rerere\n      /git-reset\n     \n    + ## Documentation/git-replay.txt (new) ##\n    +@@\n    ++git-replay(1)\n    ++=============\n    ++\n    ++NAME\n    ++----\n    ++git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos too\n    ++\n    ++\n    ++SYNOPSIS\n    ++--------\n    ++[verse]\n    ++'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n    ++\n    ++DESCRIPTION\n    ++-----------\n    ++\n    ++Takes a range of commits and replays them onto a new location.\n    ++\n    ++THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n    ++\n    ++OPTIONS\n    ++-------\n    ++\n    ++--onto <newbase>::\n    ++  Starting point at which to create the new commits.  May be any\n    ++  valid commit, and not just an existing branch name.\n    ++\n    ++EXIT STATUS\n    ++-----------\n    ++\n    ++For a successful, non-conflicted replay, the exit status is 0.  When\n    ++the replay has conflicts, the exit status is 1.  If the replay is not\n    ++able to complete (or start) due to some kind of error, the exit status\n    ++is something other than 0 or 1.\n    ++\n    ++GIT\n    ++---\n    ++Part of the linkgit:git[1] suite\n    +\n      ## Makefile ##\n     @@ Makefile: TEST_BUILTINS_OBJS += test-dump-split-index.o\n      TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n      TEST_BUILTINS_OBJS += test-env-helper.o\n      TEST_BUILTINS_OBJS += test-example-decorate.o\n     -TEST_BUILTINS_OBJS += test-fast-rebase.o\n    + TEST_BUILTINS_OBJS += test-find-pack.o\n      TEST_BUILTINS_OBJS += test-fsmonitor-client.o\n      TEST_BUILTINS_OBJS += test-genrandom.o\n    - TEST_BUILTINS_OBJS += test-genzeros.o\n     @@ Makefile: BUILTIN_OBJS += builtin/remote-fd.o\n      BUILTIN_OBJS += builtin/remote.o\n      BUILTIN_OBJS += builtin/repack.o\n    @@ t/helper/test-tool.c: static struct test_cmd cmds[] = {\n        { \"env-helper\", cmd__env_helper },\n        { \"example-decorate\", cmd__example_decorate },\n     -  { \"fast-rebase\", cmd__fast_rebase },\n    +   { \"find-pack\", cmd__find_pack },\n        { \"fsmonitor-client\", cmd__fsmonitor_client },\n        { \"genrandom\", cmd__genrandom },\n    -   { \"genzeros\", cmd__genzeros },\n     \n      ## t/helper/test-tool.h ##\n     @@ t/helper/test-tool.h: int cmd__dump_untracked_cache(int argc, const char **argv);\n    @@ t/helper/test-tool.h: int cmd__dump_untracked_cache(int argc, const char **argv)\n      int cmd__env_helper(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__find_pack(int argc, const char **argv);\n      int cmd__fsmonitor_client(int argc, const char **argv);\n      int cmd__genrandom(int argc, const char **argv);\n    - int cmd__genzeros(int argc, const char **argv);\n    +\n    + ## t/t3650-replay-basics.sh (new) ##\n    +@@\n    ++#!/bin/sh\n    ++\n    ++test_description='basic git replay tests'\n    ++\n    ++GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n    ++export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n    ++\n    ++. ./test-lib.sh\n    ++\n    ++GIT_AUTHOR_NAME=author@name\n    ++GIT_AUTHOR_EMAIL=bogus@email@address\n    ++export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL\n    ++\n    ++test_expect_success 'setup' '\n    ++  test_commit A &&\n    ++  test_commit B &&\n    ++\n    ++  git switch -c topic1 &&\n    ++  test_commit C &&\n    ++  git switch -c topic2 &&\n    ++  test_commit D &&\n    ++  test_commit E &&\n    ++  git switch topic1 &&\n    ++  test_commit F &&\n    ++  git switch -c topic3 &&\n    ++  test_commit G &&\n    ++  test_commit H &&\n    ++  git switch -c topic4 main &&\n    ++  test_commit I &&\n    ++  test_commit J &&\n    ++\n    ++  git switch -c next main &&\n    ++  test_commit K &&\n    ++  git merge -m \"Merge topic1\" topic1 &&\n    ++  git merge -m \"Merge topic2\" topic2 &&\n    ++  git merge -m \"Merge topic3\" topic3 &&\n    ++  >evil &&\n    ++  git add evil &&\n    ++  git commit --amend &&\n    ++  git merge -m \"Merge topic4\" topic4 &&\n    ++\n    ++  git switch main &&\n    ++  test_commit L &&\n    ++  test_commit M &&\n    ++\n    ++  git switch -c conflict B &&\n    ++  test_commit C.conflict C.t conflict\n    ++'\n    ++\n    ++test_expect_success 'using replay to rebase two branches, one on top of other' '\n    ++  git switch main &&\n    ++\n    ++  git replay --onto main topic1 topic2 >result &&\n    ++\n    ++  git log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n    ++  test_write_lines E D M L B A >expect &&\n    ++  test_cmp expect actual\n    ++'\n    ++\n    ++test_done\n     \n      ## t/t6429-merge-sequence-rename-caching.sh ##\n     @@ t/t6429-merge-sequence-rename-caching.sh: test_expect_success 'caching renames does not preclude finding new ones' '\n 3:  11abb9d120 =  3:  a7886952b7 replay: start using parse_options API\n 4:  9e568eae84 =  4:  bab0286f0a replay: die() instead of failing assert()\n 5:  e7ebf3c5ef =  5:  469e95e5bf replay: introduce pick_regular_commit()\n 6:  37d545d5d6 !  6:  edafe4846f replay: change rev walking options\n    @@ Commit message\n     \n         Let's set the rev walking options we need after calling\n         setup_revisions() instead of before. This enforces options we always\n    -    want.\n    -\n    -    We want the command to work from older commits to newer ones by default,\n    -    but we are Ok with letting users reverse that, using --reverse, if that's\n    -    what they really want.\n    +    want for now.\n     \n    +    We want the command to work from older commits to newer ones by default.\n         Also we don't want history simplification, as we want to deal with all\n         the commits in the affected range.\n     \n    +    When we see an option that we are going to override, we emit a warning\n    +    to avoid confusion as much as possible though.\n    +\n         Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n         Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Elijah Newren <newren@gmail.com>\n    @@ Commit message\n     \n      ## builtin/replay.c ##\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    +   struct merge_result result;\n    +   struct strbuf reflog_msg = STRBUF_INIT;\n    +   struct strbuf branch_name = STRBUF_INIT;\n    +-  int ret = 0;\n    ++  int i, ret = 0;\n    + \n    +   const char * const replay_usage[] = {\n    +           N_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n    +@@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n      \n        repo_init_revisions(the_repository, &revs, prefix);\n      \n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n     -\n        strvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n      \n    ++  /*\n    ++   * TODO: For now, let's warn when we see an option that we are\n    ++   * going to override after setup_revisions() below. In the\n    ++   * future we might want to either die() or allow them if we\n    ++   * think they could be useful though.\n    ++   */\n    ++  for (i = 0; i < argc; i++) {\n    ++          if (!strcmp(argv[i], \"--reverse\") || !strcmp(argv[i], \"--date-order\") ||\n    ++              !strcmp(argv[i], \"--topo-order\") || !strcmp(argv[i], \"--author-date-order\") ||\n    ++              !strcmp(argv[i], \"--full-history\"))\n    ++                  warning(_(\"option '%s' will be overridden\"), argv[i]);\n    ++  }\n    ++\n        if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n    -@@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    +           ret = error(_(\"unhandled options\"));\n                goto cleanup;\n        }\n      \n     +  /* requirements/overrides for revs */\n    -+  revs.reverse = !revs.reverse;\n    ++  revs.reverse = 1;\n     +  revs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n     +  revs.topo_order = 1;\n     +  revs.simplify_history = 0;\n 7:  2943f08926 =  7:  b81574744a replay: add an important FIXME comment about gpg signing\n 8:  f81962ba41 =  8:  b08ad2b2f1 replay: remove progress and info output\n 9:  236747497e !  9:  5099c94d2e replay: remove HEAD related sanity check\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n                               REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n                        error(_(\"could not update %s\"), argv[2]);\n                        die(\"Failed to update %s\", argv[2]);\n    +\n    + ## t/t3650-replay-basics.sh ##\n    +@@ t/t3650-replay-basics.sh: test_expect_success 'setup' '\n    + '\n    + \n    + test_expect_success 'using replay to rebase two branches, one on top of other' '\n    +-  git switch main &&\n    +-\n    +   git replay --onto main topic1 topic2 >result &&\n    + \n    +   git log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n10:  3374d5be23 ! 10:  b4a5d1edd4 replay: make it a minimal server side command\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        struct merge_result result;\n     -  struct strbuf reflog_msg = STRBUF_INIT;\n        struct strbuf branch_name = STRBUF_INIT;\n    -   int ret = 0;\n    +   int i, ret = 0;\n      \n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n        onto = peel_committish(onto_name);\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n     +  return ret ? 0 : 1;\n      }\n     \n    + ## t/t3650-replay-basics.sh ##\n    +@@ t/t3650-replay-basics.sh: test_expect_success 'setup' '\n    +   test_commit C.conflict C.t conflict\n    + '\n    + \n    ++test_expect_success 'setup bare' '\n    ++  git clone --bare . bare\n    ++'\n    ++\n    + test_expect_success 'using replay to rebase two branches, one on top of other' '\n    +   git replay --onto main topic1 topic2 >result &&\n    + \n    ++  test_line_count = 1 result &&\n    ++\n    +   git log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n    +   test_write_lines E D M L B A >expect &&\n    +-  test_cmp expect actual\n    ++  test_cmp expect actual &&\n    ++\n    ++  printf \"update refs/heads/topic2 \" >expect &&\n    ++  printf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n    ++  git rev-parse topic2 >>expect &&\n    ++\n    ++  test_cmp expect result\n    ++'\n    ++\n    ++test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n    ++  git -C bare replay --onto main topic1 topic2 >result-bare &&\n    ++  test_cmp expect result-bare\n    + '\n    + \n    + test_done\n    +\n      ## t/t6429-merge-sequence-rename-caching.sh ##\n     @@ t/t6429-merge-sequence-rename-caching.sh: test_expect_success 'caching renames does not preclude finding new ones' '\n      \n11:  197d076a93 ! 11:  1d8a6ef432 replay: use standard revision ranges\n    @@ Commit message\n         history, as they could be confusing for most users.\n     \n         Also as the interface of the command is now mostly finalized,\n    -    we can add some documentation as well as testcases to make sure\n    +    we can add more documentation and more testcases to make sure\n         the command will continue to work as designed in the future.\n     \n         We only document the rev-list related options among all the\n    @@ Commit message\n         Signed-off-by: Elijah Newren <newren@gmail.com>\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n    - ## Documentation/git-replay.txt (new) ##\n    -@@\n    -+git-replay(1)\n    -+=============\n    -+\n    -+NAME\n    -+----\n    -+git-replay - Replay commits on a new base, works on bare repos too\n    -+\n    -+\n    -+SYNOPSIS\n    -+--------\n    -+[verse]\n    -+'git replay' --onto <newbase> <revision-range>...\n    -+\n    -+DESCRIPTION\n    -+-----------\n    -+\n    + ## Documentation/git-replay.txt ##\n    +@@ Documentation/git-replay.txt: SYNOPSIS\n    + DESCRIPTION\n    + -----------\n    + \n    +-Takes a range of commits and replays them onto a new location.\n     +Takes a range of commits and replays them onto a new location. Leaves\n     +the working tree and the index untouched, and updates no\n     +references. The output of this command is meant to be used as input to\n     +`git update-ref --stdin`, which would update the relevant branches\n     +(see the OUTPUT section below).\n    -+\n    -+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n    -+\n    -+OPTIONS\n    -+-------\n    -+\n    -+--onto <newbase>::\n    -+  Starting point at which to create the new commits.  May be any\n    -+  valid commit, and not just an existing branch name.\n    + \n    + THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n    + \n    +@@ Documentation/git-replay.txt: OPTIONS\n    + --onto <newbase>::\n    +   Starting point at which to create the new commits.  May be any\n    +   valid commit, and not just an existing branch name.\n     ++\n     +The update-ref command(s) in the output will update the branch(es) in\n     +the revision range to point at the new commits, similar to the way how\n    @@ Documentation/git-replay.txt (new)\n     +\n     +where the number of refs updated depends on the arguments passed and\n     +the shape of the history being replayed.\n    -+\n    -+EXIT STATUS\n    -+-----------\n    -+\n    -+For a successful, non-conflicted replay, the exit status is 0.  When\n    -+the replay has conflicts, the exit status is 1.  If the replay is not\n    -+able to complete (or start) due to some kind of error, the exit status\n    -+is something other than 0 or 1.\n    -+\n    + \n    + EXIT STATUS\n    + -----------\n    +@@ Documentation/git-replay.txt: the replay has conflicts, the exit status is 1.  If the replay is not\n    + able to complete (or start) due to some kind of error, the exit status\n    + is something other than 0 or 1.\n    + \n     +EXAMPLES\n     +--------\n     +\n    @@ Documentation/git-replay.txt (new)\n     +`origin/main`. These three branches may have commits on top of `base`\n     +that they have in common, but that does not need to be the case.\n     +\n    -+GIT\n    -+---\n    -+Part of the linkgit:git[1] suite\n    + GIT\n    + ---\n    + Part of the linkgit:git[1] suite\n     \n      ## builtin/replay.c ##\n     @@\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        struct merge_options merge_opt;\n        struct merge_result result;\n     -  struct strbuf branch_name = STRBUF_INIT;\n    -   int ret = 0;\n    +   int i, ret = 0;\n      \n        const char * const replay_usage[] = {\n     -          N_(\"git replay --onto <newbase> <oldbase> <branch>\"),\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n      \n     -  strvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n     -\n    +   /*\n    +    * TODO: For now, let's warn when we see an option that we are\n    +    * going to override after setup_revisions() below. In the\n    +@@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    +                   warning(_(\"option '%s' will be overridden\"), argv[i]);\n    +   }\n    + \n     -  if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n     -          ret = error(_(\"unhandled options\"));\n     +  argc = setup_revisions(argc, argv, &revs, NULL);\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n      \n        /* Return */\n     \n    - ## t/t3650-replay-basics.sh (new) ##\n    -@@\n    -+#!/bin/sh\n    -+\n    -+test_description='basic git replay tests'\n    -+\n    -+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n    -+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n    -+\n    -+. ./test-lib.sh\n    -+\n    -+GIT_AUTHOR_NAME=author@name\n    -+GIT_AUTHOR_EMAIL=bogus@email@address\n    -+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL\n    -+\n    -+test_expect_success 'setup' '\n    -+  test_commit A &&\n    -+  test_commit B &&\n    -+\n    -+  git switch -c topic1 &&\n    -+  test_commit C &&\n    -+  git switch -c topic2 &&\n    -+  test_commit D &&\n    -+  test_commit E &&\n    -+  git switch topic1 &&\n    -+  test_commit F &&\n    -+  git switch -c topic3 &&\n    -+  test_commit G &&\n    -+  test_commit H &&\n    -+  git switch -c topic4 main &&\n    -+  test_commit I &&\n    -+  test_commit J &&\n    -+\n    -+  git switch -c next main &&\n    -+  test_commit K &&\n    -+  git merge -m \"Merge topic1\" topic1 &&\n    -+  git merge -m \"Merge topic2\" topic2 &&\n    -+  git merge -m \"Merge topic3\" topic3 &&\n    -+  >evil &&\n    -+  git add evil &&\n    -+  git commit --amend &&\n    -+  git merge -m \"Merge topic4\" topic4 &&\n    -+\n    -+  git switch main &&\n    -+  test_commit L &&\n    -+  test_commit M &&\n    -+\n    -+  git switch -c conflict B &&\n    -+  test_commit C.conflict C.t conflict\n    -+'\n    -+\n    -+test_expect_success 'setup bare' '\n    -+  git clone --bare . bare\n    -+'\n    -+\n    -+test_expect_success 'using replay to rebase two branches, one on top of other' '\n    + ## t/t3650-replay-basics.sh ##\n    +@@ t/t3650-replay-basics.sh: test_expect_success 'setup bare' '\n    + '\n    + \n    + test_expect_success 'using replay to rebase two branches, one on top of other' '\n    +-  git replay --onto main topic1 topic2 >result &&\n     +  git replay --onto main topic1..topic2 >result &&\n    -+\n    -+  test_line_count = 1 result &&\n    -+\n    -+  git log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n    -+  test_write_lines E D M L B A >expect &&\n    -+  test_cmp expect actual &&\n    -+\n    -+  printf \"update refs/heads/topic2 \" >expect &&\n    -+  printf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n    -+  git rev-parse topic2 >>expect &&\n    -+\n    -+  test_cmp expect result\n    -+'\n    -+\n    -+test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n    + \n    +   test_line_count = 1 result &&\n    + \n    +@@ t/t3650-replay-basics.sh: test_expect_success 'using replay to rebase two branches, one on top of other' '\n    + '\n    + \n    + test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n    +-  git -C bare replay --onto main topic1 topic2 >result-bare &&\n     +  git -C bare replay --onto main topic1..topic2 >result-bare &&\n    -+  test_cmp expect result-bare\n    -+'\n    -+\n    +   test_cmp expect result-bare\n    + '\n    + \n     +test_expect_success 'using replay to rebase with a conflict' '\n     +  test_expect_code 1 git replay --onto topic1 B..conflict\n     +'\n    @@ t/t3650-replay-basics.sh (new)\n     +  test_expect_code 1 git -C bare replay --onto topic1 B..conflict\n     +'\n     +\n    -+test_done\n    + test_done\n     \n      ## t/t6429-merge-sequence-rename-caching.sh ##\n     @@ t/t6429-merge-sequence-rename-caching.sh: test_expect_success 'caching renames does not preclude finding new ones' '\n12:  e52d8b961c ! 12:  3a207e5d7f replay: add --advance or 'cherry-pick' mode\n    @@ Commit message\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n      ## Documentation/git-replay.txt ##\n    -@@ Documentation/git-replay.txt: git-replay - Replay commits on a new base, works on bare repos too\n    +@@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n      SYNOPSIS\n      --------\n      [verse]\n    --'git replay' --onto <newbase> <revision-range>...\n    -+'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n    +-'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n    ++'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n      \n      DESCRIPTION\n      -----------\n    @@ builtin/replay.c: static struct commit *pick_regular_commit(struct commit *pickm\n        struct merge_options merge_opt;\n        struct merge_result result;\n     +  struct strset *update_refs = NULL;\n    -   int ret = 0;\n    +   int i, ret = 0;\n      \n        const char * const replay_usage[] = {\n     -          N_(\"git replay --onto <newbase> <revision-range>...\"),\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n     -\n        repo_init_revisions(the_repository, &revs, prefix);\n      \n    -   argc = setup_revisions(argc, argv, &revs, NULL);\n    +   /*\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n        revs.topo_order = 1;\n        revs.simplify_history = 0;\n13:  fc79a930b5 ! 13:  23a164196b replay: add --contained to rebase contained branches\n    @@ Commit message\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n      ## Documentation/git-replay.txt ##\n    -@@ Documentation/git-replay.txt: git-replay - Replay commits on a new base, works on bare repos too\n    +@@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n      SYNOPSIS\n      --------\n      [verse]\n    --'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n    -+'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\n    +-'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n    ++'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n      \n      DESCRIPTION\n      -----------\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        struct rev_info revs;\n        struct commit *last_commit = NULL;\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    -   int ret = 0;\n    +   int i, ret = 0;\n      \n        const char * const replay_usage[] = {\n     -          N_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n     +\n        repo_init_revisions(the_repository, &revs, prefix);\n      \n    -   argc = setup_revisions(argc, argv, &revs, NULL);\n    +   /*\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n                        continue;\n                while (decoration) {\n14:  1160ff54e6 ! 14:  5c4b15cccd replay: stop assuming replayed branches do not diverge\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        struct merge_result result;\n        struct strset *update_refs = NULL;\n     +  kh_oid_map_t *replayed_commits;\n    -   int ret = 0;\n    +   int i, ret = 0;\n      \n        const char * const replay_usage[] = {\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n\n\nElijah Newren (14):\n  t6429: remove switching aspects of fast-rebase\n  replay: introduce new builtin\n  replay: start using parse_options API\n  replay: die() instead of failing assert()\n  replay: introduce pick_regular_commit()\n  replay: change rev walking options\n  replay: add an important FIXME comment about gpg signing\n  replay: remove progress and info output\n  replay: remove HEAD related sanity check\n  replay: make it a minimal server side command\n  replay: use standard revision ranges\n  replay: add --advance or 'cherry-pick' mode\n  replay: add --contained to rebase contained branches\n  replay: stop assuming replayed branches do not diverge\n\n .gitignore                               |   1 +\n Documentation/git-replay.txt             | 127 +++++++\n Makefile                                 |   2 +-\n builtin.h                                |   1 +\n builtin/replay.c                         | 421 +++++++++++++++++++++++\n command-list.txt                         |   1 +\n git.c                                    |   1 +\n t/helper/test-fast-rebase.c              | 241 -------------\n t/helper/test-tool.c                     |   1 -\n t/helper/test-tool.h                     |   1 -\n t/t3650-replay-basics.sh                 | 198 +++++++++++\n t/t6429-merge-sequence-rename-caching.sh |  45 +--\n 12 files changed, 776 insertions(+), 264 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100644 builtin/replay.c\n delete mode 100644 t/helper/test-fast-rebase.c\n create mode 100755 t/t3650-replay-basics.sh\n\n\nbase-commit: 2e8e77cbac8ac17f94eee2087187fa1718e38b14\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484354","messageId":"20231102135151.843758-4-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 03/14] replay: start using parse_options API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:40Z","receivedAt":"2023-11-02T13:52:18Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of manually parsing arguments, let's start using the parse_options\nAPI. This way this new builtin will look more standard, and in some\nupcoming commits will more easily be able to handle more command line\noptions.\n\nNote that we plan to later use standard revision ranges instead of\nhardcoded \"<oldbase> <branch>\" arguments. When we will use standard\nrevision ranges, it will be easier to check if there are no spurious\narguments if we keep ARGV[0], so let's call parse_options() with\nPARSE_OPT_KEEP_ARGV0 even if we don't need ARGV[0] right now to avoid\nsome useless code churn.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 45 ++++++++++++++++++++++++++++++++-------------\n 1 file changed, 32 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex f2d8444417..afabb844d3 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -15,7 +15,7 @@\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n #include \"object-name.h\"\n-#include \"read-cache-ll.h\"\n+#include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n #include \"sequencer.h\"\n@@ -92,6 +92,7 @@ static struct commit *create_commit(struct tree *tree,\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n+\tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n \tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n@@ -105,16 +106,32 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n-\tif (argc == 2 && !strcmp(argv[1], \"-h\")) {\n-\t\tprintf(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\\n\");\n-\t\texit(129);\n+\tconst char * const replay_usage[] = {\n+\t\tN_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n+\t\tNULL\n+\t};\n+\tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"onto\", &onto_name,\n+\t\t\t   N_(\"revision\"),\n+\t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n+\n+\tif (!onto_name) {\n+\t\terror(_(\"option --onto is mandatory\"));\n+\t\tusage_with_options(replay_usage, replay_options);\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+\tif (argc != 3) {\n+\t\terror(_(\"bad number of arguments\"));\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n \n-\tonto = peel_committish(argv[2]);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\tonto = peel_committish(onto_name);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \t/* Sanity check */\n \tif (repo_get_oid(the_repository, \"HEAD\", &head))\n@@ -126,6 +143,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tBUG(\"Could not read index\");\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n+\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\n@@ -134,7 +152,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n@@ -197,8 +216,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &last_picked_commit->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -210,8 +229,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &head,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t}\n \tret = (result.clean == 0);\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484355","messageId":"20231102135151.843758-5-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 04/14] replay: die() instead of failing assert()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:41Z","receivedAt":"2023-11-02T13:52:19Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIt's not a good idea for regular Git commands to use an assert() to\ncheck for things that could happen but are not supported.\n\nLet's die() with an explanation of the issue instead.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex afabb844d3..32dbaaf028 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -179,7 +179,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\n+\t\tif (!commit->parents)\n+\t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n+\t\tif (commit->parents->next)\n+\t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n+\n \t\tbase = commit->parents->item;\n \n \t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484353","messageId":"20231102135151.843758-6-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 05/14] replay: introduce pick_regular_commit()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:42Z","receivedAt":"2023-11-02T13:52:21Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's refactor the code to handle a regular commit (a commit that is\nneither a root commit nor a merge commit) into a single function instead\nof keeping it inside cmd_replay().\n\nThis is good for separation of concerns, and this will help further work\nin the future to replay merge commits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 54 ++++++++++++++++++++++++++++++------------------\n 1 file changed, 34 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 32dbaaf028..5c4cbd11db 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -89,6 +89,35 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+static struct commit *pick_regular_commit(struct commit *pickme,\n+\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  struct merge_options *merge_opt,\n+\t\t\t\t\t  struct merge_result *result)\n+{\n+\tstruct commit *base;\n+\tstruct tree *pickme_tree, *base_tree;\n+\n+\tbase = pickme->parents->item;\n+\n+\tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n+\tbase_tree = repo_get_commit_tree(the_repository, base);\n+\n+\tmerge_opt->branch2 = short_commit_name(pickme);\n+\tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n+\n+\tmerge_incore_nonrecursive(merge_opt,\n+\t\t\t\t  base_tree,\n+\t\t\t\t  result->tree,\n+\t\t\t\t  pickme_tree,\n+\t\t\t\t  result);\n+\n+\tfree((char*)merge_opt->ancestor);\n+\tmerge_opt->ancestor = NULL;\n+\tif (!result->clean)\n+\t\treturn NULL;\n+\treturn create_commit(result->tree, pickme, last_commit);\n+}\n+\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n@@ -100,7 +129,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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 tree *head_tree;\n \tstruct merge_result result;\n \tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n@@ -175,7 +204,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tresult.tree = head_tree;\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *base;\n+\t\tstruct commit *pick;\n \n \t\tfprintf(stderr, \"Rebasing %s...\\r\",\n \t\t\toid_to_hex(&commit->object.oid));\n@@ -185,26 +214,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tbase = commit->parents->item;\n-\n-\t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-\t\tbase_tree = repo_get_commit_tree(the_repository, 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\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!pick)\n \t\t\tbreak;\n+\t\tlast_commit = pick;\n \t\tlast_picked_commit = commit;\n-\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484356","messageId":"20231102135151.843758-3-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 02/14] replay: introduce new builtin","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:39Z","receivedAt":"2023-11-02T13:52:21Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor now, this is just a rename from `t/helper/test-fast-rebase.c` into\n`builtin/replay.c` with minimal changes to make it build appropriately.\n\nLet's add a stub documentation and a stub test script though.\n\nSubsequent commits will flesh out the capabilities of the new command\nand make it a more standard regular builtin.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n .gitignore                                    |  1 +\n Documentation/git-replay.txt                  | 38 ++++++++++++\n Makefile                                      |  2 +-\n builtin.h                                     |  1 +\n .../test-fast-rebase.c => builtin/replay.c    | 29 +++------\n command-list.txt                              |  1 +\n git.c                                         |  1 +\n t/helper/test-tool.c                          |  1 -\n t/helper/test-tool.h                          |  1 -\n t/t3650-replay-basics.sh                      | 60 +++++++++++++++++++\n t/t6429-merge-sequence-rename-caching.sh      | 27 +++------\n 11 files changed, 121 insertions(+), 41 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n rename t/helper/test-fast-rebase.c => builtin/replay.c (87%)\n create mode 100755 t/t3650-replay-basics.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 5e56e471b3..612c0f6a0f 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -135,6 +135,7 @@\n /git-remote-ext\n /git-repack\n /git-replace\n+/git-replay\n /git-request-pull\n /git-rerere\n /git-reset\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nnew file mode 100644\nindex 0000000000..44bf584fed\n--- /dev/null\n+++ b/Documentation/git-replay.txt\n@@ -0,0 +1,38 @@\n+git-replay(1)\n+=============\n+\n+NAME\n+----\n+git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos too\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n+\n+DESCRIPTION\n+-----------\n+\n+Takes a range of commits and replays them onto a new location.\n+\n+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n+\n+OPTIONS\n+-------\n+\n+--onto <newbase>::\n+\tStarting point at which to create the new commits.  May be any\n+\tvalid commit, and not just an existing branch name.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted replay, the exit status is 0.  When\n+the replay has conflicts, the exit status is 1.  If the replay is not\n+able to complete (or start) due to some kind of error, the exit status\n+is something other than 0 or 1.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 03adcb5a48..3834bc1544 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -799,7 +799,6 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o\n TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n TEST_BUILTINS_OBJS += test-env-helper.o\n TEST_BUILTINS_OBJS += test-example-decorate.o\n-TEST_BUILTINS_OBJS += test-fast-rebase.o\n TEST_BUILTINS_OBJS += test-find-pack.o\n TEST_BUILTINS_OBJS += test-fsmonitor-client.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n@@ -1290,6 +1289,7 @@ BUILTIN_OBJS += builtin/remote-fd.o\n BUILTIN_OBJS += builtin/remote.o\n BUILTIN_OBJS += builtin/repack.o\n BUILTIN_OBJS += builtin/replace.o\n+BUILTIN_OBJS += builtin/replay.o\n BUILTIN_OBJS += builtin/rerere.o\n BUILTIN_OBJS += builtin/reset.o\n BUILTIN_OBJS += builtin/rev-list.o\ndiff --git a/builtin.h b/builtin.h\nindex d560baa661..28280636da 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -211,6 +211,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix);\n int cmd_remote_ext(int argc, const char **argv, const char *prefix);\n int cmd_remote_fd(int argc, const char **argv, const char *prefix);\n int cmd_repack(int argc, const char **argv, const char *prefix);\n+int cmd_replay(int argc, const char **argv, const char *prefix);\n int cmd_rerere(int argc, const char **argv, const char *prefix);\n int cmd_reset(int argc, const char **argv, const char *prefix);\n int cmd_restore(int argc, const char **argv, const char *prefix);\ndiff --git a/t/helper/test-fast-rebase.c b/builtin/replay.c\nsimilarity index 87%\nrename from t/helper/test-fast-rebase.c\nrename to builtin/replay.c\nindex 2bfab66b1b..f2d8444417 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/builtin/replay.c\n@@ -1,17 +1,11 @@\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+ * \"git replay\" builtin command\n  */\n \n #define USE_THE_INDEX_VARIABLE\n-#include \"test-tool.h\"\n+#include \"git-compat-util.h\"\n+\n+#include \"builtin.h\"\n #include \"cache-tree.h\"\n #include \"commit.h\"\n #include \"environment.h\"\n@@ -27,7 +21,8 @@\n #include \"sequencer.h\"\n #include \"setup.h\"\n #include \"strvec.h\"\n-#include \"tree.h\"\n+#include <oidset.h>\n+#include <tree.h>\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -94,7 +89,7 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n-int cmd__fast_rebase(int argc, const char **argv)\n+int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n@@ -110,14 +105,8 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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\tprintf(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\\n\");\n \t\texit(129);\n \t}\n \n@@ -136,7 +125,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n \n-\trepo_init_revisions(the_repository, &revs, NULL);\n+\trepo_init_revisions(the_repository, &revs, prefix);\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\ndiff --git a/command-list.txt b/command-list.txt\nindex 54b2a50f5f..c4cd0f352b 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -160,6 +160,7 @@ git-reflog                              ancillarymanipulators           complete\n git-remote                              ancillarymanipulators           complete\n git-repack                              ancillarymanipulators           complete\n git-replace                             ancillarymanipulators           complete\n+git-replay                              plumbingmanipulators\n git-request-pull                        foreignscminterface             complete\n git-rerere                              ancillaryinterrogators\n git-reset                               mainporcelain           history\ndiff --git a/git.c b/git.c\nindex c67e44dd82..7068a184b0 100644\n--- a/git.c\n+++ b/git.c\n@@ -594,6 +594,7 @@ static struct cmd_struct commands[] = {\n \t{ \"remote-fd\", cmd_remote_fd, NO_PARSEOPT },\n \t{ \"repack\", cmd_repack, RUN_SETUP },\n \t{ \"replace\", cmd_replace, RUN_SETUP },\n+\t{ \"replay\", cmd_replay, RUN_SETUP },\n \t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t{ \"reset\", cmd_reset, RUN_SETUP },\n \t{ \"restore\", cmd_restore, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 876cd2dc31..37ba996539 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -30,7 +30,6 @@ static struct test_cmd cmds[] = {\n \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n \t{ \"env-helper\", cmd__env_helper },\n \t{ \"example-decorate\", cmd__example_decorate },\n-\t{ \"fast-rebase\", cmd__fast_rebase },\n \t{ \"find-pack\", cmd__find_pack },\n \t{ \"fsmonitor-client\", cmd__fsmonitor_client },\n \t{ \"genrandom\", cmd__genrandom },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 70dd4eba11..8a1a7c63da 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -24,7 +24,6 @@ int cmd__dump_untracked_cache(int argc, const char **argv);\n int cmd__dump_reftable(int argc, const char **argv);\n int cmd__env_helper(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__find_pack(int argc, const char **argv);\n int cmd__fsmonitor_client(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nnew file mode 100755\nindex 0000000000..36c1b5082a\n--- /dev/null\n+++ b/t/t3650-replay-basics.sh\n@@ -0,0 +1,60 @@\n+#!/bin/sh\n+\n+test_description='basic git replay tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+GIT_AUTHOR_NAME=author@name\n+GIT_AUTHOR_EMAIL=bogus@email@address\n+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\n+\tgit switch -c topic1 &&\n+\ttest_commit C &&\n+\tgit switch -c topic2 &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit switch topic1 &&\n+\ttest_commit F &&\n+\tgit switch -c topic3 &&\n+\ttest_commit G &&\n+\ttest_commit H &&\n+\tgit switch -c topic4 main &&\n+\ttest_commit I &&\n+\ttest_commit J &&\n+\n+\tgit switch -c next main &&\n+\ttest_commit K &&\n+\tgit merge -m \"Merge topic1\" topic1 &&\n+\tgit merge -m \"Merge topic2\" topic2 &&\n+\tgit merge -m \"Merge topic3\" topic3 &&\n+\t>evil &&\n+\tgit add evil &&\n+\tgit commit --amend &&\n+\tgit merge -m \"Merge topic4\" topic4 &&\n+\n+\tgit switch main &&\n+\ttest_commit L &&\n+\ttest_commit M &&\n+\n+\tgit switch -c conflict B &&\n+\ttest_commit C.conflict C.t conflict\n+'\n+\n+test_expect_success 'using replay to rebase two branches, one on top of other' '\n+\tgit switch main &&\n+\n+\tgit replay --onto main topic1 topic2 >result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 75d3fd2dba..7670b72008 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,9 +71,8 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -141,8 +140,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -200,9 +198,8 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -278,9 +275,8 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -356,8 +352,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,9 +451,8 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -523,9 +517,8 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -626,9 +619,8 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -685,9 +677,8 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484357","messageId":"20231102135151.843758-8-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 07/14] replay: add an important FIXME comment about gpg signing","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:44Z","receivedAt":"2023-11-02T13:52:24Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want to be able to handle signed commits in some way in the future,\nbut we are not ready to do it now. So for the time being let's just add\na FIXME comment to remind us about it.\n\nThese are different ways we could handle them:\n\n  - in case of a cli user and if there was an interactive mode, we could\n    perhaps ask if the user wants to sign again\n  - we could add an option to just fail if there are signed commits\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex a0d27ab249..544a546674 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -62,7 +62,7 @@ static struct commit *create_commit(struct tree *tree,\n \tstruct object *obj;\n \tstruct commit_list *parents = NULL;\n \tchar *author;\n-\tchar *sign_commit = NULL;\n+\tchar *sign_commit = NULL; /* FIXME: cli users might want to sign again */\n \tstruct commit_extra_header *extra;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *out_enc = get_commit_output_encoding();\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484358","messageId":"20231102135151.843758-7-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 06/14] replay: change rev walking options","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:43Z","receivedAt":"2023-11-02T13:52:26Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's set the rev walking options we need after calling\nsetup_revisions() instead of before. This enforces options we always\nwant for now.\n\nWe want the command to work from older commits to newer ones by default.\nAlso we don't want history simplification, as we want to deal with all\nthe commits in the affected range.\n\nWhen we see an option that we are going to override, we emit a warning\nto avoid confusion as much as possible though.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 30 ++++++++++++++++++++----------\n 1 file changed, 20 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 5c4cbd11db..a0d27ab249 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -133,7 +133,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct merge_result result;\n \tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n-\tint ret = 0;\n+\tint i, ret = 0;\n \n \tconst char * const replay_usage[] = {\n \t\tN_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n@@ -173,22 +173,32 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \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-\n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n+\t/*\n+\t * TODO: For now, let's warn when we see an option that we are\n+\t * going to override after setup_revisions() below. In the\n+\t * future we might want to either die() or allow them if we\n+\t * think they could be useful though.\n+\t */\n+\tfor (i = 0; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--reverse\") || !strcmp(argv[i], \"--date-order\") ||\n+\t\t    !strcmp(argv[i], \"--topo-order\") || !strcmp(argv[i], \"--author-date-order\") ||\n+\t\t    !strcmp(argv[i], \"--full-history\"))\n+\t\t\twarning(_(\"option '%s' will be overridden\"), argv[i]);\n+\t}\n+\n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n \t\tgoto cleanup;\n \t}\n \n+\t/* requirements/overrides for revs */\n+\trevs.reverse = 1;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\trevs.simplify_history = 0;\n+\n \tstrvec_clear(&rev_walk_args);\n \n \tif (prepare_revision_walk(&revs) < 0) {\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484359","messageId":"20231102135151.843758-9-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 08/14] replay: remove progress and info output","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:45Z","receivedAt":"2023-11-02T13:52:28Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command will be changed in a follow up commit, so that it\nwill not update refs directly, but instead it will print on stdout a\nlist of commands that can be consumed by `git update-ref --stdin`.\n\nWe don't want this output to be polluted by its current low value\noutput, so let's just remove the latter.\n\nIn the future, when the command gets an option to update refs by\nitself, it will make a lot of sense to display a progress meter, but\nwe are not there yet.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 +------\n 1 file changed, 1 insertion(+), 6 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 544a546674..74680b0c8f 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -208,7 +208,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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.show_rename_progress = 0;\n \tmerge_opt.branch1 = \"HEAD\";\n \thead_tree = repo_get_commit_tree(the_repository, onto);\n \tresult.tree = head_tree;\n@@ -216,9 +216,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \twhile ((commit = get_revision(&revs))) {\n \t\tstruct commit *pick;\n \n-\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n-\t\t\toid_to_hex(&commit->object.oid));\n-\n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n@@ -237,7 +234,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\texit(128);\n \n \tif (result.clean) {\n-\t\tfprintf(stderr, \"\\nDone.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n \t\t\t    oid_to_hex(&last_commit->object.oid));\n@@ -251,7 +247,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t} else {\n-\t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484360","messageId":"20231102135151.843758-10-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 09/14] replay: remove HEAD related sanity check","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:46Z","receivedAt":"2023-11-02T13:52:33Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want replay to be a command that can be used on the server side on\nany branch, not just the current one, so we are going to stop updating\nHEAD in a future commit.\n\nA \"sanity check\" that makes sure we are replaying the current branch\ndoesn't make sense anymore. Let's remove it.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 8 +-------\n t/t3650-replay-basics.sh | 2 --\n 2 files changed, 1 insertion(+), 9 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 74680b0c8f..9331f5c6ec 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -123,7 +123,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct strvec rev_walk_args = STRVEC_INIT;\n \tstruct rev_info revs;\n@@ -162,11 +161,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\t/* Sanity check */\n-\tif (repo_get_oid(the_repository, \"HEAD\", &head))\n-\t\tdie(_(\"Cannot read HEAD\"));\n-\tassert(oideq(&onto->object.oid, &head));\n-\n \trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n@@ -251,7 +245,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n \t\t\t       &last_commit->object.oid,\n-\t\t\t       &head,\n+\t\t\t       &onto->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n \t\t\terror(_(\"could not update %s\"), argv[2]);\n \t\t\tdie(\"Failed to update %s\", argv[2]);\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 36c1b5082a..b5b9f9ade2 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -48,8 +48,6 @@ test_expect_success 'setup' '\n '\n \n test_expect_success 'using replay to rebase two branches, one on top of other' '\n-\tgit switch main &&\n-\n \tgit replay --onto main topic1 topic2 >result &&\n \n \tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484361","messageId":"20231102135151.843758-11-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 10/14] replay: make it a minimal server side command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:47Z","receivedAt":"2023-11-02T13:52:35Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want this command to be a minimal command that just does server side\npicking of commits, displaying the results on stdout for higher level\nscripts to consume.\n\nSo let's simplify it:\n  * remove the worktree and index reading/writing,\n  * remove the ref (and reflog) updating,\n  * remove the assumptions tying us to HEAD, since (a) this is not a\n    rebase and (b) we want to be able to pick commits in a bare repo,\n    i.e. to/from branches that are not checked out and not the main\n    branch,\n  * remove unneeded includes,\n  * handle rebasing multiple branches by printing on stdout the update\n    ref commands that should be performed.\n\nThe output can be piped into `git update-ref --stdin` for the ref\nupdates to happen.\n\nIn the future to make it easier for users to use this command\ndirectly maybe an option can be added to automatically pipe its output\ninto `git update-ref`.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             |  5 +-\n builtin/replay.c                         | 78 ++++++++----------------\n t/t3650-replay-basics.sh                 | 19 +++++-\n t/t6429-merge-sequence-rename-caching.sh | 39 +++++++-----\n 4 files changed, 72 insertions(+), 69 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 44bf584fed..4c852ff3bd 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -14,7 +14,10 @@ SYNOPSIS\n DESCRIPTION\n -----------\n \n-Takes a range of commits and replays them onto a new location.\n+Takes a range of commits and replays them onto a new location. Leaves\n+the working tree and the index untouched, and updates no\n+references. The output of this command is meant to be used as input to\n+`git update-ref --stdin`, which would update the relevant branches.\n \n THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n \ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 9331f5c6ec..7e3ebac3db 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -6,11 +6,7 @@\n #include \"git-compat-util.h\"\n \n #include \"builtin.h\"\n-#include \"cache-tree.h\"\n-#include \"commit.h\"\n #include \"environment.h\"\n-#include \"gettext.h\"\n-#include \"hash.h\"\n #include \"hex.h\"\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n@@ -18,8 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"sequencer.h\"\n-#include \"setup.h\"\n #include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n@@ -102,6 +96,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n+\tmerge_opt->branch1 = short_commit_name(last_commit);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -122,15 +117,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct lock_file lock = LOCK_INIT;\n+\tstruct commit *last_commit = NULL;\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 *head_tree;\n \tstruct merge_result result;\n-\tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint i, ret = 0;\n \n@@ -161,10 +153,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index(the_repository) < 0)\n-\t\tBUG(\"Could not read index\");\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n@@ -203,58 +191,44 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\tmerge_opt.branch1 = \"HEAD\";\n-\thead_tree = repo_get_commit_tree(the_repository, onto);\n-\tresult.tree = head_tree;\n+\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *pick;\n+\t\tconst struct name_decoration *decoration;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n-\t\tif (!pick)\n+\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!last_commit)\n \t\t\tbreak;\n-\t\tlast_commit = pick;\n-\t\tlast_picked_commit = commit;\n+\n+\t\tdecoration = get_name_decoration(&commit->object);\n+\t\tif (!decoration)\n+\t\t\tcontinue;\n+\n+\t\twhile (decoration) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\t\tprintf(\"update %s %s %s\\n\",\n+\t\t\t\t       decoration->name,\n+\t\t\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t\t\t       oid_to_hex(&commit->object.oid));\n+\t\t\t}\n+\t\t\tdecoration = decoration->next;\n+\t\t}\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n+\tret = result.clean;\n \n-\tif (result.clean < 0)\n-\t\texit(128);\n-\n-\tif (result.clean) {\n-\t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n-\t\t\t    oid_to_hex(&last_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, branch_name.buf,\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &last_picked_commit->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n-\t\t\tdie(_(\"unable to update HEAD\"));\n-\t} else {\n-\t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &onto->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t}\n-\tret = (result.clean == 0);\n cleanup:\n-\tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n-\treturn ret;\n+\n+\t/* Return */\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret ? 0 : 1;\n }\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex b5b9f9ade2..3567c98362 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -47,12 +47,29 @@ test_expect_success 'setup' '\n \ttest_commit C.conflict C.t conflict\n '\n \n+test_expect_success 'setup bare' '\n+\tgit clone --bare . bare\n+'\n+\n test_expect_success 'using replay to rebase two branches, one on top of other' '\n \tgit replay --onto main topic1 topic2 >result &&\n \n+\ttest_line_count = 1 result &&\n+\n \tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n \ttest_write_lines E D M L B A >expect &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n+\tgit -C bare replay --onto main topic1 topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n '\n \n test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 7670b72008..099aefeffc 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,8 +71,9 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -140,7 +141,9 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -198,8 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -275,8 +279,9 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -451,8 +456,9 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -517,8 +523,9 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -619,8 +626,9 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -677,8 +685,9 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484362","messageId":"20231102135151.843758-12-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 11/14] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:48Z","receivedAt":"2023-11-02T13:52:37Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of the fixed \"<oldbase> <branch>\" arguments, the replay\ncommand now accepts \"<revision-range>...\" arguments in a similar\nway as many other Git commands. This makes its interface more\nstandard and more flexible.\n\nThis also enables many revision related options accepted and\neaten by setup_revisions(). If the replay command was a high level\none or had a high level mode, it would make sense to restrict some\nof the possible options, like those generating non-contiguous\nhistory, as they could be confusing for most users.\n\nAlso as the interface of the command is now mostly finalized,\nwe can add more documentation and more testcases to make sure\nthe command will continue to work as designed in the future.\n\nWe only document the rev-list related options among all the\nrevision related options that are now accepted, as the rev-list\nrelated ones are probably the most useful for now.\n\nHelped-by: Dragan Simic <dsimic@manjaro.org>\nHelped-by: Linus Arver <linusa@google.com>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             | 55 +++++++++++++++++++++++-\n builtin/replay.c                         | 21 ++-------\n t/t3650-replay-basics.sh                 | 12 +++++-\n t/t6429-merge-sequence-rename-caching.sh | 18 ++++----\n 4 files changed, 76 insertions(+), 30 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 4c852ff3bd..36ddd7daed 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n SYNOPSIS\n --------\n [verse]\n-'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n+'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n \n DESCRIPTION\n -----------\n@@ -17,7 +17,8 @@ DESCRIPTION\n Takes a range of commits and replays them onto a new location. Leaves\n the working tree and the index untouched, and updates no\n references. The output of this command is meant to be used as input to\n-`git update-ref --stdin`, which would update the relevant branches.\n+`git update-ref --stdin`, which would update the relevant branches\n+(see the OUTPUT section below).\n \n THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n \n@@ -27,6 +28,30 @@ OPTIONS\n --onto <newbase>::\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n++\n+The update-ref command(s) in the output will update the branch(es) in\n+the revision range to point at the new commits, similar to the way how\n+`git rebase --update-refs` updates multiple branches in the affected\n+range.\n+\n+<revision-range>::\n+\tRange of commits to replay; see \"Specifying Ranges\" in\n+\tlinkgit:git-rev-parse and the \"Commit Limiting\" options below.\n+\n+include::rev-list-options.txt[]\n+\n+OUTPUT\n+------\n+\n+When there are no conflicts, the output of this command is usable as\n+input to `git update-ref --stdin`.  It is of the form:\n+\n+\tupdate refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+\tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+\tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+\n+where the number of refs updated depends on the arguments passed and\n+the shape of the history being replayed.\n \n EXIT STATUS\n -----------\n@@ -36,6 +61,32 @@ the replay has conflicts, the exit status is 1.  If the replay is not\n able to complete (or start) due to some kind of error, the exit status\n is something other than 0 or 1.\n \n+EXAMPLES\n+--------\n+\n+To simply rebase `mybranch` onto `target`:\n+\n+------------\n+$ git replay --onto target origin/main..mybranch\n+update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n+------------\n+\n+When calling `git replay`, one does not need to specify a range of\n+commits to replay using the syntax `A..B`; any range expression will\n+do:\n+\n+------------\n+$ git replay --onto origin/main ^base branch1 branch2 branch3\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+------------\n+\n+This will simultaneously rebase `branch1`, `branch2`, and `branch3`,\n+all commits they have since `base`, playing them on top of\n+`origin/main`. These three branches may have commits on top of `base`\n+that they have in common, but that does not need to be the case.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 7e3ebac3db..8fe4391976 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,7 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -118,16 +117,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL;\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 merge_result result;\n-\tstruct strbuf branch_name = STRBUF_INIT;\n \tint i, ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n+\t\tN_(\"git replay --onto <newbase> <revision-range>... # EXPERIMENTAL\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -145,18 +142,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tif (argc != 3) {\n-\t\terror(_(\"bad number of arguments\"));\n-\t\tusage_with_options(replay_usage, replay_options);\n-\t}\n-\n \tonto = peel_committish(onto_name);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n-\n \t/*\n \t * TODO: For now, let's warn when we see an option that we are\n \t * going to override after setup_revisions() below. In the\n@@ -170,8 +159,9 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\twarning(_(\"option '%s' will be overridden\"), argv[i]);\n \t}\n \n-\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n-\t\tret = error(_(\"unhandled options\"));\n+\targc = setup_revisions(argc, argv, &revs, NULL);\n+\tif (argc > 1) {\n+\t\tret = error(_(\"unrecognized argument: %s\"), argv[1]);\n \t\tgoto cleanup;\n \t}\n \n@@ -181,8 +171,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n-\tstrvec_clear(&rev_walk_args);\n-\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -224,7 +212,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tret = result.clean;\n \n cleanup:\n-\tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 3567c98362..a1da4f9ef9 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -52,7 +52,7 @@ test_expect_success 'setup bare' '\n '\n \n test_expect_success 'using replay to rebase two branches, one on top of other' '\n-\tgit replay --onto main topic1 topic2 >result &&\n+\tgit replay --onto main topic1..topic2 >result &&\n \n \ttest_line_count = 1 result &&\n \n@@ -68,8 +68,16 @@ test_expect_success 'using replay to rebase two branches, one on top of other' '\n '\n \n test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n-\tgit -C bare replay --onto main topic1 topic2 >result-bare &&\n+\tgit -C bare replay --onto main topic1..topic2 >result-bare &&\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to rebase with a conflict' '\n+\ttest_expect_code 1 git replay --onto topic1 B..conflict\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase with a conflict' '\n+\ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n+'\n+\n test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 099aefeffc..0f39ed0d08 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,7 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -201,7 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -279,7 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -357,7 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1..topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,7 +456,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -523,7 +523,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -626,7 +626,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -685,7 +685,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484363","messageId":"20231102135151.843758-13-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 12/14] replay: add --advance or 'cherry-pick' mode","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:49Z","receivedAt":"2023-11-02T13:52:39Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere is already a 'rebase' mode with `--onto`. Let's add an 'advance' or\n'cherry-pick' mode with `--advance`. This new mode will make the target\nbranch advance as we replay commits onto it.\n\nThe replayed commits should have a single tip, so that it's clear where\nthe target branch should be advanced. If they have more than one tip,\nthis new mode will error out.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt |  41 ++++++--\n builtin/replay.c             | 185 +++++++++++++++++++++++++++++++++--\n t/t3650-replay-basics.sh     |  34 +++++++\n 3 files changed, 243 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 36ddd7daed..e0c85cebf1 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n SYNOPSIS\n --------\n [verse]\n-'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n+'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n \n DESCRIPTION\n -----------\n@@ -29,14 +29,25 @@ OPTIONS\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n +\n-The update-ref command(s) in the output will update the branch(es) in\n-the revision range to point at the new commits, similar to the way how\n-`git rebase --update-refs` updates multiple branches in the affected\n-range.\n+When `--onto` is specified, the update-ref command(s) in the output will\n+update the branch(es) in the revision range to point at the new\n+commits, similar to the way how `git rebase --update-refs` updates\n+multiple branches in the affected range.\n+\n+--advance <branch>::\n+\tStarting point at which to create the new commits; must be a\n+\tbranch name.\n++\n+When `--advance` is specified, the update-ref command(s) in the output\n+will update the branch passed as an argument to `--advance` to point at\n+the new commits (in other words, this mimics a cherry-pick operation).\n \n <revision-range>::\n-\tRange of commits to replay; see \"Specifying Ranges\" in\n-\tlinkgit:git-rev-parse and the \"Commit Limiting\" options below.\n+\tRange of commits to replay. More than one <revision-range> can\n+\tbe passed, but in `--advance <branch>` mode, they should have\n+\ta single tip, so that it's clear where <branch> should point\n+\tto. See \"Specifying Ranges\" in linkgit:git-rev-parse and the\n+\t\"Commit Limiting\" options below.\n \n include::rev-list-options.txt[]\n \n@@ -51,7 +62,9 @@ input to `git update-ref --stdin`.  It is of the form:\n \tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n \n where the number of refs updated depends on the arguments passed and\n-the shape of the history being replayed.\n+the shape of the history being replayed.  When using `--advance`, the\n+number of refs updated is always one, but for `--onto`, it can be one\n+or more (rebasing multiple branches simultaneously is supported).\n \n EXIT STATUS\n -----------\n@@ -71,6 +84,18 @@ $ git replay --onto target origin/main..mybranch\n update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n ------------\n \n+To cherry-pick the commits from mybranch onto target:\n+\n+------------\n+$ git replay --advance target origin/main..mybranch\n+update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH}\n+------------\n+\n+Note that the first two examples replay the exact same commits and on\n+top of the exact same new base, they only differ in that the first\n+provides instructions to make mybranch point at the new commits and\n+the second provides instructions to make target point at them.\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 8fe4391976..145ce9d9a3 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,6 +14,7 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"strmap.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -82,6 +83,146 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+struct ref_info {\n+\tstruct commit *onto;\n+\tstruct strset positive_refs;\n+\tstruct strset negative_refs;\n+\tint positive_refexprs;\n+\tint negative_refexprs;\n+};\n+\n+static void get_ref_information(struct rev_cmdline_info *cmd_info,\n+\t\t\t\tstruct ref_info *ref_info)\n+{\n+\tint i;\n+\n+\tref_info->onto = NULL;\n+\tstrset_init(&ref_info->positive_refs);\n+\tstrset_init(&ref_info->negative_refs);\n+\tref_info->positive_refexprs = 0;\n+\tref_info->negative_refexprs = 0;\n+\n+\t/*\n+\t * When the user specifies e.g.\n+\t *   git replay origin/main..mybranch\n+\t *   git replay ^origin/next mybranch1 mybranch2\n+\t * we want to be able to determine where to replay the commits.  In\n+\t * these examples, the branches are probably based on an old version\n+\t * of either origin/main or origin/next, so we want to replay on the\n+\t * newest version of that branch.  In contrast we would want to error\n+\t * out if they ran\n+\t *   git replay ^origin/master ^origin/next mybranch\n+\t *   git replay mybranch~2..mybranch\n+\t * the first of those because there's no unique base to choose, and\n+\t * the second because they'd likely just be replaying commits on top\n+\t * of the same commit and not making any difference.\n+\t */\n+\tfor (i = 0; i < cmd_info->nr; i++) {\n+\t\tstruct rev_cmdline_entry *e = cmd_info->rev + i;\n+\t\tstruct object_id oid;\n+\t\tconst char *refexpr = e->name;\n+\t\tchar *fullname = NULL;\n+\t\tint can_uniquely_dwim = 1;\n+\n+\t\tif (*refexpr == '^')\n+\t\t\trefexpr++;\n+\t\tif (repo_dwim_ref(the_repository, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)\n+\t\t\tcan_uniquely_dwim = 0;\n+\n+\t\tif (e->flags & BOTTOM) {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->negative_refs, fullname);\n+\t\t\tif (!ref_info->negative_refexprs)\n+\t\t\t\tref_info->onto = lookup_commit_reference_gently(the_repository,\n+\t\t\t\t\t\t\t\t\t\t&e->item->oid, 1);\n+\t\t\tref_info->negative_refexprs++;\n+\t\t} else {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->positive_refs, fullname);\n+\t\t\tref_info->positive_refexprs++;\n+\t\t}\n+\n+\t\tfree(fullname);\n+\t}\n+}\n+\n+static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n+\t\t\t\t  const char *onto_name,\n+\t\t\t\t  const char **advance_name,\n+\t\t\t\t  struct commit **onto,\n+\t\t\t\t  struct strset **update_refs)\n+{\n+\tstruct ref_info rinfo;\n+\n+\tget_ref_information(cmd_info, &rinfo);\n+\tif (!rinfo.positive_refexprs)\n+\t\tdie(_(\"need some commits to replay\"));\n+\tif (onto_name && *advance_name)\n+\t\tdie(_(\"--onto and --advance are incompatible\"));\n+\telse if (onto_name) {\n+\t\t*onto = peel_committish(onto_name);\n+\t\tif (rinfo.positive_refexprs <\n+\t\t    strset_get_size(&rinfo.positive_refs))\n+\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t} else if (*advance_name) {\n+\t\tstruct object_id oid;\n+\t\tchar *fullname = NULL;\n+\n+\t\t*onto = peel_committish(*advance_name);\n+\t\tif (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name),\n+\t\t\t     &oid, &fullname, 0) == 1) {\n+\t\t\t*advance_name = fullname;\n+\t\t} else {\n+\t\t\tdie(_(\"argument to --advance must be a reference\"));\n+\t\t}\n+\t\tif (rinfo.positive_refexprs > 1)\n+\t\t\tdie(_(\"cannot advance target with multiple sources because ordering would be ill-defined\"));\n+\t} else {\n+\t\tint positive_refs_complete = (\n+\t\t\trinfo.positive_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.positive_refs));\n+\t\tint negative_refs_complete = (\n+\t\t\trinfo.negative_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.negative_refs));\n+\t\t/*\n+\t\t * We need either positive_refs_complete or\n+\t\t * negative_refs_complete, but not both.\n+\t\t */\n+\t\tif (rinfo.negative_refexprs > 0 &&\n+\t\t    positive_refs_complete == negative_refs_complete)\n+\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\tif (negative_refs_complete) {\n+\t\t\tstruct hashmap_iter iter;\n+\t\t\tstruct strmap_entry *entry;\n+\n+\t\t\tif (rinfo.negative_refexprs == 0)\n+\t\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t\t\telse if (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\t\telse if (rinfo.positive_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\n+\t\t\t/* Only one entry, but we have to loop to get it */\n+\t\t\tstrset_for_each_entry(&rinfo.negative_refs,\n+\t\t\t\t\t      &iter, entry) {\n+\t\t\t\t*advance_name = entry->key;\n+\t\t\t}\n+\t\t} else { /* positive_refs_complete */\n+\t\t\tif (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine correct base for --onto\"));\n+\t\t\tif (rinfo.negative_refexprs == 1)\n+\t\t\t\t*onto = rinfo.onto;\n+\t\t}\n+\t}\n+\tif (!*advance_name) {\n+\t\t*update_refs = xcalloc(1, sizeof(**update_refs));\n+\t\t**update_refs = rinfo.positive_refs;\n+\t\tmemset(&rinfo.positive_refs, 0, sizeof(**update_refs));\n+\t}\n+\tstrset_clear(&rinfo.negative_refs);\n+\tstrset_clear(&rinfo.positive_refs);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n \t\t\t\t\t  struct commit *last_commit,\n \t\t\t\t\t  struct merge_options *merge_opt,\n@@ -114,20 +255,26 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n-\tstruct commit *onto;\n+\tconst char *advance_name = NULL;\n+\tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL;\n+\n \tstruct rev_info revs;\n+\tstruct commit *last_commit = NULL;\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n+\tstruct strset *update_refs = NULL;\n \tint i, ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <revision-range>... # EXPERIMENTAL\"),\n+\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"advance\", &advance_name,\n+\t\t\t   N_(\"branch\"),\n+\t\t\t   N_(\"make replay advance given branch\")),\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n@@ -137,13 +284,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n \t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n \n-\tif (!onto_name) {\n-\t\terror(_(\"option --onto is mandatory\"));\n+\tif (!onto_name && !advance_name) {\n+\t\terror(_(\"option --onto or --advance is mandatory\"));\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tonto = peel_committish(onto_name);\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \t/*\n@@ -171,6 +316,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n+\tdetermine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n+\t\t\t      &onto, &update_refs);\n+\n+\tif (!onto) /* FIXME: Should handle replaying down to root commit */\n+\t\tdie(\"Replaying down to root commit is not supported yet!\");\n+\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -179,6 +330,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n+\n \tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n@@ -193,12 +345,15 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Update any necessary branches */\n+\t\tif (advance_name)\n+\t\t\tcontinue;\n \t\tdecoration = get_name_decoration(&commit->object);\n \t\tif (!decoration)\n \t\t\tcontinue;\n-\n \t\twhile (decoration) {\n-\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n+\t\t\t    strset_contains(update_refs, decoration->name)) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\n@@ -208,10 +363,22 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\t/* In --advance mode, advance the target ref */\n+\tif (result.clean == 1 && advance_name) {\n+\t\tprintf(\"update %s %s %s\\n\",\n+\t\t       advance_name,\n+\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t       oid_to_hex(&onto->object.oid));\n+\t}\n+\n \tmerge_finalize(&merge_opt, &result);\n \tret = result.clean;\n \n cleanup:\n+\tif (update_refs) {\n+\t\tstrset_clear(update_refs);\n+\t\tfree(update_refs);\n+\t}\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex a1da4f9ef9..68a87e7803 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -80,4 +80,38 @@ test_expect_success 'using replay on bare repo to rebase with a conflict' '\n \ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n '\n \n+test_expect_success 'using replay to perform basic cherry-pick' '\n+\t# The differences between this test and previous ones are:\n+\t#   --advance vs --onto\n+\t# 2nd field of result is refs/heads/main vs. refs/heads/topic2\n+\t# 4th field of result is hash for main instead of hash for topic2\n+\n+\tgit replay --advance main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/main \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse main >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n+\tgit -C bare replay --advance main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n+test_expect_success 'replay on bare repo fails with both --advance and --onto' '\n+\ttest_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare\n+'\n+\n+test_expect_success 'replay fails when both --advance and --onto are omitted' '\n+\ttest_must_fail git replay topic1..topic2 >result\n+'\n+\n test_done\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484364","messageId":"20231102135151.843758-14-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 13/14] replay: add --contained to rebase contained branches","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:50Z","receivedAt":"2023-11-02T13:52:40Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's add a `--contained` option that can be used along with\n`--onto` to rebase all the branches contained in the <revision-range>\nargument.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt | 12 +++++++++++-\n builtin/replay.c             | 13 +++++++++++--\n t/t3650-replay-basics.sh     | 29 +++++++++++++++++++++++++++++\n 3 files changed, 51 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex e0c85cebf1..e7551aec54 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n SYNOPSIS\n --------\n [verse]\n-'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n+'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n \n DESCRIPTION\n -----------\n@@ -96,6 +96,16 @@ top of the exact same new base, they only differ in that the first\n provides instructions to make mybranch point at the new commits and\n the second provides instructions to make target point at them.\n \n+What if you have a stack of branches, one depending upon another, and\n+you'd really like to rebase the whole set?\n+\n+------------\n+$ git replay --contained --onto origin/main origin/main..tipbranch\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}\n+------------\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 145ce9d9a3..08ff9bab5e 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -258,6 +258,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tconst char *advance_name = NULL;\n \tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n+\tint contained = 0;\n \n \tstruct rev_info revs;\n \tstruct commit *last_commit = NULL;\n@@ -268,7 +269,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tint i, ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\"),\n+\t\tN_(\"git replay ([--contained] --onto <newbase> | --advance <branch>) \"\n+\t\t   \"<revision-range>... # EXPERIMENTAL\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -278,6 +280,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_BOOL(0, \"contained\", &contained,\n+\t\t\t N_(\"advance all branches contained in revision-range\")),\n \t\tOPT_END()\n \t};\n \n@@ -289,6 +293,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n+\tif (advance_name && contained)\n+\t\tdie(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t    \"--advance\", \"--contained\");\n+\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \t/*\n@@ -353,7 +361,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tcontinue;\n \t\twhile (decoration) {\n \t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n-\t\t\t    strset_contains(update_refs, decoration->name)) {\n+\t\t\t    (contained || strset_contains(update_refs,\n+\t\t\t\t\t\t\t  decoration->name))) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 68a87e7803..d6286f9580 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -114,4 +114,33 @@ test_expect_success 'replay fails when both --advance and --onto are omitted' '\n \ttest_must_fail git replay topic1..topic2 >result\n '\n \n+test_expect_success 'using replay to also rebase a contained branch' '\n+\tgit replay --contained --onto main main..topic3 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines H G F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic1 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic1 >>expect &&\n+\tprintf \"update refs/heads/topic3 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic3 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to also rebase a contained branch' '\n+\tgit -C bare replay --contained --onto main main..topic3 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n test_done\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484365","messageId":"20231102135151.843758-15-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v6 14/14] replay: stop assuming replayed branches do not diverge","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T13:51:51Z","receivedAt":"2023-11-02T13:52:41Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command is able to replay multiple branches but when some of\nthem are based on other replayed branches, their commit should be\nreplayed onto already replayed commits.\n\nFor this purpose, let's store the replayed commit and its original\ncommit in a key value store, so that we can easily find and reuse a\nreplayed commit instead of the original one.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 44 ++++++++++++++++++++++++++--------\n t/t3650-replay-basics.sh | 52 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 86 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 08ff9bab5e..c3d53ff0cd 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -223,20 +223,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n \tstrset_clear(&rinfo.positive_refs);\n }\n \n+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,\n+\t\t\t\t    struct commit *commit,\n+\t\t\t\t    struct commit *fallback)\n+{\n+\tkhint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);\n+\tif (pos == kh_end(replayed_commits))\n+\t\treturn fallback;\n+\treturn kh_value(replayed_commits, pos);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n-\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  kh_oid_map_t *replayed_commits,\n+\t\t\t\t\t  struct commit *onto,\n \t\t\t\t\t  struct merge_options *merge_opt,\n \t\t\t\t\t  struct merge_result *result)\n {\n-\tstruct commit *base;\n+\tstruct commit *base, *replayed_base;\n \tstruct tree *pickme_tree, *base_tree;\n \n \tbase = pickme->parents->item;\n+\treplayed_base = mapped_commit(replayed_commits, base, onto);\n \n+\tresult->tree = repo_get_commit_tree(the_repository, replayed_base);\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n-\tmerge_opt->branch1 = short_commit_name(last_commit);\n+\tmerge_opt->branch1 = short_commit_name(replayed_base);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -250,7 +263,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tmerge_opt->ancestor = NULL;\n \tif (!result->clean)\n \t\treturn NULL;\n-\treturn create_commit(result->tree, pickme, last_commit);\n+\treturn create_commit(result->tree, pickme, replayed_base);\n }\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n@@ -266,6 +279,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n \tstruct strset *update_refs = NULL;\n+\tkh_oid_map_t *replayed_commits;\n \tint i, ret = 0;\n \n \tconst char * const replay_usage[] = {\n@@ -338,21 +352,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\n-\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n+\treplayed_commits = kh_init_oid_map();\n \twhile ((commit = get_revision(&revs))) {\n \t\tconst struct name_decoration *decoration;\n+\t\tkhint_t pos;\n+\t\tint hr;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tlast_commit = pick_regular_commit(commit, replayed_commits, onto,\n+\t\t\t\t\t\t  &merge_opt, &result);\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Record commit -> last_commit mapping */\n+\t\tpos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);\n+\t\tif (hr == 0)\n+\t\t\tBUG(\"Duplicate rewritten commit: %s\\n\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tkh_value(replayed_commits, pos) = last_commit;\n+\n \t\t/* Update any necessary branches */\n \t\tif (advance_name)\n \t\t\tcontinue;\n@@ -381,13 +404,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-\tret = result.clean;\n-\n-cleanup:\n+\tkh_destroy_oid_map(replayed_commits);\n \tif (update_refs) {\n \t\tstrset_clear(update_refs);\n \t\tfree(update_refs);\n \t}\n+\tret = result.clean;\n+\n+cleanup:\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex d6286f9580..389670262e 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -143,4 +143,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to rebase multiple divergent branches' '\n+\tgit replay --onto main ^topic1 topic2 topic4 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines J I M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\tprintf \"update refs/heads/topic4 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic4 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '\n+\tgit -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&\n+\n+\ttest_line_count = 4 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\t>expect &&\n+\tfor i in 2 1 3 4\n+\tdo\n+\t\tprintf \"update refs/heads/topic$i \" >>expect &&\n+\t\tprintf \"%s \" $(grep topic$i result | cut -f 3 -d \" \") >>expect &&\n+\t\tgit -C bare rev-parse topic$i >>expect || return 1\n+\tdone &&\n+\n+\ttest_cmp expect result &&\n+\n+\ttest_write_lines F C M L B A >expect1 &&\n+\ttest_write_lines E D C M L B A >expect2 &&\n+\ttest_write_lines H G F C M L B A >expect3 &&\n+\ttest_write_lines J I M L B A >expect4 &&\n+\n+\tfor i in 1 2 3 4\n+\tdo\n+\t\tgit -C bare log --format=%s $(grep topic$i result | cut -f 3 -d \" \") >actual &&\n+\t\ttest_cmp expect$i actual || return 1\n+\tdone\n+'\n+\n test_done\n-- \n2.42.0.496.g529a7fda40\n\n"},{"id":"484367","messageId":"CAP8UFD0iS2za20Mzy2Dy7XLnhHTwN-_=8gV-NCzxzxcfaF-TMQ@mail.gmail.com","threadId":"59560","inReplyTo":"CABPp-BGfsda-8CK7_YPJfhGMfpLqdDeB8X6wnqPAGmhiY4KjDA@mail.gmail.com","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T14:44:29Z","receivedAt":"2023-11-02T14:44:49Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Elijah and Dscho,\n\nOn Mon, Oct 30, 2023 at 6:18 PM Elijah Newren <newren@gmail.com> wrote:\n> On Sun, Oct 29, 2023 at 7:14 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> > On Sat, 28 Oct 2023, Elijah Newren wrote:\n\n> > > I can see why this might sometimes be useful for exclusively linear\n> > > history, but it seems to open a can of worms and possibly unfixable\n> > > corner cases for non-linear history.  I'd rather not do this, or at\n> > > least pull it out of this series and let us discuss it in some follow\n> > > up series.  There are some other alternatives that might handle such\n> > > usecases better.\n> >\n> > I find myself wishing for an easy way to reverse commits, if only to\n> > switch around the latest two commits while stopped during a rebase.\n> >\n> > So it would have been nice for me if there had been an easy, worktree-less\n> > way to make that happen.\n>\n> Seems reasonable; we'll definitely want to keep this in mind.\n>\n> > I guess this would be going in the direction of reordering commits,\n> > though, something we deliberately left for later?\n>\n> Yes, I think that's a good framing for it.\n\nOk, in the v6 I just sent, a warning() is emitted when `--reverse` is\npassed and the option has no effect.\n\nI agree that handling such options in a better way should be left for\nlater patch series.\n\nThanks both!\n"},{"id":"484368","messageId":"CAP8UFD38KEdpArdixVHpTFGF__G89s_sMUHa+gmWoLULv+CvJw@mail.gmail.com","threadId":"59560","inReplyTo":"CABPp-BGCzxL-kpOvZzWRTJcx2v18QHm5ev8bFv7bm0dyNqhKug@mail.gmail.com","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T14:48:13Z","receivedAt":"2023-11-02T14:48:29Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sun, Oct 29, 2023 at 7:00 AM Elijah Newren <newren@gmail.com> wrote:\n\n> On Tue, Oct 10, 2023 at 5:39 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n\n> >     @@ Documentation/git-replay.txt (new)\n> >      +\n> >      +NAME\n> >      +----\n> >     -+git-replay - Replay commits on a different base, without touching working tree\n> >     ++git-replay - Replay commits on a new base, works on bare repos too\n>\n> really minor point: \"works on\" or \"works in\" or \"works with\" ?\n\nI have changed it to \"works with\", I hope it sounds better. Also I\nforgot to talk about this change in the cover letter, sorry about\nthat.\n"},{"id":"484369","messageId":"CAP8UFD04PbOLgep3-s-_4_fMbqtrWMR87K1cG-DsKnbyHEBT_w@mail.gmail.com","threadId":"59560","inReplyTo":"CABPp-BFrVfGHOrBk7g=4TkGxDv=oSqF1FOkhp6WVbxUV-2yveQ@mail.gmail.com","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T14:59:21Z","receivedAt":"2023-11-02T14:59:40Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sun, Oct 29, 2023 at 7:02 AM Elijah Newren <newren@gmail.com> wrote:\n\n> On Thu, Oct 26, 2023 at 6:44 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> > On Tue, 10 Oct 2023, Christian Couder wrote:\n> >\n> [...]\n> > >      +  /* requirements/overrides for revs */\n> > >     -+  revs.reverse = 1;\n> > >     ++  revs.reverse = !revs.reverse;\n> > >      +  revs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n> > >      +  revs.topo_order = 1;\n> > >      +  revs.simplify_history = 0;\n> >\n> > This still overrides a couple of command-line options, _silently_. I would\n> > prefer those three assignments to be moved just before the\n> > `setup_revisions()` call.\n> >\n> > Letting users override these settings may not make much sense, but it\n> > makes even less sense to pretend to let them override the settings and\n> > then just ignore them without warning. (See also\n> > https://en.wikipedia.org/wiki/Principle_of_least_astonishment.)\n> >\n> > Moving these three assignments before the `setup_revisions()` call would\n> > neatly remedy that.\n>\n> I agree that warnings or error messages would be better.\n\nOk, a warning() is emitted now in case an command-line option will be\noverridden.\n\n> But if we're talking about something short of that, I'd actually argue\n> the opposite of what you do here.  I intentionally moved these\n> assignments after setup_revisions(), and in my mind, the purpose in\n> doing so was to satisfy the Principle of Least Astonishment.  My\n> experience with git-fast-export, where some settings are made before\n> calling setup_revisions() and then can be overridden, and then do\n> completely hideous things, was much worse to me than just admitting\n> the flags are bad given the various assumptions the tool makes.  I\n> have some patches sitting around to fix fast-export that I never got\n> around to upstreaming, but when it came time to implement git-replay,\n> I made sure to fix what I viewed as the bigger problem.\n\nI hope you will be able to upstream such changes.\n\n> [...]\n> > >     @@ Documentation/git-replay.txt (new)\n> > >      +\n> > >      +NAME\n> > >      +----\n> > >     -+git-replay - Replay commits on a different base, without touching working tree\n> > >     ++git-replay - Replay commits on a new base, works on bare repos too\n> > >      +\n> > >      +\n> > >      +SYNOPSIS\n> >\n> > As mentioned in\n> > https://lore.kernel.org/git/03460733-0219-c648-5757-db1958f8042e@gmx.de/,\n> > I would like the `EXPERIMENTAL` label to be shown prominently here.\n> > Probably not only the `SYNOPSIS` as I had originally suggested but also in\n> > the `NAME`.\n\nOk, I have made changes in the v6 I just sent, so that there is\nEXPERIMENTAL both in the NAME and SYNOPSIS.\n\n> > Otherwise we may end up with the same situation as with the (from my\n> > perspective, failed) `git switch`/`git restore` experiment, where we\n> > wanted to explore a better user experience than the overloaded `git\n> > checkout` command, only to now be stuck with having to maintain\n> > backward-compatibility for `git switch`/`git restore` command-line options\n> > that were not meant to be set in stone but to be iterated on, instead. A\n> > real-life demonstration of [Hyrum's Law](hyrumslaw.com/), if you like. Or,\n> > from a different angle, we re-enacted https://xkcd.com/927/ in a way.\n\nNit: Hyrum's Law says:\n\n\"With a sufficient number of users of an API,\nit does not matter what you promise in the contract:\nall observable behaviors of your system\nwill be depended on by somebody.\"\n\nThe doc is part of the contract, which according to this law doesn't\nmatter. So I don't see why you use this law to suggest a doc change.\n\n> > I'd like to suggest to learn from history and avoid this by tacking on a\n> > warning label right at the top of the documentation. We may eventually\n> > manage to iterate `git replay` to a point where it is totally capable to\n> > supersede `git rebase`, by doing everything the latter does, except\n> > better, who knows? But we _do_ need the liberty to make sweeping changes\n> > to this new builtin if we want to have a prayer of doing that. And I fear\n> > that not even mentioning the EXPERIMENTAL nature right at the top of the\n> > manual page would just render us into that undesirable corner.\n>\n> I fully support this.  Absolutely, 100%.\n\nOk. Note that as I changed the SYNOPSIS, I also had to change the\nusage string, so that it matches the SYNOPSIS, otherwise a test would\nfail. So there is \"EXPERIMENTAL\" in the usage string too.\n"},{"id":"484370","messageId":"CAP8UFD09dZbrbebRFZvarY71q5Vc0YBfRQHbTg7A3H-qM2g8fg@mail.gmail.com","threadId":"59560","inReplyTo":"bd872b81-80a9-5e4e-dcb6-faebc9671848@gmx.de","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-02T15:06:47Z","receivedAt":"2023-11-02T15:07:03Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Dscho and Elijah,\n\nOn Thu, Oct 26, 2023 at 3:44 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n\n> In addition, I am still a bit uneasy with introducing both the manual page\n> and the test script in this commit (see my comments in\n> https://lore.kernel.org/git/03460733-0219-c648-5757-db1958f8042e@gmx.de/).\n> It would be better to uphold our high standard and introduce scaffolds for\n> both files in the first commit, then populate the file contents\n> incrementally in the same the patches that introduce the corresponding\n> options/features/changes.\n\nI have tried to improve on that in the v6 I just sent, but there are\nmany patches implementing changes in behavior that I think weren't\nworth documenting and testing in `test-tool fast-rebase` (which had no\ndoc and no test) and that aren't worth documenting and testing\nspecifically in `git replay` either.\n\nThanks!\n"},{"id":"484501","messageId":"CABPp-BE6G2qaF50bhz-CwxSsvxGDHzwvsWtfQO4zVcX6ERppLw@mail.gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"Re: [PATCH v6 00/14] Introduce new `git replay` command","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-11-07T02:43:50Z","receivedAt":"2023-11-07T02:44:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nLooking good, just one comment on one small hunk...\n\nOn Thu, Nov 2, 2023 at 6:52 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n[...]\n\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>      -\n>         strvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n>\n>     ++  /*\n>     ++   * TODO: For now, let's warn when we see an option that we are\n>     ++   * going to override after setup_revisions() below. In the\n>     ++   * future we might want to either die() or allow them if we\n>     ++   * think they could be useful though.\n>     ++   */\n>     ++  for (i = 0; i < argc; i++) {\n>     ++          if (!strcmp(argv[i], \"--reverse\") || !strcmp(argv[i], \"--date-order\") ||\n>     ++              !strcmp(argv[i], \"--topo-order\") || !strcmp(argv[i], \"--author-date-order\") ||\n>     ++              !strcmp(argv[i], \"--full-history\"))\n>     ++                  warning(_(\"option '%s' will be overridden\"), argv[i]);\n>     ++  }\n>     ++\n\nTwo things:\n\n1) Not sure it makes sense to throw a warning with --topo-order or\n--full-history, since they would result in a value matching what we\nwould be setting anyway.\n\n2) This seems like an inefficient way to provide this warning; could\nwe avoid parsing the arguments for an extra time?  Perhaps instead\n  a) set the desired values here, before setup_revisions()\n  b) after setup_revisions, check whether these values differ from the\ndesired values, if so throw a warning.\n  c) set the desired values, again\n?\n"},{"id":"484517","messageId":"CAP8UFD24fzhiecJtANqEsxvh1mxT4pKR=QjfUFZh8C6HQE-k1A@mail.gmail.com","threadId":"59560","inReplyTo":"CABPp-BE6G2qaF50bhz-CwxSsvxGDHzwvsWtfQO4zVcX6ERppLw@mail.gmail.com","subject":"Re: [PATCH v6 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-07T09:43:22Z","receivedAt":"2023-11-07T09:43:37Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi,\n\nOn Tue, Nov 7, 2023 at 3:44 AM Elijah Newren <newren@gmail.com> wrote:\n> Looking good, just one comment on one small hunk...\n>\n> On Thu, Nov 2, 2023 at 6:52 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n> >\n> [...]\n>\n> >     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n> >      -\n> >         strvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n> >\n> >     ++  /*\n> >     ++   * TODO: For now, let's warn when we see an option that we are\n> >     ++   * going to override after setup_revisions() below. In the\n> >     ++   * future we might want to either die() or allow them if we\n> >     ++   * think they could be useful though.\n> >     ++   */\n> >     ++  for (i = 0; i < argc; i++) {\n> >     ++          if (!strcmp(argv[i], \"--reverse\") || !strcmp(argv[i], \"--date-order\") ||\n> >     ++              !strcmp(argv[i], \"--topo-order\") || !strcmp(argv[i], \"--author-date-order\") ||\n> >     ++              !strcmp(argv[i], \"--full-history\"))\n> >     ++                  warning(_(\"option '%s' will be overridden\"), argv[i]);\n> >     ++  }\n> >     ++\n>\n> Two things:\n>\n> 1) Not sure it makes sense to throw a warning with --topo-order or\n> --full-history, since they would result in a value matching what we\n> would be setting anyway.\n\nYeah, I am not sure about this either. About \"--reverse\" I think it\nmakes sense because even if the command is setting the reverse bit, it\nwould be possible to reverse the reverse like Dscho wanted. But I\nagree \"--topo-order\" and \"--full-history\" will very unlikely be reused\nfor anything else in the future.\n\n> 2) This seems like an inefficient way to provide this warning; could\n> we avoid parsing the arguments for an extra time?  Perhaps instead\n>   a) set the desired values here, before setup_revisions()\n>   b) after setup_revisions, check whether these values differ from the\n> desired values, if so throw a warning.\n>   c) set the desired values, again\n\nYeah, that would work. The downside is that it would be more difficult\nin the warning to tell the user which command line option was\noverridden as there are some values changed by different options.\nMaybe we can come up with a warning message that is still useful and\nenough for now, as the command is supposed to be used by experienced\nusers. Perhaps something like:\n\nwarning(_(\"some rev walking options will be overridden as '%s' bit in\n'struct rev_info' will be forced\"), \"sort_order\");\n\nThanks!\n"},{"id":"484573","messageId":"cbeabc59-6d2c-a4a6-f7a2-394d72ee7bd3@gmx.de","threadId":"59560","inReplyTo":"CABPp-BE6G2qaF50bhz-CwxSsvxGDHzwvsWtfQO4zVcX6ERppLw@mail.gmail.com","subject":"Re: [PATCH v6 00/14] Introduce new `git replay` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-11-08T12:19:43Z","receivedAt":"2023-11-08T12:19:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 6 Nov 2023, Elijah Newren wrote:\n\n> On Thu, Nov 2, 2023 at 6:52 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n> >\n> [...]\n>\n> >     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n> >      -\n> >         strvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n> >\n> >     ++  /*\n> >     ++   * TODO: For now, let's warn when we see an option that we are\n> >     ++   * going to override after setup_revisions() below. In the\n> >     ++   * future we might want to either die() or allow them if we\n> >     ++   * think they could be useful though.\n> >     ++   */\n> >     ++  for (i = 0; i < argc; i++) {\n> >     ++          if (!strcmp(argv[i], \"--reverse\") || !strcmp(argv[i], \"--date-order\") ||\n> >     ++              !strcmp(argv[i], \"--topo-order\") || !strcmp(argv[i], \"--author-date-order\") ||\n> >     ++              !strcmp(argv[i], \"--full-history\"))\n> >     ++                  warning(_(\"option '%s' will be overridden\"), argv[i]);\n> >     ++  }\n> >     ++\n>\n> [...] This seems like an inefficient way to provide this warning;\n\nNot only inefficient: think about the false positive when looking at a\ncommand-line containing `--grep --reverse`. In this instance, `--reverse`\nis an argument of the `--grep` option, but would be mistaken for an option\nin its own right.\n\nCiao,\nJohannes\n"},{"id":"484574","messageId":"cdb9e449-8c60-b21e-bcd3-0944d2b744b5@gmx.de","threadId":"59560","inReplyTo":"CAP8UFD04PbOLgep3-s-_4_fMbqtrWMR87K1cG-DsKnbyHEBT_w@mail.gmail.com","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-11-08T12:25:13Z","receivedAt":"2023-11-08T12:25:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Thu, 2 Nov 2023, Christian Couder wrote:\n\n> On Sun, Oct 29, 2023 at 7:02 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> > On Thu, Oct 26, 2023 at 6:44 AM Johannes Schindelin\n> > <Johannes.Schindelin@gmx.de> wrote:\n> > >\n> > > As mentioned in\n> > > https://lore.kernel.org/git/03460733-0219-c648-5757-db1958f8042e@gmx.de/,\n> > > I would like the `EXPERIMENTAL` label to be shown prominently here.\n> > > Probably not only the `SYNOPSIS` as I had originally suggested but\n> > > also in the `NAME`.\n>\n> Ok, I have made changes in the v6 I just sent, so that there is\n> EXPERIMENTAL both in the NAME and SYNOPSIS.\n>\n> > > Otherwise we may end up with the same situation as with the (from my\n> > > perspective, failed) `git switch`/`git restore` experiment, where we\n> > > wanted to explore a better user experience than the overloaded `git\n> > > checkout` command, only to now be stuck with having to maintain\n> > > backward-compatibility for `git switch`/`git restore` command-line options\n> > > that were not meant to be set in stone but to be iterated on, instead. A\n> > > real-life demonstration of [Hyrum's Law](hyrumslaw.com/), if you like. Or,\n> > > from a different angle, we re-enacted https://xkcd.com/927/ in a way.\n>\n> Nit: Hyrum's Law says:\n>\n> \"With a sufficient number of users of an API,\n> it does not matter what you promise in the contract:\n> all observable behaviors of your system\n> will be depended on by somebody.\"\n>\n> The doc is part of the contract, which according to this law doesn't\n> matter. So I don't see why you use this law to suggest a doc change.\n\nYou're right. In addition to the documentation (where we definitely need\nto state the experimental nature of the command), we may want to consider\nadding the `EXPERIMENTAL` label not only to the output of `git replay -h`,\nbut show also a warning for every `git replay` invocation cautioning users\nagainst depending on the current command-line options of this command.\n\nCiao,\nJohannes\n"},{"id":"484575","messageId":"1f2959d7-6e0f-bc8e-8341-56a1221dd48d@gmx.de","threadId":"59560","inReplyTo":"CAP8UFD09dZbrbebRFZvarY71q5Vc0YBfRQHbTg7A3H-qM2g8fg@mail.gmail.com","subject":"Re: [PATCH v5 00/14] Introduce new `git replay` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-11-08T12:25:59Z","receivedAt":"2023-11-08T12:26:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Thu, 2 Nov 2023, Christian Couder wrote:\n\n> On Thu, Oct 26, 2023 at 3:44 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n>\n> > In addition, I am still a bit uneasy with introducing both the manual page\n> > and the test script in this commit (see my comments in\n> > https://lore.kernel.org/git/03460733-0219-c648-5757-db1958f8042e@gmx.de/).\n> > It would be better to uphold our high standard and introduce scaffolds for\n> > both files in the first commit, then populate the file contents\n> > incrementally in the same the patches that introduce the corresponding\n> > options/features/changes.\n>\n> I have tried to improve on that in the v6 I just sent, but there are\n> many patches implementing changes in behavior that I think weren't\n> worth documenting and testing in `test-tool fast-rebase` (which had no\n> doc and no test) and that aren't worth documenting and testing\n> specifically in `git replay` either.\n\nThank you for putting in the effort. I appreciate it very much.\n\nCiao,\nJohannes\n"},{"id":"484576","messageId":"fcfacd1a-cf5a-a393-d2e0-3c0388ae3529@gmx.de","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"Re: [PATCH v6 00/14] Introduce new `git replay` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-11-08T12:47:28Z","receivedAt":"2023-11-08T12:47:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Thu, 2 Nov 2023, Christian Couder wrote:\n\n> # Range-diff between v5 and v6\n>\n> (Sorry it's very long mostly due to doc and test changes over a number\n> of patches.)\n\nI am grateful for the detailed range-diff that let's me focus exclusively\non what changed between iterations.\n\n>  1:  72c34a0eba =  1:  fac0a9dff4 t6429: remove switching aspects of fast-rebase\n>  2:  f85e6c823c !  2:  8a605ddef8 replay: introduce new builtin\n>     @@ Commit message\n>          For now, this is just a rename from `t/helper/test-fast-rebase.c` into\n>          `builtin/replay.c` with minimal changes to make it build appropriately.\n>\n>     +    There is a stub documentation and a stub test script though.\n>     +\n>          Subsequent commits will flesh out its capabilities and make it a more\n>          standard regular builtin.\n>\n>     @@ .gitignore\n>       /git-rerere\n>       /git-reset\n>\n>     + ## Documentation/git-replay.txt (new) ##\n>     +@@\n>     ++git-replay(1)\n>     ++=============\n>     ++\n>     ++NAME\n>     ++----\n>     ++git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos too\n>     ++\n>     ++\n>     ++SYNOPSIS\n>     ++--------\n>     ++[verse]\n>     ++'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n\nTechnically, at this stage `git replay` requires precisely 5 arguments, so\nthe `<revision>...` is incorrect and should be `<upstream> <branch>`\ninstead. But it's not worth a new iteration to fix this.\n\n>     ++\n>     ++DESCRIPTION\n>     ++-----------\n>     ++\n>     ++Takes a range of commits and replays them onto a new location.\n>     ++\n>     ++THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n>     ++\n>     ++OPTIONS\n>     ++-------\n>     ++\n>     ++--onto <newbase>::\n>     ++  Starting point at which to create the new commits.  May be any\n>     ++  valid commit, and not just an existing branch name.\n>     ++\n\nTraditionally, this would be a place to describe the `<revision>` argument\n(or, in this patch, to reflect the current state of `builtin/replay.c`,\nthe `<upstream> <branch>` arguments instead).\n\n>     ++EXIT STATUS\n>     ++-----------\n>     ++\n>     ++For a successful, non-conflicted replay, the exit status is 0.  When\n>     ++the replay has conflicts, the exit status is 1.  If the replay is not\n>     ++able to complete (or start) due to some kind of error, the exit status\n>     ++is something other than 0 or 1.\n>     ++\n>     ++GIT\n>     ++---\n>     ++Part of the linkgit:git[1] suite\n>     +\n>       ## Makefile ##\n> [... snipping non-controversial changes ...]\n>  6:  37d545d5d6 !  6:  edafe4846f replay: change rev walking options\n> [... snipping changes Elijah already talked about ...]\n>  7:  2943f08926 =  7:  b81574744a replay: add an important FIXME comment about gpg signing\n>  8:  f81962ba41 =  8:  b08ad2b2f1 replay: remove progress and info output\n>  9:  236747497e !  9:  5099c94d2e replay: remove HEAD related sanity check\n> {... snipping non-controversial changes ...]\n\nThank you!\n\nCiao,\nJohannes\n"},{"id":"484919","messageId":"20231115143327.2441397-2-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 01/14] t6429: remove switching aspects of fast-rebase","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:14Z","receivedAt":"2023-11-15T14:33:49Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAt the time t6429 was written, merge-ort was still under development,\ndid not have quite as many tests, and certainly was not widely deployed.\nSince t6429 was exercising some codepaths just a little differently, we\nthought having them also test the \"merge_switch_to_result()\" bits of\nmerge-ort was useful even though they weren't intrinsic to the real\npoint of these tests.\n\nHowever, the value provided by doing extra testing of the\n\"merge_switch_to_result()\" bits has decreased a bit over time, and it's\nactively making it harder to refactor `test-tool fast-rebase` into `git\nreplay`, which we are going to do in following commits.  Dispense with\nthese bits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/helper/test-fast-rebase.c              | 9 +--------\n t/t6429-merge-sequence-rename-caching.sh | 9 +++++++--\n 2 files changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c\nindex cac20a72b3..2bfab66b1b 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/t/helper/test-fast-rebase.c\n@@ -194,7 +194,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n-\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\tmerge_finalize(&merge_opt, &result);\n \n \tif (result.clean < 0)\n \t\texit(128);\n@@ -213,9 +213,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n-\n-\t\tprime_cache_tree(the_repository, the_repository->index,\n-\t\t\t\t result.tree);\n \t} else {\n \t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n@@ -228,10 +225,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t\tdie(\"Failed to update %s\", argv[4]);\n \t\t}\n \t}\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-\n \tret = (result.clean == 0);\n cleanup:\n \tstrbuf_release(&reflog_msg);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex d02fa16614..75d3fd2dba 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -72,6 +72,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \t\tgit switch upstream &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n@@ -200,6 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -277,6 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -356,8 +359,6 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n \t\t#git cherry-pick upstream..topic &&\n \n-\t\tgrep CONFLICT..rename/rename output &&\n-\n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n \t)\n@@ -456,6 +457,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -522,6 +524,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -624,6 +627,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -682,6 +686,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484920","messageId":"20231115143327.2441397-1-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231102135151.843758-1-christian.couder@gmail.com","subject":"[PATCH v7 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:13Z","receivedAt":"2023-11-15T14:33:50Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"# Intro\n\n`git replay` has initially been developed entirely by Elijah Newren\nmostly last year (2022) at:\n\nhttps://github.com/newren/git/commits/replay\n\nI took over this year to polish and upstream it as GitLab is\ninterested in replacing libgit2, and for that purpose needs a command\nto do server side (so without using a worktree) rebases, cherry-picks\nand reverts.\n\nI reduced the number of commits and features in this patch series,\ncompared to what Elijah already developed. Especially I stopped short\nof replaying merge commits and replaying interactively. These and\nother features might be upstreamed in the future after this patch\nseries has graduated.\n\nThe focus in this series is to make it a good plumbing command that\ncan already be used server side and that replaces the \"fast-rebase\"\ntest-tool command. So things to make it easier to use on the command\nline, and more advanced features (like replaying merges) are left out.\n\nIt looks like GitHub has actually already been using version 3 of this\npatch series in production with good results. See:\n\nhttps://github.blog/2023-07-27-scaling-merge-ort-across-github/\nhttps://lore.kernel.org/git/304f2a49-5e05-7655-9f87-2011606df5db@gmx.de/\n\n# Content of this cover letter\n\nThe \"Quick Overview\" and \"Reasons for diverging from cherry-pick &\nrebase\" sections just below are describing the purpose of the new\ncommand in the big scheme of things. They are taken from Elijah's\ndesign notes\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt)\nand describe what we want this command to become and the reasons for\nthat, not what the command is after only this patch series. Also these\ndesign notes were written at least one year ago, so parts of those 2\nsections are not true anymore. I have added Phillip Wood's or Felipe\nContreras' notes (thanks to them) where that's the case, but some now\nflawed parts may have missed.\n\nAfter these two sections, starting with the \"Important limitations\"\nsection, you will find sections describing what is actually in this\npatch series.\n\nMore interesting material is available in Elijah's design notes like\nan \"Intro via examples\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L37-L132),\na discussion about \"Preserving topology, replaying merges\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L264-L341)\nand a \"Current status\" section describing Elijah's work\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L344-L392)\nbefore I started working on upstreaming it.\n\nI have not included this material here though, as the documentation\nadded by this patch series for the `git replay` command already\nincludes an \"EXAMPLES\" section, and other sections of Elijah's design\nnotes might not be interesting for now. Also this cover letter is\nalready pretty long.  But reviewers can refer to the links above if\nthey think it can help.\n\n# Quick Overview (from Elijah's design notes)\n\n`git replay`, at a basic level, can perhaps be thought of as a\n\"default-to-dry-run rebase\" -- meaning no updates to the working tree,\nor to the index, or to any references.  However, it differs from\nrebase in that it:\n\n  * Works for branches that aren't checked out\n\n  * Works in a bare repository\n\n  * Can replay multiple branches simultaneously (with or without common\n    history in the range being replayed)\n\n  * Preserves relative topology by default (merges are replayed too in\n    Elijah's original work, not in this series)\n\n  * Focuses on performance\n\n  * Has several altered defaults as a result of the above\n\nI sometimes think of `git replay` as \"fast-replay\", a patch-based\nanalogue to the snapshot-based fast-export & fast-import tools.\n\n# Reasons for diverging from cherry-pick & rebase (from Elijah's\n  design notes)\n\nThere are multiple reasons to diverge from the defaults in cherry-pick and\nrebase.\n\n* Server side needs\n\n  * Both cherry-pick and rebase, via the sequencer, are heavily tied\n    to updating the working tree, index, some refs, and a lot of\n    control files with every commit replayed, and invoke a mess of\n    hooks[1] that might be hard to avoid for backward compatibility\n    reasons (at least, that's been brought up a few times on the\n    list).\n\n  * cherry-pick and rebase both fork various subprocesses\n    unnecessarily, but somewhat intrinsically in part to ensure the\n    same hooks are called that old scripted implementations would have\n    called.\n\n    Note: since 356ee4659bb (sequencer: try to commit without forking\n    'git commit', 2017-11-24) cherry-pick and rebase do not fork\n    subprocesses other than hooks for the cases covered by this patch\n    series (i.e. they do not fork \"git commit\" for simple picks).\n\n  * \"Dry run\" behavior, where there are no updates to worktree, index,\n    or even refs might be important.\n\n  * Should not assume users only want to operate on HEAD (see next\n    section)\n\n* Decapitate HEAD-centric assumptions\n\n  * cherry-pick forces commits to be played on top of HEAD;\n    inflexible.\n\n  * rebase assumes the range of commits to be replayed is\n    upstream..HEAD by default, though it allows one to replay\n    upstream..otherbranch -- but it still forcibly and needlessly\n    checks out 'otherbranch' before starting to replay things.\n\n    Note: since 767a9c417eb (rebase -i: stop checking out the tip of\n    the branch to rebase, 2020-01-24) it's not true that rebase\n    forcibly and needlessly checks out 'otherbranch'.\n\n  * Assuming HEAD is involved severely limits replaying multiple\n    (possibly divergent) branches.\n\n    Note: since 89fc0b53fdb (rebase: update refs from 'update-ref'\n    commands, 2022-07-19) the sequencer can update multiple\n    branches. The issue with divergent branch is with command line\n    arguments and the todo list generation rather than the\n    capabilities of the sequencer.\n\n  * Once you stop assuming HEAD has a certain meaning, there's not\n    much reason to have two separate commands anymore (except for the\n    funny extra not-necessarily-compatible options both have gained\n    over time).\n\n  * (Micro issue: Assuming HEAD is involved also makes it harder for\n    new users to learn what rebase means and does; it makes command\n    lines hard to parse.  Not sure I want to harp on this too much, as\n    I have a suspicion I might be creating a tool for experts with\n    complicated use cases, but it's a minor quibble.)\n\n* Performance\n\n  * jj is slaughtering us on rebase speed[2].  I would like us to become\n    competitive.  (I dropped a few comments in the link at [2] about why\n    git is currently so bad.)\n\n  * From [3], there was a simple 4-patch series in linux.git that took\n    53 seconds to rebase.  Switching to ort dropped it to 16 seconds.\n    While that sounds great, only 11 *milliseconds* were needed to do\n    the actual merges.  That means almost *all* the time (>99%) was\n    overhead!  Big offenders:\n\n    * --reapply-cherry-picks should be the default\n\n    * can_fast_forward() should be ripped out, and perhaps other extraneous\n      revision walks\n\n      Note: d42c9ffa0f (rebase: factor out branch_base calculation,\n      2022-10-17) might already deal with that (according to Felipe\n      Contreras).\n\n    * avoid updating working tree, index, refs, reflogs, and control\n      structures except when needed (e.g. hitting a conflict, or operation\n      finished)\n\n  * Other performance ideas (mostly for future work, not in this\n    series)\n\n    * single-file control structures instead of directory of files\n      (when doing interactive things which is in Elijah's original\n      work, but not in this series)\n\n    * avoid forking subprocesses unless explicitly requested (e.g.\n      --exec, --strategy, --run-hooks).  For example, definitely do not\n      invoke `git commit` or `git merge`.\n\n    * Sanitize hooks:\n\n      * dispense with all per-commit hooks for sure (pre-commit,\n        post-commit, post-checkout).\n\n      * pre-rebase also seems to assume exactly 1 ref is written, and\n        invoking it repeatedly would be stupid.  Plus, it's specific\n        to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n        rebase probably broke the pre-rebase assumptions already...)\n\n      * post-rewrite hook might make sense, but fast-import got\n        exempted, and I think of replay like a patch-based analogue\n        to the snapshot-based fast-import.\n\n    * When not running server side, resolve conflicts in a sparse-cone\n      sparse-index worktree to reduce number of files written to a\n      working tree.  (See below as well.)\n\n    * [High risk of possible premature optimization] Avoid large\n      numbers of newly created loose objects, when replaying large\n      numbers of commits.  Two possibilities: (1) Consider using\n      tmp-objdir and pack objects from the tmp-objdir at end of\n      exercise, (2) Lift code from git-fast-import to immediately\n      stuff new objects into a pack?\n\n* Multiple branches and non-checked out branches\n\n  * The ability to operate on non-checked out branches also implies\n    that we should generally be able to replay when in a dirty working\n    tree (exception being when we expect to update HEAD and any of the\n    dirty files is one that needs to be updated by the replay).\n\n  * Also, if we are operating locally on a non-checked out branch and\n    hit a conflict, we should have a way to resolve the conflict\n    without messing with the user's work on their current\n    branch. (This is not is this patch series though.)\n\n    * Idea: new worktree with sparse cone + sparse index checkout,\n      containing only files in the root directory, and whatever is\n      necessary to get the conflicts\n\n    * Companion to above idea: control structures should be written to\n      $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n      replay sessions, and so we know which worktrees are associated\n      with which replay operations.\n\n  - [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/\n  - [2] https://github.com/martinvonz/jj/discussions/49\n  - [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/\n\n# Important limitations\n\n* The code exits with code 1 if there are any conflict. No\n  resumability. No nice output. No interactivity. No special exit code\n  depending on the reason.\n\n* When a commit becomes empty as it is replayed, it is still replayed\n  as an empty commit, instead of being dropped.\n\n* No replaying merges, nor root commits. Only regular commits.\n\n* Signed commits are not properly handled. It's not clear what to do\n  to such commits when replaying on the server side.\n\n* Notes associated with replayed commits are not updated nor\n  duplicated. (Thanks to Phillip Wood for noticing.)\n\n# Commit overview\n\n* 1/14 t6429: remove switching aspects of fast-rebase\n\n    Preparatory commit to make it easier to later replace the\n    fast-rebase test-tool by `git replay` without breaking existing\n    tests.\n\n* 2/14 replay: introduce new builtin\n\n    This creates a minimal `git replay` command by moving the code\n    from the `fast-rebase` test helper from `t/helper/` into\n    `builtin/` and doing some renames and a few other needed changes.\n    Since v6, there are only a few doc improvements as suggested by\n    Dscho.\n\n* - 3/14 replay: start using parse_options API\n  - 4/14 replay: die() instead of failing assert()\n  - 5/14 replay: introduce pick_regular_commit()\n  - 6/14 replay: change rev walking options\n  - 7/14 replay: add an important FIXME comment about gpg signing\n  - 8/14 replay: remove progress and info output\n  - 9/14 replay: remove HEAD related sanity check\n\n    These slowly change the command to make it behave more like\n    regular commands and to start cleaning up its output. In patch\n    6/14 (replay: change rev walking options) there are some changes\n    compared to v6 as suggested by Elijah and Dscho. We are now\n    setting the rev walking bits we want before the call to\n    setup_revisions(). And then after that call we check if these bits\n    have been changed, and if that's the case we warn that we are\n    going to override those changes and we override the bits.\n\n* 10/14 replay: make it a minimal server side command\n\n    After the cleaning up in previous commits, it's now time to\n    radically change the way it works by stopping it to do ref\n    updates, to update the index and worktree, to consider HEAD as\n    special. Instead just make it output commands that should be\n    passed to `git update-ref --stdin`.\n\n* 11/14 replay: use standard revision ranges\n\n    Start adding new interesting features and also documentation and\n    tests, as the interface of the command is cristalizing into its\n    final form. Since v6 \"Takes a range of commits\" has been replaced\n    with \"Takes ranges of commits\" to reflect the fact that it can\n    accept more than one <revision-range>.\n\n* - 12/14 replay: add --advance or 'cherry-pick' mode\n  - 13/14 replay: add --contained to rebase contained branches\n\n    Add new options and features to the command.\n\n* 14/14 replay: stop assuming replayed branches do not diverge\n\n    This adds another interesting feature, as well as related\n    documentation and tests.\n\n# Notes about `fast-rebase`, tests and documentation\n\nThe `fast-rebase` test-tool helper was developed by Elijah to\nexperiment with a rebasing tool that would be developed from scratch\nbased on his merge-ort work, could be used to test that merge-ort\nwork, and would not have the speed and interface limitations of `git\nrebase` or `git cherry-pick`.\n\nThis `fast-rebase` helper was used before this series in:\n\nt6429-merge-sequence-rename-caching.sh\n\nSo when `git replay` is created from `fast-rebase` in patch 2/14, the\nt6429 test script is also converted to use `git replay`. This ensures\nthat `git replay` doesn't break too badly during the first 10 patches\nin this patch series.\n\nTests and documentation are introduced specifically for `git replay`\nas soon as patch 2/14, but they are not much improved since around\n11/14 as it doesn't make much sense to document and test behavior that\nwe know is going to change soon.\n\n# Possibly controversial issues \n\n* bare or not bare: this series works towards a plumbing command with\n  the end goal of it being usable and used first on bare repos,\n  contrary to existing commands like `git rebase` and `git\n  cherry-pick`. The tests check that the command works on both bare\n  and non-bare repo though.\n\n* exit status: a successful, non-conflicted replay exits with code\n  0. When the replay has conflicts, the exit status is 1. If the\n  replay is not able to complete (or start) due to some kind of error,\n  the exit status is something other than 0 or 1. There are a few\n  tests checking that. It has been suggested in an internal review\n  that conflicts might want to get a more specific error code as an\n  error code of 1 might be quite easy to return by accident. It\n  doesn't seem to me from their docs (which might want to be improved,\n  I didn't look at the code) that other commands like `git merge` and\n  `git rebase` exit with a special error code in case of conflict.\n\n* make worktree and index changes optional: commit 10/14 stops\n  updating the index and worktree, but it might be better especially\n  for cli users to make that optional. The issue is that this would\n  make the command more complex while we are developing a number of\n  important features so that the command can be used on bare repos. It\n  seems that this should rather be done in an iterative improvement\n  after the important features have landed.\n\n* --advance and --contained: these two advanced options might not\n  belong to this first series and could perhaps be added in a followup\n  series in separate commits. On the other hand the code for\n  --contained seems involved with the code of --advance and it's nice\n  to see soon that git replay can indeed do cherry-picking and rebase\n  many refs at once, and this way fullfil these parts of its promise.\n\n* replaying diverging branches: 14/14 the last patch in the series,\n  which allow replaying diverging branches, can be seen as a\n  fundamental fix or alternatively as adding an interesting\n  feature. So it's debatable if it should be in its own patch along\n  with its own tests as in this series, or if it should be merged into\n  a previous patch and which one.\n\n* only 2 patches: this patch series can be seen from a high level\n  point of view as 1) introducing the new `git replay` command, and 2)\n  using `git replay` to replace, and get rid of, the fast-rebase\n  test-tool command. The fact that not much of the original\n  fast-rebase code and interface is left would agree with that point\n  of view. On the other hand, fast-rebase can also be seen as a first\n  iteration towards `git replay`. So it can also make sense to see how\n  `git replay` evolved from it.\n\n# Changes between v6 and v7\n\nThanks to Dscho, Linus Arver and Dragan Simic for their suggestions on\nthe previous version! The few changes compared to v6 are:\n\n* The patch series was rebased onto master at dadef801b3 (Git\n  2.43-rc1, 2023-11-08). This is to make it stand on a possibly more\n  stable base.\n\n* In patch 2/14 (replay: introduce new builtin), there are a few doc\n  improvements as suggested by Dscho, as we now talk about the\n  <oldbase> and <branch> arguments in the description.\n\n* In patch 6/14 (replay: change rev walking options), as suggested by\n  Elijah and Dscho, we are now setting the rev walking bits we want\n  before the call to setup_revisions(). And then after that call we\n  check if these bits have been changed, and if that's the case we\n  warn that we are going to override those changes and we override the\n  bits.\n\n* In patch 11/14 (replay: use standard revision ranges), \"Takes a\n  range of commits\" has been replaced with \"Takes ranges of commits\"\n  to reflect the fact that it can accept more than one\n  <revision-range>.\n\nCI tests seem to pass according to:\n\nhttps://github.com/chriscool/git/actions/runs/6878406898\n\n(Except the \"sparse\" test, but failure doesn't seem to be related. And\nsorry I stopped waiting for the MacOS and ASAN tests to finish after\n23 minutes.)\n\n# Range-diff between v6 and v7\n\n(Sorry it looks like patch 6/14 in v7 is considered to be completely\ndifferent from what it was in v6, so the range-diff is not showing\ndifferences between them.)\n\n 1:  fac0a9dff4 =  1:  cddcd967b2 t6429: remove switching aspects of fast-rebase\n 2:  bec2eb8928 !  2:  c8476fb093 replay: introduce new builtin\n    @@ Documentation/git-replay.txt (new)\n     +DESCRIPTION\n     +-----------\n     +\n    -+Takes a range of commits and replays them onto a new location.\n    ++Takes a range of commits, specified by <oldbase> and <branch>, and\n    ++replays them onto a new location (see `--onto` option below).\n     +\n     +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n     +\n 3:  b0cdfdc0c3 =  3:  43322abd1e replay: start using parse_options API\n 4:  c3403f0b9d =  4:  6524c7f045 replay: die() instead of failing assert()\n 5:  4188eeac30 =  5:  05d0efa3cb replay: introduce pick_regular_commit()\n 6:  b7b4d9001e <  -:  ---------- replay: change rev walking options\n -:  ---------- >  6:  c7a5aad3d6 replay: change rev walking options\n 7:  c57577a9b8 =  7:  01f35f924b replay: add an important FIXME comment about gpg signing\n 8:  e78be50f3d =  8:  1498b24bad replay: remove progress and info output\n 9:  e4c79b676f =  9:  6786fc147b replay: remove HEAD related sanity check\n10:  8d89f1b733 ! 10:  9a24dbb530 replay: make it a minimal server side command\n    @@ Commit message\n         Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n     \n      ## Documentation/git-replay.txt ##\n    -@@ Documentation/git-replay.txt: SYNOPSIS\n    - DESCRIPTION\n    +@@ Documentation/git-replay.txt: DESCRIPTION\n      -----------\n      \n    --Takes a range of commits and replays them onto a new location.\n    -+Takes a range of commits and replays them onto a new location. Leaves\n    -+the working tree and the index untouched, and updates no\n    -+references. The output of this command is meant to be used as input to\n    + Takes a range of commits, specified by <oldbase> and <branch>, and\n    +-replays them onto a new location (see `--onto` option below).\n    ++replays them onto a new location (see `--onto` option below). Leaves\n    ++the working tree and the index untouched, and updates no references.\n    ++The output of this command is meant to be used as input to\n     +`git update-ref --stdin`, which would update the relevant branches.\n      \n      THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        struct merge_result result;\n     -  struct strbuf reflog_msg = STRBUF_INIT;\n        struct strbuf branch_name = STRBUF_INIT;\n    -   int i, ret = 0;\n    +   int ret = 0;\n      \n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n        onto = peel_committish(onto_name);\n11:  3d433a1322 ! 11:  ad6ca2fbef replay: use standard revision ranges\n    @@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new\n      \n      DESCRIPTION\n      -----------\n    -@@ Documentation/git-replay.txt: DESCRIPTION\n    - Takes a range of commits and replays them onto a new location. Leaves\n    - the working tree and the index untouched, and updates no\n    - references. The output of this command is meant to be used as input to\n    + \n    +-Takes a range of commits, specified by <oldbase> and <branch>, and\n    +-replays them onto a new location (see `--onto` option below). Leaves\n    ++Takes ranges of commits and replays them onto a new location. Leaves\n    + the working tree and the index untouched, and updates no references.\n    + The output of this command is meant to be used as input to\n     -`git update-ref --stdin`, which would update the relevant branches.\n     +`git update-ref --stdin`, which would update the relevant branches\n     +(see the OUTPUT section below).\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        struct merge_options merge_opt;\n        struct merge_result result;\n     -  struct strbuf branch_name = STRBUF_INIT;\n    -   int i, ret = 0;\n    +   int ret = 0;\n      \n        const char * const replay_usage[] = {\n     -          N_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n     -  strvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n     -\n        /*\n    -    * TODO: For now, let's warn when we see an option that we are\n    -    * going to override after setup_revisions() below. In the\n    +    * Set desired values for rev walking options here. If they\n    +    * are changed by some user specified option in setup_revisions()\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    -                   warning(_(\"option '%s' will be overridden\"), argv[i]);\n    -   }\n    +   revs.topo_order = 1;\n    +   revs.simplify_history = 0;\n      \n     -  if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n     -          ret = error(_(\"unhandled options\"));\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        }\n      \n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    -   revs.topo_order = 1;\n    -   revs.simplify_history = 0;\n    +           revs.simplify_history = 0;\n    +   }\n      \n     -  strvec_clear(&rev_walk_args);\n     -\n12:  cca8105382 ! 12:  081864ed5f replay: add --advance or 'cherry-pick' mode\n    @@ builtin/replay.c: static struct commit *pick_regular_commit(struct commit *pickm\n        struct merge_options merge_opt;\n        struct merge_result result;\n     +  struct strset *update_refs = NULL;\n    -   int i, ret = 0;\n    +   int ret = 0;\n      \n        const char * const replay_usage[] = {\n     -          N_(\"git replay --onto <newbase> <revision-range>... # EXPERIMENTAL\"),\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n      \n        /*\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    -   revs.topo_order = 1;\n    -   revs.simplify_history = 0;\n    +           revs.simplify_history = 0;\n    +   }\n      \n     +  determine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n     +                        &onto, &update_refs);\n13:  92287a2cc8 ! 13:  19c4016c7c replay: add --contained to rebase contained branches\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        struct rev_info revs;\n        struct commit *last_commit = NULL;\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    -   int i, ret = 0;\n    +   int ret = 0;\n      \n        const char * const replay_usage[] = {\n     -          N_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\"),\n14:  529a7fda40 ! 14:  29556bcc86 replay: stop assuming replayed branches do not diverge\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        struct merge_result result;\n        struct strset *update_refs = NULL;\n     +  kh_oid_map_t *replayed_commits;\n    -   int i, ret = 0;\n    +   int ret = 0;\n      \n        const char * const replay_usage[] = {\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n\n\nElijah Newren (14):\n  t6429: remove switching aspects of fast-rebase\n  replay: introduce new builtin\n  replay: start using parse_options API\n  replay: die() instead of failing assert()\n  replay: introduce pick_regular_commit()\n  replay: change rev walking options\n  replay: add an important FIXME comment about gpg signing\n  replay: remove progress and info output\n  replay: remove HEAD related sanity check\n  replay: make it a minimal server side command\n  replay: use standard revision ranges\n  replay: add --advance or 'cherry-pick' mode\n  replay: add --contained to rebase contained branches\n  replay: stop assuming replayed branches do not diverge\n\n .gitignore                               |   1 +\n Documentation/git-replay.txt             | 127 +++++++\n Makefile                                 |   2 +-\n builtin.h                                |   1 +\n builtin/replay.c                         | 445 +++++++++++++++++++++++\n command-list.txt                         |   1 +\n git.c                                    |   1 +\n t/helper/test-fast-rebase.c              | 241 ------------\n t/helper/test-tool.c                     |   1 -\n t/helper/test-tool.h                     |   1 -\n t/t3650-replay-basics.sh                 | 198 ++++++++++\n t/t6429-merge-sequence-rename-caching.sh |  45 ++-\n 12 files changed, 800 insertions(+), 264 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100644 builtin/replay.c\n delete mode 100644 t/helper/test-fast-rebase.c\n create mode 100755 t/t3650-replay-basics.sh\n\n\nbase-commit: dadef801b365989099a9929e995589e455c51fed\nprerequisite-patch-id: 6df236178013b77ca82d22653c1ab78477e081ce\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484921","messageId":"20231115143327.2441397-3-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 02/14] replay: introduce new builtin","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:15Z","receivedAt":"2023-11-15T14:33:52Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor now, this is just a rename from `t/helper/test-fast-rebase.c` into\n`builtin/replay.c` with minimal changes to make it build appropriately.\n\nLet's add a stub documentation and a stub test script though.\n\nSubsequent commits will flesh out the capabilities of the new command\nand make it a more standard regular builtin.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n .gitignore                                    |  1 +\n Documentation/git-replay.txt                  | 39 ++++++++++++\n Makefile                                      |  2 +-\n builtin.h                                     |  1 +\n .../test-fast-rebase.c => builtin/replay.c    | 29 +++------\n command-list.txt                              |  1 +\n git.c                                         |  1 +\n t/helper/test-tool.c                          |  1 -\n t/helper/test-tool.h                          |  1 -\n t/t3650-replay-basics.sh                      | 60 +++++++++++++++++++\n t/t6429-merge-sequence-rename-caching.sh      | 27 +++------\n 11 files changed, 122 insertions(+), 41 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n rename t/helper/test-fast-rebase.c => builtin/replay.c (87%)\n create mode 100755 t/t3650-replay-basics.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 5e56e471b3..612c0f6a0f 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -135,6 +135,7 @@\n /git-remote-ext\n /git-repack\n /git-replace\n+/git-replay\n /git-request-pull\n /git-rerere\n /git-reset\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nnew file mode 100644\nindex 0000000000..0349058b66\n--- /dev/null\n+++ b/Documentation/git-replay.txt\n@@ -0,0 +1,39 @@\n+git-replay(1)\n+=============\n+\n+NAME\n+----\n+git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos too\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n+\n+DESCRIPTION\n+-----------\n+\n+Takes a range of commits, specified by <oldbase> and <branch>, and\n+replays them onto a new location (see `--onto` option below).\n+\n+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n+\n+OPTIONS\n+-------\n+\n+--onto <newbase>::\n+\tStarting point at which to create the new commits.  May be any\n+\tvalid commit, and not just an existing branch name.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted replay, the exit status is 0.  When\n+the replay has conflicts, the exit status is 1.  If the replay is not\n+able to complete (or start) due to some kind of error, the exit status\n+is something other than 0 or 1.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 03adcb5a48..3834bc1544 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -799,7 +799,6 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o\n TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n TEST_BUILTINS_OBJS += test-env-helper.o\n TEST_BUILTINS_OBJS += test-example-decorate.o\n-TEST_BUILTINS_OBJS += test-fast-rebase.o\n TEST_BUILTINS_OBJS += test-find-pack.o\n TEST_BUILTINS_OBJS += test-fsmonitor-client.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n@@ -1290,6 +1289,7 @@ BUILTIN_OBJS += builtin/remote-fd.o\n BUILTIN_OBJS += builtin/remote.o\n BUILTIN_OBJS += builtin/repack.o\n BUILTIN_OBJS += builtin/replace.o\n+BUILTIN_OBJS += builtin/replay.o\n BUILTIN_OBJS += builtin/rerere.o\n BUILTIN_OBJS += builtin/reset.o\n BUILTIN_OBJS += builtin/rev-list.o\ndiff --git a/builtin.h b/builtin.h\nindex d560baa661..28280636da 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -211,6 +211,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix);\n int cmd_remote_ext(int argc, const char **argv, const char *prefix);\n int cmd_remote_fd(int argc, const char **argv, const char *prefix);\n int cmd_repack(int argc, const char **argv, const char *prefix);\n+int cmd_replay(int argc, const char **argv, const char *prefix);\n int cmd_rerere(int argc, const char **argv, const char *prefix);\n int cmd_reset(int argc, const char **argv, const char *prefix);\n int cmd_restore(int argc, const char **argv, const char *prefix);\ndiff --git a/t/helper/test-fast-rebase.c b/builtin/replay.c\nsimilarity index 87%\nrename from t/helper/test-fast-rebase.c\nrename to builtin/replay.c\nindex 2bfab66b1b..f2d8444417 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/builtin/replay.c\n@@ -1,17 +1,11 @@\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+ * \"git replay\" builtin command\n  */\n \n #define USE_THE_INDEX_VARIABLE\n-#include \"test-tool.h\"\n+#include \"git-compat-util.h\"\n+\n+#include \"builtin.h\"\n #include \"cache-tree.h\"\n #include \"commit.h\"\n #include \"environment.h\"\n@@ -27,7 +21,8 @@\n #include \"sequencer.h\"\n #include \"setup.h\"\n #include \"strvec.h\"\n-#include \"tree.h\"\n+#include <oidset.h>\n+#include <tree.h>\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -94,7 +89,7 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n-int cmd__fast_rebase(int argc, const char **argv)\n+int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n@@ -110,14 +105,8 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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\tprintf(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\\n\");\n \t\texit(129);\n \t}\n \n@@ -136,7 +125,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n \n-\trepo_init_revisions(the_repository, &revs, NULL);\n+\trepo_init_revisions(the_repository, &revs, prefix);\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\ndiff --git a/command-list.txt b/command-list.txt\nindex 54b2a50f5f..c4cd0f352b 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -160,6 +160,7 @@ git-reflog                              ancillarymanipulators           complete\n git-remote                              ancillarymanipulators           complete\n git-repack                              ancillarymanipulators           complete\n git-replace                             ancillarymanipulators           complete\n+git-replay                              plumbingmanipulators\n git-request-pull                        foreignscminterface             complete\n git-rerere                              ancillaryinterrogators\n git-reset                               mainporcelain           history\ndiff --git a/git.c b/git.c\nindex c67e44dd82..7068a184b0 100644\n--- a/git.c\n+++ b/git.c\n@@ -594,6 +594,7 @@ static struct cmd_struct commands[] = {\n \t{ \"remote-fd\", cmd_remote_fd, NO_PARSEOPT },\n \t{ \"repack\", cmd_repack, RUN_SETUP },\n \t{ \"replace\", cmd_replace, RUN_SETUP },\n+\t{ \"replay\", cmd_replay, RUN_SETUP },\n \t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t{ \"reset\", cmd_reset, RUN_SETUP },\n \t{ \"restore\", cmd_restore, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 876cd2dc31..37ba996539 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -30,7 +30,6 @@ static struct test_cmd cmds[] = {\n \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n \t{ \"env-helper\", cmd__env_helper },\n \t{ \"example-decorate\", cmd__example_decorate },\n-\t{ \"fast-rebase\", cmd__fast_rebase },\n \t{ \"find-pack\", cmd__find_pack },\n \t{ \"fsmonitor-client\", cmd__fsmonitor_client },\n \t{ \"genrandom\", cmd__genrandom },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 70dd4eba11..8a1a7c63da 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -24,7 +24,6 @@ int cmd__dump_untracked_cache(int argc, const char **argv);\n int cmd__dump_reftable(int argc, const char **argv);\n int cmd__env_helper(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__find_pack(int argc, const char **argv);\n int cmd__fsmonitor_client(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nnew file mode 100755\nindex 0000000000..36c1b5082a\n--- /dev/null\n+++ b/t/t3650-replay-basics.sh\n@@ -0,0 +1,60 @@\n+#!/bin/sh\n+\n+test_description='basic git replay tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+GIT_AUTHOR_NAME=author@name\n+GIT_AUTHOR_EMAIL=bogus@email@address\n+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\n+\tgit switch -c topic1 &&\n+\ttest_commit C &&\n+\tgit switch -c topic2 &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit switch topic1 &&\n+\ttest_commit F &&\n+\tgit switch -c topic3 &&\n+\ttest_commit G &&\n+\ttest_commit H &&\n+\tgit switch -c topic4 main &&\n+\ttest_commit I &&\n+\ttest_commit J &&\n+\n+\tgit switch -c next main &&\n+\ttest_commit K &&\n+\tgit merge -m \"Merge topic1\" topic1 &&\n+\tgit merge -m \"Merge topic2\" topic2 &&\n+\tgit merge -m \"Merge topic3\" topic3 &&\n+\t>evil &&\n+\tgit add evil &&\n+\tgit commit --amend &&\n+\tgit merge -m \"Merge topic4\" topic4 &&\n+\n+\tgit switch main &&\n+\ttest_commit L &&\n+\ttest_commit M &&\n+\n+\tgit switch -c conflict B &&\n+\ttest_commit C.conflict C.t conflict\n+'\n+\n+test_expect_success 'using replay to rebase two branches, one on top of other' '\n+\tgit switch main &&\n+\n+\tgit replay --onto main topic1 topic2 >result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 75d3fd2dba..7670b72008 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,9 +71,8 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -141,8 +140,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -200,9 +198,8 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -278,9 +275,8 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -356,8 +352,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,9 +451,8 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -523,9 +517,8 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -626,9 +619,8 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -685,9 +677,8 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484922","messageId":"20231115143327.2441397-4-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 03/14] replay: start using parse_options API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:16Z","receivedAt":"2023-11-15T14:33:52Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of manually parsing arguments, let's start using the parse_options\nAPI. This way this new builtin will look more standard, and in some\nupcoming commits will more easily be able to handle more command line\noptions.\n\nNote that we plan to later use standard revision ranges instead of\nhardcoded \"<oldbase> <branch>\" arguments. When we will use standard\nrevision ranges, it will be easier to check if there are no spurious\narguments if we keep ARGV[0], so let's call parse_options() with\nPARSE_OPT_KEEP_ARGV0 even if we don't need ARGV[0] right now to avoid\nsome useless code churn.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 45 ++++++++++++++++++++++++++++++++-------------\n 1 file changed, 32 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex f2d8444417..afabb844d3 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -15,7 +15,7 @@\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n #include \"object-name.h\"\n-#include \"read-cache-ll.h\"\n+#include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n #include \"sequencer.h\"\n@@ -92,6 +92,7 @@ static struct commit *create_commit(struct tree *tree,\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n+\tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n \tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n@@ -105,16 +106,32 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n-\tif (argc == 2 && !strcmp(argv[1], \"-h\")) {\n-\t\tprintf(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\\n\");\n-\t\texit(129);\n+\tconst char * const replay_usage[] = {\n+\t\tN_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n+\t\tNULL\n+\t};\n+\tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"onto\", &onto_name,\n+\t\t\t   N_(\"revision\"),\n+\t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n+\n+\tif (!onto_name) {\n+\t\terror(_(\"option --onto is mandatory\"));\n+\t\tusage_with_options(replay_usage, replay_options);\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+\tif (argc != 3) {\n+\t\terror(_(\"bad number of arguments\"));\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n \n-\tonto = peel_committish(argv[2]);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\tonto = peel_committish(onto_name);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \t/* Sanity check */\n \tif (repo_get_oid(the_repository, \"HEAD\", &head))\n@@ -126,6 +143,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tBUG(\"Could not read index\");\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n+\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\n@@ -134,7 +152,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n@@ -197,8 +216,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &last_picked_commit->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -210,8 +229,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &head,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t}\n \tret = (result.clean == 0);\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484923","messageId":"20231115143327.2441397-5-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 04/14] replay: die() instead of failing assert()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:17Z","receivedAt":"2023-11-15T14:33:54Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIt's not a good idea for regular Git commands to use an assert() to\ncheck for things that could happen but are not supported.\n\nLet's die() with an explanation of the issue instead.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex afabb844d3..32dbaaf028 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -179,7 +179,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\n+\t\tif (!commit->parents)\n+\t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n+\t\tif (commit->parents->next)\n+\t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n+\n \t\tbase = commit->parents->item;\n \n \t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484924","messageId":"20231115143327.2441397-6-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 05/14] replay: introduce pick_regular_commit()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:18Z","receivedAt":"2023-11-15T14:33:56Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's refactor the code to handle a regular commit (a commit that is\nneither a root commit nor a merge commit) into a single function instead\nof keeping it inside cmd_replay().\n\nThis is good for separation of concerns, and this will help further work\nin the future to replay merge commits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 54 ++++++++++++++++++++++++++++++------------------\n 1 file changed, 34 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 32dbaaf028..5c4cbd11db 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -89,6 +89,35 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+static struct commit *pick_regular_commit(struct commit *pickme,\n+\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  struct merge_options *merge_opt,\n+\t\t\t\t\t  struct merge_result *result)\n+{\n+\tstruct commit *base;\n+\tstruct tree *pickme_tree, *base_tree;\n+\n+\tbase = pickme->parents->item;\n+\n+\tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n+\tbase_tree = repo_get_commit_tree(the_repository, base);\n+\n+\tmerge_opt->branch2 = short_commit_name(pickme);\n+\tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n+\n+\tmerge_incore_nonrecursive(merge_opt,\n+\t\t\t\t  base_tree,\n+\t\t\t\t  result->tree,\n+\t\t\t\t  pickme_tree,\n+\t\t\t\t  result);\n+\n+\tfree((char*)merge_opt->ancestor);\n+\tmerge_opt->ancestor = NULL;\n+\tif (!result->clean)\n+\t\treturn NULL;\n+\treturn create_commit(result->tree, pickme, last_commit);\n+}\n+\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n@@ -100,7 +129,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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 tree *head_tree;\n \tstruct merge_result result;\n \tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n@@ -175,7 +204,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tresult.tree = head_tree;\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *base;\n+\t\tstruct commit *pick;\n \n \t\tfprintf(stderr, \"Rebasing %s...\\r\",\n \t\t\toid_to_hex(&commit->object.oid));\n@@ -185,26 +214,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tbase = commit->parents->item;\n-\n-\t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-\t\tbase_tree = repo_get_commit_tree(the_repository, 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\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!pick)\n \t\t\tbreak;\n+\t\tlast_commit = pick;\n \t\tlast_picked_commit = commit;\n-\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484925","messageId":"20231115143327.2441397-7-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 06/14] replay: change rev walking options","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:19Z","receivedAt":"2023-11-15T14:33:57Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's force the rev walking options we need after calling\nsetup_revisions() instead of before.\n\nThis might override some user supplied rev walking command line options\nthough. So let's detect that and warn users by:\n\n  a) setting the desired values, before setup_revisions(),\n  b) checking after setup_revisions() whether these values differ from\n     the desired values,\n  c) if so throwing a warning and setting the desired values again.\n\nWe want the command to work from older commits to newer ones by default.\nAlso we don't want history simplification, as we want to deal with all\nthe commits in the affected range.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 48 +++++++++++++++++++++++++++++++++++++++++-------\n 1 file changed, 41 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 5c4cbd11db..8302d35eca 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -173,22 +173,56 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n-\trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n-\trevs.cherry_mark = 1;\n-\trevs.limited = 1;\n+\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n+\n+\t/*\n+\t * Set desired values for rev walking options here. If they\n+\t * are changed by some user specified option in setup_revisions()\n+\t * below, we will detect that below and then warn.\n+\t *\n+\t * TODO: In the future we might want to either die(), or allow\n+\t * some options changing these values if we think they could\n+\t * be useful.\n+\t */\n \trevs.reverse = 1;\n-\trevs.right_only = 1;\n \trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n \trevs.topo_order = 1;\n-\n-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n+\trevs.simplify_history = 0;\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n \t\tgoto cleanup;\n \t}\n \n+\t/*\n+\t * Detect and warn if we override some user specified rev\n+\t * walking options.\n+\t */\n+\tif (revs.reverse != 1) {\n+\t\twarning(_(\"some rev walking options will be overridden as \"\n+\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n+\t\t\t\"reverse\");\n+\t\trevs.reverse = 1;\n+\t}\n+\tif (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {\n+\t\twarning(_(\"some rev walking options will be overridden as \"\n+\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n+\t\t\t\"sort_order\");\n+\t\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\t}\n+\tif (revs.topo_order != 1) {\n+\t\twarning(_(\"some rev walking options will be overridden as \"\n+\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n+\t\t\t\"topo_order\");\n+\t\trevs.topo_order = 1;\n+\t}\n+\tif (revs.simplify_history != 0) {\n+\t\twarning(_(\"some rev walking options will be overridden as \"\n+\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n+\t\t\t\"simplify_history\");\n+\t\trevs.simplify_history = 0;\n+\t}\n+\n \tstrvec_clear(&rev_walk_args);\n \n \tif (prepare_revision_walk(&revs) < 0) {\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484926","messageId":"20231115143327.2441397-8-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 07/14] replay: add an important FIXME comment about gpg signing","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:20Z","receivedAt":"2023-11-15T14:33:59Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want to be able to handle signed commits in some way in the future,\nbut we are not ready to do it now. So for the time being let's just add\na FIXME comment to remind us about it.\n\nThese are different ways we could handle them:\n\n  - in case of a cli user and if there was an interactive mode, we could\n    perhaps ask if the user wants to sign again\n  - we could add an option to just fail if there are signed commits\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 8302d35eca..2e1df83027 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -62,7 +62,7 @@ static struct commit *create_commit(struct tree *tree,\n \tstruct object *obj;\n \tstruct commit_list *parents = NULL;\n \tchar *author;\n-\tchar *sign_commit = NULL;\n+\tchar *sign_commit = NULL; /* FIXME: cli users might want to sign again */\n \tstruct commit_extra_header *extra;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *out_enc = get_commit_output_encoding();\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484927","messageId":"20231115143327.2441397-9-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 08/14] replay: remove progress and info output","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:21Z","receivedAt":"2023-11-15T14:34:01Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command will be changed in a follow up commit, so that it\nwill not update refs directly, but instead it will print on stdout a\nlist of commands that can be consumed by `git update-ref --stdin`.\n\nWe don't want this output to be polluted by its current low value\noutput, so let's just remove the latter.\n\nIn the future, when the command gets an option to update refs by\nitself, it will make a lot of sense to display a progress meter, but\nwe are not there yet.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 +------\n 1 file changed, 1 insertion(+), 6 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 2e1df83027..1035435705 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -232,7 +232,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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.show_rename_progress = 0;\n \tmerge_opt.branch1 = \"HEAD\";\n \thead_tree = repo_get_commit_tree(the_repository, onto);\n \tresult.tree = head_tree;\n@@ -240,9 +240,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \twhile ((commit = get_revision(&revs))) {\n \t\tstruct commit *pick;\n \n-\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n-\t\t\toid_to_hex(&commit->object.oid));\n-\n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n@@ -261,7 +258,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\texit(128);\n \n \tif (result.clean) {\n-\t\tfprintf(stderr, \"\\nDone.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n \t\t\t    oid_to_hex(&last_commit->object.oid));\n@@ -275,7 +271,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t} else {\n-\t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484928","messageId":"20231115143327.2441397-10-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 09/14] replay: remove HEAD related sanity check","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:22Z","receivedAt":"2023-11-15T14:34:02Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want replay to be a command that can be used on the server side on\nany branch, not just the current one, so we are going to stop updating\nHEAD in a future commit.\n\nA \"sanity check\" that makes sure we are replaying the current branch\ndoesn't make sense anymore. Let's remove it.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 8 +-------\n t/t3650-replay-basics.sh | 2 --\n 2 files changed, 1 insertion(+), 9 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 1035435705..30292d219d 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -123,7 +123,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct strvec rev_walk_args = STRVEC_INIT;\n \tstruct rev_info revs;\n@@ -162,11 +161,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\t/* Sanity check */\n-\tif (repo_get_oid(the_repository, \"HEAD\", &head))\n-\t\tdie(_(\"Cannot read HEAD\"));\n-\tassert(oideq(&onto->object.oid, &head));\n-\n \trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n@@ -275,7 +269,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n \t\t\t       &last_commit->object.oid,\n-\t\t\t       &head,\n+\t\t\t       &onto->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n \t\t\terror(_(\"could not update %s\"), argv[2]);\n \t\t\tdie(\"Failed to update %s\", argv[2]);\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 36c1b5082a..b5b9f9ade2 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -48,8 +48,6 @@ test_expect_success 'setup' '\n '\n \n test_expect_success 'using replay to rebase two branches, one on top of other' '\n-\tgit switch main &&\n-\n \tgit replay --onto main topic1 topic2 >result &&\n \n \tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484929","messageId":"20231115143327.2441397-11-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 10/14] replay: make it a minimal server side command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:23Z","receivedAt":"2023-11-15T14:34:04Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want this command to be a minimal command that just does server side\npicking of commits, displaying the results on stdout for higher level\nscripts to consume.\n\nSo let's simplify it:\n  * remove the worktree and index reading/writing,\n  * remove the ref (and reflog) updating,\n  * remove the assumptions tying us to HEAD, since (a) this is not a\n    rebase and (b) we want to be able to pick commits in a bare repo,\n    i.e. to/from branches that are not checked out and not the main\n    branch,\n  * remove unneeded includes,\n  * handle rebasing multiple branches by printing on stdout the update\n    ref commands that should be performed.\n\nThe output can be piped into `git update-ref --stdin` for the ref\nupdates to happen.\n\nIn the future to make it easier for users to use this command\ndirectly maybe an option can be added to automatically pipe its output\ninto `git update-ref`.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             |  5 +-\n builtin/replay.c                         | 78 ++++++++----------------\n t/t3650-replay-basics.sh                 | 19 +++++-\n t/t6429-merge-sequence-rename-caching.sh | 39 +++++++-----\n 4 files changed, 72 insertions(+), 69 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 0349058b66..87a85a7f57 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -15,7 +15,10 @@ DESCRIPTION\n -----------\n \n Takes a range of commits, specified by <oldbase> and <branch>, and\n-replays them onto a new location (see `--onto` option below).\n+replays them onto a new location (see `--onto` option below). Leaves\n+the working tree and the index untouched, and updates no references.\n+The output of this command is meant to be used as input to\n+`git update-ref --stdin`, which would update the relevant branches.\n \n THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n \ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 30292d219d..73a25e9e85 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -6,11 +6,7 @@\n #include \"git-compat-util.h\"\n \n #include \"builtin.h\"\n-#include \"cache-tree.h\"\n-#include \"commit.h\"\n #include \"environment.h\"\n-#include \"gettext.h\"\n-#include \"hash.h\"\n #include \"hex.h\"\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n@@ -18,8 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"sequencer.h\"\n-#include \"setup.h\"\n #include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n@@ -102,6 +96,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n+\tmerge_opt->branch1 = short_commit_name(last_commit);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -122,15 +117,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct lock_file lock = LOCK_INIT;\n+\tstruct commit *last_commit = NULL;\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 *head_tree;\n \tstruct merge_result result;\n-\tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n@@ -161,10 +153,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index(the_repository) < 0)\n-\t\tBUG(\"Could not read index\");\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n@@ -227,58 +215,44 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\tmerge_opt.branch1 = \"HEAD\";\n-\thead_tree = repo_get_commit_tree(the_repository, onto);\n-\tresult.tree = head_tree;\n+\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *pick;\n+\t\tconst struct name_decoration *decoration;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n-\t\tif (!pick)\n+\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!last_commit)\n \t\t\tbreak;\n-\t\tlast_commit = pick;\n-\t\tlast_picked_commit = commit;\n+\n+\t\tdecoration = get_name_decoration(&commit->object);\n+\t\tif (!decoration)\n+\t\t\tcontinue;\n+\n+\t\twhile (decoration) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\t\tprintf(\"update %s %s %s\\n\",\n+\t\t\t\t       decoration->name,\n+\t\t\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t\t\t       oid_to_hex(&commit->object.oid));\n+\t\t\t}\n+\t\t\tdecoration = decoration->next;\n+\t\t}\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n+\tret = result.clean;\n \n-\tif (result.clean < 0)\n-\t\texit(128);\n-\n-\tif (result.clean) {\n-\t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n-\t\t\t    oid_to_hex(&last_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, branch_name.buf,\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &last_picked_commit->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n-\t\t\tdie(_(\"unable to update HEAD\"));\n-\t} else {\n-\t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &onto->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t}\n-\tret = (result.clean == 0);\n cleanup:\n-\tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n-\treturn ret;\n+\n+\t/* Return */\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret ? 0 : 1;\n }\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex b5b9f9ade2..3567c98362 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -47,12 +47,29 @@ test_expect_success 'setup' '\n \ttest_commit C.conflict C.t conflict\n '\n \n+test_expect_success 'setup bare' '\n+\tgit clone --bare . bare\n+'\n+\n test_expect_success 'using replay to rebase two branches, one on top of other' '\n \tgit replay --onto main topic1 topic2 >result &&\n \n+\ttest_line_count = 1 result &&\n+\n \tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n \ttest_write_lines E D M L B A >expect &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n+\tgit -C bare replay --onto main topic1 topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n '\n \n test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 7670b72008..099aefeffc 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,8 +71,9 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -140,7 +141,9 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -198,8 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -275,8 +279,9 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -451,8 +456,9 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -517,8 +523,9 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -619,8 +626,9 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -677,8 +685,9 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484930","messageId":"20231115143327.2441397-12-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 11/14] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:24Z","receivedAt":"2023-11-15T14:34:06Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of the fixed \"<oldbase> <branch>\" arguments, the replay\ncommand now accepts \"<revision-range>...\" arguments in a similar\nway as many other Git commands. This makes its interface more\nstandard and more flexible.\n\nThis also enables many revision related options accepted and\neaten by setup_revisions(). If the replay command was a high level\none or had a high level mode, it would make sense to restrict some\nof the possible options, like those generating non-contiguous\nhistory, as they could be confusing for most users.\n\nAlso as the interface of the command is now mostly finalized,\nwe can add more documentation and more testcases to make sure\nthe command will continue to work as designed in the future.\n\nWe only document the rev-list related options among all the\nrevision related options that are now accepted, as the rev-list\nrelated ones are probably the most useful for now.\n\nHelped-by: Dragan Simic <dsimic@manjaro.org>\nHelped-by: Linus Arver <linusa@google.com>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             | 58 ++++++++++++++++++++++--\n builtin/replay.c                         | 21 ++-------\n t/t3650-replay-basics.sh                 | 12 ++++-\n t/t6429-merge-sequence-rename-caching.sh | 18 ++++----\n 4 files changed, 77 insertions(+), 32 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 87a85a7f57..fab4ea0178 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,16 +9,16 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n SYNOPSIS\n --------\n [verse]\n-'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n+'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n \n DESCRIPTION\n -----------\n \n-Takes a range of commits, specified by <oldbase> and <branch>, and\n-replays them onto a new location (see `--onto` option below). Leaves\n+Takes ranges of commits and replays them onto a new location. Leaves\n the working tree and the index untouched, and updates no references.\n The output of this command is meant to be used as input to\n-`git update-ref --stdin`, which would update the relevant branches.\n+`git update-ref --stdin`, which would update the relevant branches\n+(see the OUTPUT section below).\n \n THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n \n@@ -28,6 +28,30 @@ OPTIONS\n --onto <newbase>::\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n++\n+The update-ref command(s) in the output will update the branch(es) in\n+the revision range to point at the new commits, similar to the way how\n+`git rebase --update-refs` updates multiple branches in the affected\n+range.\n+\n+<revision-range>::\n+\tRange of commits to replay; see \"Specifying Ranges\" in\n+\tlinkgit:git-rev-parse and the \"Commit Limiting\" options below.\n+\n+include::rev-list-options.txt[]\n+\n+OUTPUT\n+------\n+\n+When there are no conflicts, the output of this command is usable as\n+input to `git update-ref --stdin`.  It is of the form:\n+\n+\tupdate refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+\tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+\tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+\n+where the number of refs updated depends on the arguments passed and\n+the shape of the history being replayed.\n \n EXIT STATUS\n -----------\n@@ -37,6 +61,32 @@ the replay has conflicts, the exit status is 1.  If the replay is not\n able to complete (or start) due to some kind of error, the exit status\n is something other than 0 or 1.\n \n+EXAMPLES\n+--------\n+\n+To simply rebase `mybranch` onto `target`:\n+\n+------------\n+$ git replay --onto target origin/main..mybranch\n+update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n+------------\n+\n+When calling `git replay`, one does not need to specify a range of\n+commits to replay using the syntax `A..B`; any range expression will\n+do:\n+\n+------------\n+$ git replay --onto origin/main ^base branch1 branch2 branch3\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+------------\n+\n+This will simultaneously rebase `branch1`, `branch2`, and `branch3`,\n+all commits they have since `base`, playing them on top of\n+`origin/main`. These three branches may have commits on top of `base`\n+that they have in common, but that does not need to be the case.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 73a25e9e85..7a660020d1 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,7 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -118,16 +117,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL;\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 merge_result result;\n-\tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n+\t\tN_(\"git replay --onto <newbase> <revision-range>... # EXPERIMENTAL\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -145,18 +142,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tif (argc != 3) {\n-\t\terror(_(\"bad number of arguments\"));\n-\t\tusage_with_options(replay_usage, replay_options);\n-\t}\n-\n \tonto = peel_committish(onto_name);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n-\n \t/*\n \t * Set desired values for rev walking options here. If they\n \t * are changed by some user specified option in setup_revisions()\n@@ -171,8 +160,9 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n-\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n-\t\tret = error(_(\"unhandled options\"));\n+\targc = setup_revisions(argc, argv, &revs, NULL);\n+\tif (argc > 1) {\n+\t\tret = error(_(\"unrecognized argument: %s\"), argv[1]);\n \t\tgoto cleanup;\n \t}\n \n@@ -205,8 +195,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\trevs.simplify_history = 0;\n \t}\n \n-\tstrvec_clear(&rev_walk_args);\n-\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -248,7 +236,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tret = result.clean;\n \n cleanup:\n-\tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 3567c98362..a1da4f9ef9 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -52,7 +52,7 @@ test_expect_success 'setup bare' '\n '\n \n test_expect_success 'using replay to rebase two branches, one on top of other' '\n-\tgit replay --onto main topic1 topic2 >result &&\n+\tgit replay --onto main topic1..topic2 >result &&\n \n \ttest_line_count = 1 result &&\n \n@@ -68,8 +68,16 @@ test_expect_success 'using replay to rebase two branches, one on top of other' '\n '\n \n test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n-\tgit -C bare replay --onto main topic1 topic2 >result-bare &&\n+\tgit -C bare replay --onto main topic1..topic2 >result-bare &&\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to rebase with a conflict' '\n+\ttest_expect_code 1 git replay --onto topic1 B..conflict\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase with a conflict' '\n+\ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n+'\n+\n test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 099aefeffc..0f39ed0d08 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,7 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -201,7 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -279,7 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -357,7 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1..topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,7 +456,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -523,7 +523,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -626,7 +626,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -685,7 +685,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484931","messageId":"20231115143327.2441397-13-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 12/14] replay: add --advance or 'cherry-pick' mode","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:25Z","receivedAt":"2023-11-15T14:34:08Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere is already a 'rebase' mode with `--onto`. Let's add an 'advance' or\n'cherry-pick' mode with `--advance`. This new mode will make the target\nbranch advance as we replay commits onto it.\n\nThe replayed commits should have a single tip, so that it's clear where\nthe target branch should be advanced. If they have more than one tip,\nthis new mode will error out.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt |  41 ++++++--\n builtin/replay.c             | 185 +++++++++++++++++++++++++++++++++--\n t/t3650-replay-basics.sh     |  34 +++++++\n 3 files changed, 243 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex fab4ea0178..6daa5b4275 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n SYNOPSIS\n --------\n [verse]\n-'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n+'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n \n DESCRIPTION\n -----------\n@@ -29,14 +29,25 @@ OPTIONS\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n +\n-The update-ref command(s) in the output will update the branch(es) in\n-the revision range to point at the new commits, similar to the way how\n-`git rebase --update-refs` updates multiple branches in the affected\n-range.\n+When `--onto` is specified, the update-ref command(s) in the output will\n+update the branch(es) in the revision range to point at the new\n+commits, similar to the way how `git rebase --update-refs` updates\n+multiple branches in the affected range.\n+\n+--advance <branch>::\n+\tStarting point at which to create the new commits; must be a\n+\tbranch name.\n++\n+When `--advance` is specified, the update-ref command(s) in the output\n+will update the branch passed as an argument to `--advance` to point at\n+the new commits (in other words, this mimics a cherry-pick operation).\n \n <revision-range>::\n-\tRange of commits to replay; see \"Specifying Ranges\" in\n-\tlinkgit:git-rev-parse and the \"Commit Limiting\" options below.\n+\tRange of commits to replay. More than one <revision-range> can\n+\tbe passed, but in `--advance <branch>` mode, they should have\n+\ta single tip, so that it's clear where <branch> should point\n+\tto. See \"Specifying Ranges\" in linkgit:git-rev-parse and the\n+\t\"Commit Limiting\" options below.\n \n include::rev-list-options.txt[]\n \n@@ -51,7 +62,9 @@ input to `git update-ref --stdin`.  It is of the form:\n \tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n \n where the number of refs updated depends on the arguments passed and\n-the shape of the history being replayed.\n+the shape of the history being replayed.  When using `--advance`, the\n+number of refs updated is always one, but for `--onto`, it can be one\n+or more (rebasing multiple branches simultaneously is supported).\n \n EXIT STATUS\n -----------\n@@ -71,6 +84,18 @@ $ git replay --onto target origin/main..mybranch\n update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n ------------\n \n+To cherry-pick the commits from mybranch onto target:\n+\n+------------\n+$ git replay --advance target origin/main..mybranch\n+update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH}\n+------------\n+\n+Note that the first two examples replay the exact same commits and on\n+top of the exact same new base, they only differ in that the first\n+provides instructions to make mybranch point at the new commits and\n+the second provides instructions to make target point at them.\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 7a660020d1..1a419cb7fd 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,6 +14,7 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"strmap.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -82,6 +83,146 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+struct ref_info {\n+\tstruct commit *onto;\n+\tstruct strset positive_refs;\n+\tstruct strset negative_refs;\n+\tint positive_refexprs;\n+\tint negative_refexprs;\n+};\n+\n+static void get_ref_information(struct rev_cmdline_info *cmd_info,\n+\t\t\t\tstruct ref_info *ref_info)\n+{\n+\tint i;\n+\n+\tref_info->onto = NULL;\n+\tstrset_init(&ref_info->positive_refs);\n+\tstrset_init(&ref_info->negative_refs);\n+\tref_info->positive_refexprs = 0;\n+\tref_info->negative_refexprs = 0;\n+\n+\t/*\n+\t * When the user specifies e.g.\n+\t *   git replay origin/main..mybranch\n+\t *   git replay ^origin/next mybranch1 mybranch2\n+\t * we want to be able to determine where to replay the commits.  In\n+\t * these examples, the branches are probably based on an old version\n+\t * of either origin/main or origin/next, so we want to replay on the\n+\t * newest version of that branch.  In contrast we would want to error\n+\t * out if they ran\n+\t *   git replay ^origin/master ^origin/next mybranch\n+\t *   git replay mybranch~2..mybranch\n+\t * the first of those because there's no unique base to choose, and\n+\t * the second because they'd likely just be replaying commits on top\n+\t * of the same commit and not making any difference.\n+\t */\n+\tfor (i = 0; i < cmd_info->nr; i++) {\n+\t\tstruct rev_cmdline_entry *e = cmd_info->rev + i;\n+\t\tstruct object_id oid;\n+\t\tconst char *refexpr = e->name;\n+\t\tchar *fullname = NULL;\n+\t\tint can_uniquely_dwim = 1;\n+\n+\t\tif (*refexpr == '^')\n+\t\t\trefexpr++;\n+\t\tif (repo_dwim_ref(the_repository, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)\n+\t\t\tcan_uniquely_dwim = 0;\n+\n+\t\tif (e->flags & BOTTOM) {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->negative_refs, fullname);\n+\t\t\tif (!ref_info->negative_refexprs)\n+\t\t\t\tref_info->onto = lookup_commit_reference_gently(the_repository,\n+\t\t\t\t\t\t\t\t\t\t&e->item->oid, 1);\n+\t\t\tref_info->negative_refexprs++;\n+\t\t} else {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->positive_refs, fullname);\n+\t\t\tref_info->positive_refexprs++;\n+\t\t}\n+\n+\t\tfree(fullname);\n+\t}\n+}\n+\n+static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n+\t\t\t\t  const char *onto_name,\n+\t\t\t\t  const char **advance_name,\n+\t\t\t\t  struct commit **onto,\n+\t\t\t\t  struct strset **update_refs)\n+{\n+\tstruct ref_info rinfo;\n+\n+\tget_ref_information(cmd_info, &rinfo);\n+\tif (!rinfo.positive_refexprs)\n+\t\tdie(_(\"need some commits to replay\"));\n+\tif (onto_name && *advance_name)\n+\t\tdie(_(\"--onto and --advance are incompatible\"));\n+\telse if (onto_name) {\n+\t\t*onto = peel_committish(onto_name);\n+\t\tif (rinfo.positive_refexprs <\n+\t\t    strset_get_size(&rinfo.positive_refs))\n+\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t} else if (*advance_name) {\n+\t\tstruct object_id oid;\n+\t\tchar *fullname = NULL;\n+\n+\t\t*onto = peel_committish(*advance_name);\n+\t\tif (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name),\n+\t\t\t     &oid, &fullname, 0) == 1) {\n+\t\t\t*advance_name = fullname;\n+\t\t} else {\n+\t\t\tdie(_(\"argument to --advance must be a reference\"));\n+\t\t}\n+\t\tif (rinfo.positive_refexprs > 1)\n+\t\t\tdie(_(\"cannot advance target with multiple sources because ordering would be ill-defined\"));\n+\t} else {\n+\t\tint positive_refs_complete = (\n+\t\t\trinfo.positive_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.positive_refs));\n+\t\tint negative_refs_complete = (\n+\t\t\trinfo.negative_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.negative_refs));\n+\t\t/*\n+\t\t * We need either positive_refs_complete or\n+\t\t * negative_refs_complete, but not both.\n+\t\t */\n+\t\tif (rinfo.negative_refexprs > 0 &&\n+\t\t    positive_refs_complete == negative_refs_complete)\n+\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\tif (negative_refs_complete) {\n+\t\t\tstruct hashmap_iter iter;\n+\t\t\tstruct strmap_entry *entry;\n+\n+\t\t\tif (rinfo.negative_refexprs == 0)\n+\t\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t\t\telse if (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\t\telse if (rinfo.positive_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\n+\t\t\t/* Only one entry, but we have to loop to get it */\n+\t\t\tstrset_for_each_entry(&rinfo.negative_refs,\n+\t\t\t\t\t      &iter, entry) {\n+\t\t\t\t*advance_name = entry->key;\n+\t\t\t}\n+\t\t} else { /* positive_refs_complete */\n+\t\t\tif (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine correct base for --onto\"));\n+\t\t\tif (rinfo.negative_refexprs == 1)\n+\t\t\t\t*onto = rinfo.onto;\n+\t\t}\n+\t}\n+\tif (!*advance_name) {\n+\t\t*update_refs = xcalloc(1, sizeof(**update_refs));\n+\t\t**update_refs = rinfo.positive_refs;\n+\t\tmemset(&rinfo.positive_refs, 0, sizeof(**update_refs));\n+\t}\n+\tstrset_clear(&rinfo.negative_refs);\n+\tstrset_clear(&rinfo.positive_refs);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n \t\t\t\t\t  struct commit *last_commit,\n \t\t\t\t\t  struct merge_options *merge_opt,\n@@ -114,20 +255,26 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n-\tstruct commit *onto;\n+\tconst char *advance_name = NULL;\n+\tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL;\n+\n \tstruct rev_info revs;\n+\tstruct commit *last_commit = NULL;\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n+\tstruct strset *update_refs = NULL;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay --onto <newbase> <revision-range>... # EXPERIMENTAL\"),\n+\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"advance\", &advance_name,\n+\t\t\t   N_(\"branch\"),\n+\t\t\t   N_(\"make replay advance given branch\")),\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n@@ -137,13 +284,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n \t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n \n-\tif (!onto_name) {\n-\t\terror(_(\"option --onto is mandatory\"));\n+\tif (!onto_name && !advance_name) {\n+\t\terror(_(\"option --onto or --advance is mandatory\"));\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tonto = peel_committish(onto_name);\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \t/*\n@@ -195,6 +340,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\trevs.simplify_history = 0;\n \t}\n \n+\tdetermine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n+\t\t\t      &onto, &update_refs);\n+\n+\tif (!onto) /* FIXME: Should handle replaying down to root commit */\n+\t\tdie(\"Replaying down to root commit is not supported yet!\");\n+\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -203,6 +354,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n+\n \tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n@@ -217,12 +369,15 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Update any necessary branches */\n+\t\tif (advance_name)\n+\t\t\tcontinue;\n \t\tdecoration = get_name_decoration(&commit->object);\n \t\tif (!decoration)\n \t\t\tcontinue;\n-\n \t\twhile (decoration) {\n-\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n+\t\t\t    strset_contains(update_refs, decoration->name)) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\n@@ -232,10 +387,22 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\t/* In --advance mode, advance the target ref */\n+\tif (result.clean == 1 && advance_name) {\n+\t\tprintf(\"update %s %s %s\\n\",\n+\t\t       advance_name,\n+\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t       oid_to_hex(&onto->object.oid));\n+\t}\n+\n \tmerge_finalize(&merge_opt, &result);\n \tret = result.clean;\n \n cleanup:\n+\tif (update_refs) {\n+\t\tstrset_clear(update_refs);\n+\t\tfree(update_refs);\n+\t}\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex a1da4f9ef9..68a87e7803 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -80,4 +80,38 @@ test_expect_success 'using replay on bare repo to rebase with a conflict' '\n \ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n '\n \n+test_expect_success 'using replay to perform basic cherry-pick' '\n+\t# The differences between this test and previous ones are:\n+\t#   --advance vs --onto\n+\t# 2nd field of result is refs/heads/main vs. refs/heads/topic2\n+\t# 4th field of result is hash for main instead of hash for topic2\n+\n+\tgit replay --advance main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/main \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse main >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n+\tgit -C bare replay --advance main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n+test_expect_success 'replay on bare repo fails with both --advance and --onto' '\n+\ttest_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare\n+'\n+\n+test_expect_success 'replay fails when both --advance and --onto are omitted' '\n+\ttest_must_fail git replay topic1..topic2 >result\n+'\n+\n test_done\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484932","messageId":"20231115143327.2441397-14-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 13/14] replay: add --contained to rebase contained branches","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:26Z","receivedAt":"2023-11-15T14:34:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's add a `--contained` option that can be used along with\n`--onto` to rebase all the branches contained in the <revision-range>\nargument.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt | 12 +++++++++++-\n builtin/replay.c             | 13 +++++++++++--\n t/t3650-replay-basics.sh     | 29 +++++++++++++++++++++++++++++\n 3 files changed, 51 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 6daa5b4275..133d7af9ee 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n SYNOPSIS\n --------\n [verse]\n-'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n+'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n \n DESCRIPTION\n -----------\n@@ -96,6 +96,16 @@ top of the exact same new base, they only differ in that the first\n provides instructions to make mybranch point at the new commits and\n the second provides instructions to make target point at them.\n \n+What if you have a stack of branches, one depending upon another, and\n+you'd really like to rebase the whole set?\n+\n+------------\n+$ git replay --contained --onto origin/main origin/main..tipbranch\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}\n+------------\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 1a419cb7fd..e14e33bcc5 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -258,6 +258,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tconst char *advance_name = NULL;\n \tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n+\tint contained = 0;\n \n \tstruct rev_info revs;\n \tstruct commit *last_commit = NULL;\n@@ -268,7 +269,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\"),\n+\t\tN_(\"git replay ([--contained] --onto <newbase> | --advance <branch>) \"\n+\t\t   \"<revision-range>... # EXPERIMENTAL\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -278,6 +280,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_BOOL(0, \"contained\", &contained,\n+\t\t\t N_(\"advance all branches contained in revision-range\")),\n \t\tOPT_END()\n \t};\n \n@@ -289,6 +293,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n+\tif (advance_name && contained)\n+\t\tdie(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t    \"--advance\", \"--contained\");\n+\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \t/*\n@@ -377,7 +385,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tcontinue;\n \t\twhile (decoration) {\n \t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n-\t\t\t    strset_contains(update_refs, decoration->name)) {\n+\t\t\t    (contained || strset_contains(update_refs,\n+\t\t\t\t\t\t\t  decoration->name))) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 68a87e7803..d6286f9580 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -114,4 +114,33 @@ test_expect_success 'replay fails when both --advance and --onto are omitted' '\n \ttest_must_fail git replay topic1..topic2 >result\n '\n \n+test_expect_success 'using replay to also rebase a contained branch' '\n+\tgit replay --contained --onto main main..topic3 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines H G F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic1 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic1 >>expect &&\n+\tprintf \"update refs/heads/topic3 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic3 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to also rebase a contained branch' '\n+\tgit -C bare replay --contained --onto main main..topic3 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n test_done\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484933","messageId":"20231115143327.2441397-15-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v7 14/14] replay: stop assuming replayed branches do not diverge","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:33:27Z","receivedAt":"2023-11-15T14:34:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command is able to replay multiple branches but when some of\nthem are based on other replayed branches, their commit should be\nreplayed onto already replayed commits.\n\nFor this purpose, let's store the replayed commit and its original\ncommit in a key value store, so that we can easily find and reuse a\nreplayed commit instead of the original one.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 44 ++++++++++++++++++++++++++--------\n t/t3650-replay-basics.sh | 52 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 86 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex e14e33bcc5..f37e511d8e 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -223,20 +223,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n \tstrset_clear(&rinfo.positive_refs);\n }\n \n+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,\n+\t\t\t\t    struct commit *commit,\n+\t\t\t\t    struct commit *fallback)\n+{\n+\tkhint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);\n+\tif (pos == kh_end(replayed_commits))\n+\t\treturn fallback;\n+\treturn kh_value(replayed_commits, pos);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n-\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  kh_oid_map_t *replayed_commits,\n+\t\t\t\t\t  struct commit *onto,\n \t\t\t\t\t  struct merge_options *merge_opt,\n \t\t\t\t\t  struct merge_result *result)\n {\n-\tstruct commit *base;\n+\tstruct commit *base, *replayed_base;\n \tstruct tree *pickme_tree, *base_tree;\n \n \tbase = pickme->parents->item;\n+\treplayed_base = mapped_commit(replayed_commits, base, onto);\n \n+\tresult->tree = repo_get_commit_tree(the_repository, replayed_base);\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n-\tmerge_opt->branch1 = short_commit_name(last_commit);\n+\tmerge_opt->branch1 = short_commit_name(replayed_base);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -250,7 +263,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tmerge_opt->ancestor = NULL;\n \tif (!result->clean)\n \t\treturn NULL;\n-\treturn create_commit(result->tree, pickme, last_commit);\n+\treturn create_commit(result->tree, pickme, replayed_base);\n }\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n@@ -266,6 +279,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n \tstruct strset *update_refs = NULL;\n+\tkh_oid_map_t *replayed_commits;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n@@ -362,21 +376,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\n-\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n+\treplayed_commits = kh_init_oid_map();\n \twhile ((commit = get_revision(&revs))) {\n \t\tconst struct name_decoration *decoration;\n+\t\tkhint_t pos;\n+\t\tint hr;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tlast_commit = pick_regular_commit(commit, replayed_commits, onto,\n+\t\t\t\t\t\t  &merge_opt, &result);\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Record commit -> last_commit mapping */\n+\t\tpos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);\n+\t\tif (hr == 0)\n+\t\t\tBUG(\"Duplicate rewritten commit: %s\\n\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tkh_value(replayed_commits, pos) = last_commit;\n+\n \t\t/* Update any necessary branches */\n \t\tif (advance_name)\n \t\t\tcontinue;\n@@ -405,13 +428,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-\tret = result.clean;\n-\n-cleanup:\n+\tkh_destroy_oid_map(replayed_commits);\n \tif (update_refs) {\n \t\tstrset_clear(update_refs);\n \t\tfree(update_refs);\n \t}\n+\tret = result.clean;\n+\n+cleanup:\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex d6286f9580..389670262e 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -143,4 +143,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to rebase multiple divergent branches' '\n+\tgit replay --onto main ^topic1 topic2 topic4 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines J I M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\tprintf \"update refs/heads/topic4 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic4 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '\n+\tgit -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&\n+\n+\ttest_line_count = 4 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\t>expect &&\n+\tfor i in 2 1 3 4\n+\tdo\n+\t\tprintf \"update refs/heads/topic$i \" >>expect &&\n+\t\tprintf \"%s \" $(grep topic$i result | cut -f 3 -d \" \") >>expect &&\n+\t\tgit -C bare rev-parse topic$i >>expect || return 1\n+\tdone &&\n+\n+\ttest_cmp expect result &&\n+\n+\ttest_write_lines F C M L B A >expect1 &&\n+\ttest_write_lines E D C M L B A >expect2 &&\n+\ttest_write_lines H G F C M L B A >expect3 &&\n+\ttest_write_lines J I M L B A >expect4 &&\n+\n+\tfor i in 1 2 3 4\n+\tdo\n+\t\tgit -C bare log --format=%s $(grep topic$i result | cut -f 3 -d \" \") >actual &&\n+\t\ttest_cmp expect$i actual || return 1\n+\tdone\n+'\n+\n test_done\n-- \n2.43.0.rc1.15.g29556bcc86\n\n"},{"id":"484935","messageId":"CAP8UFD0Es4qai98WB6bpykisBT628JndPXG8jg1=_uUbn4zogA@mail.gmail.com","threadId":"59560","inReplyTo":"fcfacd1a-cf5a-a393-d2e0-3c0388ae3529@gmx.de","subject":"Re: [PATCH v6 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:46:22Z","receivedAt":"2023-11-15T14:46:38Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Dscho,\n\nOn Wed, Nov 8, 2023 at 1:47 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> On Thu, 2 Nov 2023, Christian Couder wrote:\n\n> >     + ## Documentation/git-replay.txt (new) ##\n> >     +@@\n> >     ++git-replay(1)\n> >     ++=============\n> >     ++\n> >     ++NAME\n> >     ++----\n> >     ++git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos too\n> >     ++\n> >     ++\n> >     ++SYNOPSIS\n> >     ++--------\n> >     ++[verse]\n> >     ++'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n>\n> Technically, at this stage `git replay` requires precisely 5 arguments, so\n> the `<revision>...` is incorrect and should be `<upstream> <branch>`\n> instead. But it's not worth a new iteration to fix this.\n\nIt was actually:\n\n'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n\n(you can see it there:\nhttps://lore.kernel.org/git/20231102135151.843758-3-christian.couder@gmail.com/)\n\nBut I made a mistake in the range-diff command as I ran it against a\nprevious development version instead of the current one.\n\n> >     ++\n> >     ++DESCRIPTION\n> >     ++-----------\n> >     ++\n> >     ++Takes a range of commits and replays them onto a new location.\n> >     ++\n> >     ++THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n> >     ++\n> >     ++OPTIONS\n> >     ++-------\n> >     ++\n> >     ++--onto <newbase>::\n> >     ++  Starting point at which to create the new commits.  May be any\n> >     ++  valid commit, and not just an existing branch name.\n> >     ++\n>\n> Traditionally, this would be a place to describe the `<revision>` argument\n> (or, in this patch, to reflect the current state of `builtin/replay.c`,\n> the `<upstream> <branch>` arguments instead).\n\nI have fixed that in the v7 I just sent with the following:\n\n+SYNOPSIS\n+--------\n+[verse]\n+'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n+\n+DESCRIPTION\n+-----------\n+\n+Takes a range of commits, specified by <oldbase> and <branch>, and\n+replays them onto a new location (see `--onto` option below).\n+\n+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n+\n\nThanks for your review!\n"},{"id":"484936","messageId":"CAP8UFD10Ej1EQKodG=Ujd06X0Q7QXcDdXKU4mH+4-9Ox7Kt3+Q@mail.gmail.com","threadId":"59560","inReplyTo":"CAP8UFD24fzhiecJtANqEsxvh1mxT4pKR=QjfUFZh8C6HQE-k1A@mail.gmail.com","subject":"Re: [PATCH v6 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-15T14:51:23Z","receivedAt":"2023-11-15T14:51:38Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Elijah and Dscho,\n\nOn Tue, Nov 7, 2023 at 10:43 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n> On Tue, Nov 7, 2023 at 3:44 AM Elijah Newren <newren@gmail.com> wrote:\n> > Looking good, just one comment on one small hunk...\n> >\n> > On Thu, Nov 2, 2023 at 6:52 AM Christian Couder\n> > <christian.couder@gmail.com> wrote:\n> > >\n> > [...]\n> >\n> > >     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n> > >      -\n> > >         strvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n> > >\n> > >     ++  /*\n> > >     ++   * TODO: For now, let's warn when we see an option that we are\n> > >     ++   * going to override after setup_revisions() below. In the\n> > >     ++   * future we might want to either die() or allow them if we\n> > >     ++   * think they could be useful though.\n> > >     ++   */\n> > >     ++  for (i = 0; i < argc; i++) {\n> > >     ++          if (!strcmp(argv[i], \"--reverse\") || !strcmp(argv[i], \"--date-order\") ||\n> > >     ++              !strcmp(argv[i], \"--topo-order\") || !strcmp(argv[i], \"--author-date-order\") ||\n> > >     ++              !strcmp(argv[i], \"--full-history\"))\n> > >     ++                  warning(_(\"option '%s' will be overridden\"), argv[i]);\n> > >     ++  }\n> > >     ++\n\n> > 2) This seems like an inefficient way to provide this warning; could\n> > we avoid parsing the arguments for an extra time?  Perhaps instead\n> >   a) set the desired values here, before setup_revisions()\n> >   b) after setup_revisions, check whether these values differ from the\n> > desired values, if so throw a warning.\n> >   c) set the desired values, again\n>\n> Yeah, that would work. The downside is that it would be more difficult\n> in the warning to tell the user which command line option was\n> overridden as there are some values changed by different options.\n> Maybe we can come up with a warning message that is still useful and\n> enough for now, as the command is supposed to be used by experienced\n> users. Perhaps something like:\n>\n> warning(_(\"some rev walking options will be overridden as '%s' bit in\n> 'struct rev_info' will be forced\"), \"sort_order\");\n\nI have implemented this in the v7 I just sent.\n\nThanks!\n"},{"id":"484972","messageId":"0ddca907-6e64-b684-2e08-c7e95e737a3c@gmx.de","threadId":"59560","inReplyTo":"CAP8UFD0Es4qai98WB6bpykisBT628JndPXG8jg1=_uUbn4zogA@mail.gmail.com","subject":"Re: [PATCH v6 00/14] Introduce new `git replay` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-11-16T08:45:20Z","receivedAt":"2023-11-16T08:45:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Wed, 15 Nov 2023, Christian Couder wrote:\n\n> On Wed, Nov 8, 2023 at 1:47 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> > On Thu, 2 Nov 2023, Christian Couder wrote:\n>\n> > >     + ## Documentation/git-replay.txt (new) ##\n> > >     +@@\n> > >     ++git-replay(1)\n> > >     ++=============\n> > >     ++\n> > >     ++NAME\n> > >     ++----\n> > >     ++git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos too\n> > >     ++\n> > >     ++\n> > >     ++SYNOPSIS\n> > >     ++--------\n> > >     ++[verse]\n> > >     ++'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n> >\n> > Technically, at this stage `git replay` requires precisely 5 arguments, so\n> > the `<revision>...` is incorrect and should be `<upstream> <branch>`\n> > instead. But it's not worth a new iteration to fix this.\n>\n> It was actually:\n>\n> 'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n\nRight.\n\n> > >     ++\n> > >     ++DESCRIPTION\n> > >     ++-----------\n> > >     ++\n> > >     ++Takes a range of commits and replays them onto a new location.\n> > >     ++\n> > >     ++THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n> > >     ++\n> > >     ++OPTIONS\n> > >     ++-------\n> > >     ++\n> > >     ++--onto <newbase>::\n> > >     ++  Starting point at which to create the new commits.  May be any\n> > >     ++  valid commit, and not just an existing branch name.\n> > >     ++\n> >\n> > Traditionally, this would be a place to describe the `<revision>` argument\n> > (or, in this patch, to reflect the current state of `builtin/replay.c`,\n> > the `<upstream> <branch>` arguments instead).\n>\n> I have fixed that in the v7 I just sent with the following:\n>\n> +SYNOPSIS\n> +--------\n> +[verse]\n> +'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n\nI still think that the following would serve us better:\n\n\t[verse]\n\t(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>\n\nBut if nobody else feels as strongly, I won't bring this up again.\n\nCiao,\nJohannes\n"},{"id":"484973","messageId":"CAP8UFD2hB5j2rZ-+WTXgju4mf58HGtEJR87LB08g3Hu3-G2joA@mail.gmail.com","threadId":"59560","inReplyTo":"0ddca907-6e64-b684-2e08-c7e95e737a3c@gmx.de","subject":"Re: [PATCH v6 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-16T08:52:24Z","receivedAt":"2023-11-16T08:52:38Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Dscho,\n\nOn Thu, Nov 16, 2023 at 9:45 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> On Wed, 15 Nov 2023, Christian Couder wrote:\n\n> > I have fixed that in the v7 I just sent with the following:\n> >\n> > +SYNOPSIS\n> > +--------\n> > +[verse]\n> > +'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n>\n> I still think that the following would serve us better:\n>\n>         [verse]\n>         (EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>\n>\n> But if nobody else feels as strongly, I won't bring this up again.\n\nFor the tests to pass, the SYNOPSIS should be the same as the first\nline of the `git replay -h` output. After this series it is:\n\nusage: git replay ([--contained] --onto <newbase> | --advance\n<branch>) <revision-range>... # EXPERIMENTAL\n\nAs the usage is supposed to describe how the command should be used, I\nthink it makes sense for \"EXPERIMENTAL\" to be in a shell comment after\nthe command.\n\nThanks,\nChristian.\n"},{"id":"484974","messageId":"fb6eb685-0af1-082a-b20c-028b06b6914e@gmx.de","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"Re: [PATCH v7 00/14] Introduce new `git replay` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-11-16T08:53:15Z","receivedAt":"2023-11-16T08:53:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\n[focusing exclusively on the `range-diff` because I lack the capacity for\nanything beyond that]\n\nOn Wed, 15 Nov 2023, Christian Couder wrote:\n\n> # Range-diff between v6 and v7\n>\n> (Sorry it looks like patch 6/14 in v7 is considered to be completely\n> different from what it was in v6, so the range-diff is not showing\n> differences between them.)\n>\n>  1:  fac0a9dff4 =  1:  cddcd967b2 t6429: remove switching aspects of fast-rebase\n>  2:  bec2eb8928 !  2:  c8476fb093 replay: introduce new builtin\n>     @@ Documentation/git-replay.txt (new)\n>      +DESCRIPTION\n>      +-----------\n>      +\n>     -+Takes a range of commits and replays them onto a new location.\n>     ++Takes a range of commits, specified by <oldbase> and <branch>, and\n>     ++replays them onto a new location (see `--onto` option below).\n>      +\n>      +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n>      +\n\nThank you.\n\n>  3:  b0cdfdc0c3 =  3:  43322abd1e replay: start using parse_options API\n>  4:  c3403f0b9d =  4:  6524c7f045 replay: die() instead of failing assert()\n>  5:  4188eeac30 =  5:  05d0efa3cb replay: introduce pick_regular_commit()\n>  6:  b7b4d9001e <  -:  ---------- replay: change rev walking options\n>  -:  ---------- >  6:  c7a5aad3d6 replay: change rev walking options\n\nThe actual range-diff for the sixth patch looks like this:\n\n-- snip --\n6:  b7b4d9001e9 ! 6:  c7a5aad3d62 replay: change rev walking options\n    @@ Metadata\n      ## Commit message ##\n         replay: change rev walking options\n\n    -    Let's set the rev walking options we need after calling\n    -    setup_revisions() instead of before. This enforces options we always\n    -    want for now.\n    +    Let's force the rev walking options we need after calling\n    +    setup_revisions() instead of before.\n    +\n    +    This might override some user supplied rev walking command line options\n    +    though. So let's detect that and warn users by:\n    +\n    +      a) setting the desired values, before setup_revisions(),\n    +      b) checking after setup_revisions() whether these values differ from\n    +         the desired values,\n    +      c) if so throwing a warning and setting the desired values again.\n\n         We want the command to work from older commits to newer ones by default.\n         Also we don't want history simplification, as we want to deal with all\n         the commits in the affected range.\n\n    -    When we see an option that we are going to override, we emit a warning\n    -    to avoid confusion as much as possible though.\n    -\n         Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n         Co-authored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Elijah Newren <newren@gmail.com>\n    @@ Commit message\n\n      ## builtin/replay.c ##\n     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n    - \tstruct merge_result result;\n    - \tstruct strbuf reflog_msg = STRBUF_INIT;\n    - \tstruct strbuf branch_name = STRBUF_INIT;\n    --\tint ret = 0;\n    -+\tint i, ret = 0;\n    -\n    - \tconst char * const replay_usage[] = {\n    - \t\tN_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n    -@@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n\n      \trepo_init_revisions(the_repository, &revs, prefix);\n\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n     -\trevs.max_parents = 1;\n     -\trevs.cherry_mark = 1;\n     -\trevs.limited = 1;\n    --\trevs.reverse = 1;\n    ++\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n    ++\n    ++\t/*\n    ++\t * Set desired values for rev walking options here. If they\n    ++\t * are changed by some user specified option in setup_revisions()\n    ++\t * below, we will detect that below and then warn.\n    ++\t *\n    ++\t * TODO: In the future we might want to either die(), or allow\n    ++\t * some options changing these values if we think they could\n    ++\t * be useful.\n    ++\t */\n    + \trevs.reverse = 1;\n     -\trevs.right_only = 1;\n    --\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n    --\trevs.topo_order = 1;\n    + \trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n    + \trevs.topo_order = 1;\n     -\n    - \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n    +-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n    ++\trevs.simplify_history = 0;\n\n    -+\t/*\n    -+\t * TODO: For now, let's warn when we see an option that we are\n    -+\t * going to override after setup_revisions() below. In the\n    -+\t * future we might want to either die() or allow them if we\n    -+\t * think they could be useful though.\n    -+\t */\n    -+\tfor (i = 0; i < argc; i++) {\n    -+\t\tif (!strcmp(argv[i], \"--reverse\") || !strcmp(argv[i], \"--date-order\") ||\n    -+\t\t    !strcmp(argv[i], \"--topo-order\") || !strcmp(argv[i], \"--author-date-order\") ||\n    -+\t\t    !strcmp(argv[i], \"--full-history\"))\n    -+\t\t\twarning(_(\"option '%s' will be overridden\"), argv[i]);\n    -+\t}\n    -+\n      \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n      \t\tret = error(_(\"unhandled options\"));\n      \t\tgoto cleanup;\n      \t}\n\n    -+\t/* requirements/overrides for revs */\n    -+\trevs.reverse = 1;\n    -+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n    -+\trevs.topo_order = 1;\n    -+\trevs.simplify_history = 0;\n    ++\t/*\n    ++\t * Detect and warn if we override some user specified rev\n    ++\t * walking options.\n    ++\t */\n    ++\tif (revs.reverse != 1) {\n    ++\t\twarning(_(\"some rev walking options will be overridden as \"\n    ++\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n    ++\t\t\t\"reverse\");\n    ++\t\trevs.reverse = 1;\n    ++\t}\n    ++\tif (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {\n    ++\t\twarning(_(\"some rev walking options will be overridden as \"\n    ++\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n    ++\t\t\t\"sort_order\");\n    ++\t\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n    ++\t}\n    ++\tif (revs.topo_order != 1) {\n    ++\t\twarning(_(\"some rev walking options will be overridden as \"\n    ++\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n    ++\t\t\t\"topo_order\");\n    ++\t\trevs.topo_order = 1;\n    ++\t}\n    ++\tif (revs.simplify_history != 0) {\n    ++\t\twarning(_(\"some rev walking options will be overridden as \"\n    ++\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n    ++\t\t\t\"simplify_history\");\n    ++\t\trevs.simplify_history = 0;\n    ++\t}\n     +\n      \tstrvec_clear(&rev_walk_args);\n\n-- snap --\n\nThis looks really good. Thank you for going the extra step to make this\npatch so much better.\n\n>  7:  c57577a9b8 =  7:  01f35f924b replay: add an important FIXME comment about gpg signing\n>  8:  e78be50f3d =  8:  1498b24bad replay: remove progress and info output\n>  9:  e4c79b676f =  9:  6786fc147b replay: remove HEAD related sanity check\n> 10:  8d89f1b733 ! 10:  9a24dbb530 replay: make it a minimal server side command\n>     @@ Commit message\n>          Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n>\n>       ## Documentation/git-replay.txt ##\n>     -@@ Documentation/git-replay.txt: SYNOPSIS\n>     - DESCRIPTION\n>     +@@ Documentation/git-replay.txt: DESCRIPTION\n>       -----------\n>\n>     --Takes a range of commits and replays them onto a new location.\n>     -+Takes a range of commits and replays them onto a new location. Leaves\n>     -+the working tree and the index untouched, and updates no\n>     -+references. The output of this command is meant to be used as input to\n>     + Takes a range of commits, specified by <oldbase> and <branch>, and\n>     +-replays them onto a new location (see `--onto` option below).\n>     ++replays them onto a new location (see `--onto` option below). Leaves\n>     ++the working tree and the index untouched, and updates no references.\n>     ++The output of this command is meant to be used as input to\n>      +`git update-ref --stdin`, which would update the relevant branches.\n>\n>       THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>         struct merge_result result;\n>      -  struct strbuf reflog_msg = STRBUF_INIT;\n>         struct strbuf branch_name = STRBUF_INIT;\n>     -   int i, ret = 0;\n>     +   int ret = 0;\n>\n>      @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n>         onto = peel_committish(onto_name);\n\nLooks good to me.\n\n> 11:  3d433a1322 ! 11:  ad6ca2fbef replay: use standard revision ranges\n>     @@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new\n>\n>       DESCRIPTION\n>       -----------\n>     -@@ Documentation/git-replay.txt: DESCRIPTION\n>     - Takes a range of commits and replays them onto a new location. Leaves\n>     - the working tree and the index untouched, and updates no\n>     - references. The output of this command is meant to be used as input to\n>     +\n>     +-Takes a range of commits, specified by <oldbase> and <branch>, and\n>     +-replays them onto a new location (see `--onto` option below). Leaves\n>     ++Takes ranges of commits and replays them onto a new location. Leaves\n>     + the working tree and the index untouched, and updates no references.\n>     + The output of this command is meant to be used as input to\n>      -`git update-ref --stdin`, which would update the relevant branches.\n>      +`git update-ref --stdin`, which would update the relevant branches\n>      +(see the OUTPUT section below).\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>         struct merge_options merge_opt;\n>         struct merge_result result;\n>      -  struct strbuf branch_name = STRBUF_INIT;\n>     -   int i, ret = 0;\n>     +   int ret = 0;\n>\n>         const char * const replay_usage[] = {\n>      -          N_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>      -  strvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n>      -\n>         /*\n>     -    * TODO: For now, let's warn when we see an option that we are\n>     -    * going to override after setup_revisions() below. In the\n>     +    * Set desired values for rev walking options here. If they\n>     +    * are changed by some user specified option in setup_revisions()\n>      @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n>     -                   warning(_(\"option '%s' will be overridden\"), argv[i]);\n>     -   }\n>     +   revs.topo_order = 1;\n>     +   revs.simplify_history = 0;\n>\n>      -  if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n>      -          ret = error(_(\"unhandled options\"));\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>         }\n>\n>      @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n>     -   revs.topo_order = 1;\n>     -   revs.simplify_history = 0;\n>     +           revs.simplify_history = 0;\n>     +   }\n>\n>      -  strvec_clear(&rev_walk_args);\n>      -\n\nThis is the correct spot for that documentation change. Good.\n\n> 12:  cca8105382 ! 12:  081864ed5f replay: add --advance or 'cherry-pick' mode\n>     @@ builtin/replay.c: static struct commit *pick_regular_commit(struct commit *pickm\n>         struct merge_options merge_opt;\n>         struct merge_result result;\n>      +  struct strset *update_refs = NULL;\n>     -   int i, ret = 0;\n>     +   int ret = 0;\n>\n>         const char * const replay_usage[] = {\n>      -          N_(\"git replay --onto <newbase> <revision-range>... # EXPERIMENTAL\"),\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>\n>         /*\n>      @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n>     -   revs.topo_order = 1;\n>     -   revs.simplify_history = 0;\n>     +           revs.simplify_history = 0;\n>     +   }\n>\n>      +  determine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n>      +                        &onto, &update_refs);\n> 13:  92287a2cc8 ! 13:  19c4016c7c replay: add --contained to rebase contained branches\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>         struct rev_info revs;\n>         struct commit *last_commit = NULL;\n>      @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n>     -   int i, ret = 0;\n>     +   int ret = 0;\n>\n>         const char * const replay_usage[] = {\n>      -          N_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\"),\n> 14:  529a7fda40 ! 14:  29556bcc86 replay: stop assuming replayed branches do not diverge\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>         struct merge_result result;\n>         struct strset *update_refs = NULL;\n>      +  kh_oid_map_t *replayed_commits;\n>     -   int i, ret = 0;\n>     +   int ret = 0;\n>\n>         const char * const replay_usage[] = {\n>      @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix)\n\nThe last three only have context-only changes. Obviously good.\n\nApart from the one little outstanding nit where I would love to see\n`(EXPERIMENTAL!)` as the first word of the synopsis both in the manual\npage and in the output of `git replay -h`, you have addressed all of my\nconcerns.\n\nThank you!\nJohannes\n"},{"id":"485076","messageId":"CABPp-BFgJFeL4cUJ8+JZ2ZuM58F3rSYGYu_5w_mDLYWtzO4raw@mail.gmail.com","threadId":"59560","inReplyTo":"fb6eb685-0af1-082a-b20c-028b06b6914e@gmx.de","subject":"Re: [PATCH v7 00/14] Introduce new `git replay` command","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2023-11-23T19:32:37Z","receivedAt":"2023-11-23T19:32:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Christian,\n\nOn Thu, Nov 16, 2023 at 12:53 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> On Wed, 15 Nov 2023, Christian Couder wrote:\n>\n> > # Range-diff between v6 and v7\n> >\n[...]\n> Apart from the one little outstanding nit where I would love to see\n> `(EXPERIMENTAL!)` as the first word of the synopsis both in the manual\n> page and in the output of `git replay -h`, you have addressed all of my\n> concerns.\n>\n> Thank you!\n> Johannes\n\nLooks good to me too.  Thanks!\n"},{"id":"485081","messageId":"xmqqwmu89eqh.fsf@gitster.g","threadId":"59560","inReplyTo":"CABPp-BFgJFeL4cUJ8+JZ2ZuM58F3rSYGYu_5w_mDLYWtzO4raw@mail.gmail.com","subject":"Re: [PATCH v7 00/14] Introduce new `git replay` command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-11-24T00:28:22Z","receivedAt":"2023-11-24T00:28:25Z","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> Hi Christian,\n>\n> On Thu, Nov 16, 2023 at 12:53 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n>> On Wed, 15 Nov 2023, Christian Couder wrote:\n>>\n>> > # Range-diff between v6 and v7\n>> >\n> [...]\n>> Apart from the one little outstanding nit where I would love to see\n>> `(EXPERIMENTAL!)` as the first word of the synopsis both in the manual\n>> page and in the output of `git replay -h`, you have addressed all of my\n>> concerns.\n>>\n>> Thank you!\n>> Johannes\n>\n> Looks good to me too.  Thanks!\n\nThanks, both, for reviews.  I guess the only remaining issue now is\nthe \"(EXPERIMENTAL)\" label and we are ready to declare a victory?\n\nThanks.\n"},{"id":"485108","messageId":"20231124111044.3426007-1-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231115143327.2441397-1-christian.couder@gmail.com","subject":"[PATCH v8 00/14] Introduce new `git replay` command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:29Z","receivedAt":"2023-11-24T11:11:03Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"# Intro\n\n`git replay` has initially been developed entirely by Elijah Newren\nmostly last year (2022) at:\n\nhttps://github.com/newren/git/commits/replay\n\nI took over this year to polish and upstream it as GitLab is\ninterested in replacing libgit2, and for that purpose needs a command\nto do server side (so without using a worktree) rebases, cherry-picks\nand reverts.\n\nI reduced the number of commits and features in this patch series,\ncompared to what Elijah already developed. Especially I stopped short\nof replaying merge commits and replaying interactively. These and\nother features might be upstreamed in the future after this patch\nseries has graduated.\n\nThe focus in this series is to make it a good plumbing command that\ncan already be used server side and that replaces the \"fast-rebase\"\ntest-tool command. So things to make it easier to use on the command\nline, and more advanced features (like replaying merges) are left out.\n\nIt looks like GitHub has actually already been using version 3 of this\npatch series in production with good results. See:\n\nhttps://github.blog/2023-07-27-scaling-merge-ort-across-github/\nhttps://lore.kernel.org/git/304f2a49-5e05-7655-9f87-2011606df5db@gmx.de/\n\n# Content of this cover letter\n\nThe \"Quick Overview\" and \"Reasons for diverging from cherry-pick &\nrebase\" sections just below are describing the purpose of the new\ncommand in the big scheme of things. They are taken from Elijah's\ndesign notes\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt)\nand describe what we want this command to become and the reasons for\nthat, not what the command is after only this patch series. Also these\ndesign notes were written at least one year ago, so parts of those 2\nsections are not true anymore. I have added Phillip Wood's or Felipe\nContreras' notes (thanks to them) where that's the case, but some now\nflawed parts may have missed.\n\nAfter these two sections, starting with the \"Important limitations\"\nsection, you will find sections describing what is actually in this\npatch series.\n\nMore interesting material is available in Elijah's design notes like\nan \"Intro via examples\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L37-L132),\na discussion about \"Preserving topology, replaying merges\"\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L264-L341)\nand a \"Current status\" section describing Elijah's work\n(https://github.com/newren/git/blob/replay/replay-design-notes.txt#L344-L392)\nbefore I started working on upstreaming it.\n\nI have not included this material here though, as the documentation\nadded by this patch series for the `git replay` command already\nincludes an \"EXAMPLES\" section, and other sections of Elijah's design\nnotes might not be interesting for now. Also this cover letter is\nalready pretty long.  But reviewers can refer to the links above if\nthey think it can help.\n\n# Quick Overview (from Elijah's design notes)\n\n`git replay`, at a basic level, can perhaps be thought of as a\n\"default-to-dry-run rebase\" -- meaning no updates to the working tree,\nor to the index, or to any references.  However, it differs from\nrebase in that it:\n\n  * Works for branches that aren't checked out\n\n  * Works in a bare repository\n\n  * Can replay multiple branches simultaneously (with or without common\n    history in the range being replayed)\n\n  * Preserves relative topology by default (merges are replayed too in\n    Elijah's original work, not in this series)\n\n  * Focuses on performance\n\n  * Has several altered defaults as a result of the above\n\nI sometimes think of `git replay` as \"fast-replay\", a patch-based\nanalogue to the snapshot-based fast-export & fast-import tools.\n\n# Reasons for diverging from cherry-pick & rebase (from Elijah's\n  design notes)\n\nThere are multiple reasons to diverge from the defaults in cherry-pick and\nrebase.\n\n* Server side needs\n\n  * Both cherry-pick and rebase, via the sequencer, are heavily tied\n    to updating the working tree, index, some refs, and a lot of\n    control files with every commit replayed, and invoke a mess of\n    hooks[1] that might be hard to avoid for backward compatibility\n    reasons (at least, that's been brought up a few times on the\n    list).\n\n  * cherry-pick and rebase both fork various subprocesses\n    unnecessarily, but somewhat intrinsically in part to ensure the\n    same hooks are called that old scripted implementations would have\n    called.\n\n    Note: since 356ee4659bb (sequencer: try to commit without forking\n    'git commit', 2017-11-24) cherry-pick and rebase do not fork\n    subprocesses other than hooks for the cases covered by this patch\n    series (i.e. they do not fork \"git commit\" for simple picks).\n\n  * \"Dry run\" behavior, where there are no updates to worktree, index,\n    or even refs might be important.\n\n  * Should not assume users only want to operate on HEAD (see next\n    section)\n\n* Decapitate HEAD-centric assumptions\n\n  * cherry-pick forces commits to be played on top of HEAD;\n    inflexible.\n\n  * rebase assumes the range of commits to be replayed is\n    upstream..HEAD by default, though it allows one to replay\n    upstream..otherbranch -- but it still forcibly and needlessly\n    checks out 'otherbranch' before starting to replay things.\n\n    Note: since 767a9c417eb (rebase -i: stop checking out the tip of\n    the branch to rebase, 2020-01-24) it's not true that rebase\n    forcibly and needlessly checks out 'otherbranch'.\n\n  * Assuming HEAD is involved severely limits replaying multiple\n    (possibly divergent) branches.\n\n    Note: since 89fc0b53fdb (rebase: update refs from 'update-ref'\n    commands, 2022-07-19) the sequencer can update multiple\n    branches. The issue with divergent branch is with command line\n    arguments and the todo list generation rather than the\n    capabilities of the sequencer.\n\n  * Once you stop assuming HEAD has a certain meaning, there's not\n    much reason to have two separate commands anymore (except for the\n    funny extra not-necessarily-compatible options both have gained\n    over time).\n\n  * (Micro issue: Assuming HEAD is involved also makes it harder for\n    new users to learn what rebase means and does; it makes command\n    lines hard to parse.  Not sure I want to harp on this too much, as\n    I have a suspicion I might be creating a tool for experts with\n    complicated use cases, but it's a minor quibble.)\n\n* Performance\n\n  * jj is slaughtering us on rebase speed[2].  I would like us to become\n    competitive.  (I dropped a few comments in the link at [2] about why\n    git is currently so bad.)\n\n  * From [3], there was a simple 4-patch series in linux.git that took\n    53 seconds to rebase.  Switching to ort dropped it to 16 seconds.\n    While that sounds great, only 11 *milliseconds* were needed to do\n    the actual merges.  That means almost *all* the time (>99%) was\n    overhead!  Big offenders:\n\n    * --reapply-cherry-picks should be the default\n\n    * can_fast_forward() should be ripped out, and perhaps other extraneous\n      revision walks\n\n      Note: d42c9ffa0f (rebase: factor out branch_base calculation,\n      2022-10-17) might already deal with that (according to Felipe\n      Contreras).\n\n    * avoid updating working tree, index, refs, reflogs, and control\n      structures except when needed (e.g. hitting a conflict, or operation\n      finished)\n\n  * Other performance ideas (mostly for future work, not in this\n    series)\n\n    * single-file control structures instead of directory of files\n      (when doing interactive things which is in Elijah's original\n      work, but not in this series)\n\n    * avoid forking subprocesses unless explicitly requested (e.g.\n      --exec, --strategy, --run-hooks).  For example, definitely do not\n      invoke `git commit` or `git merge`.\n\n    * Sanitize hooks:\n\n      * dispense with all per-commit hooks for sure (pre-commit,\n        post-commit, post-checkout).\n\n      * pre-rebase also seems to assume exactly 1 ref is written, and\n        invoking it repeatedly would be stupid.  Plus, it's specific\n        to \"rebase\".  So...ignore?  (Stolee's --ref-update option for\n        rebase probably broke the pre-rebase assumptions already...)\n\n      * post-rewrite hook might make sense, but fast-import got\n        exempted, and I think of replay like a patch-based analogue\n        to the snapshot-based fast-import.\n\n    * When not running server side, resolve conflicts in a sparse-cone\n      sparse-index worktree to reduce number of files written to a\n      working tree.  (See below as well.)\n\n    * [High risk of possible premature optimization] Avoid large\n      numbers of newly created loose objects, when replaying large\n      numbers of commits.  Two possibilities: (1) Consider using\n      tmp-objdir and pack objects from the tmp-objdir at end of\n      exercise, (2) Lift code from git-fast-import to immediately\n      stuff new objects into a pack?\n\n* Multiple branches and non-checked out branches\n\n  * The ability to operate on non-checked out branches also implies\n    that we should generally be able to replay when in a dirty working\n    tree (exception being when we expect to update HEAD and any of the\n    dirty files is one that needs to be updated by the replay).\n\n  * Also, if we are operating locally on a non-checked out branch and\n    hit a conflict, we should have a way to resolve the conflict\n    without messing with the user's work on their current\n    branch. (This is not is this patch series though.)\n\n    * Idea: new worktree with sparse cone + sparse index checkout,\n      containing only files in the root directory, and whatever is\n      necessary to get the conflicts\n\n    * Companion to above idea: control structures should be written to\n      $GIT_COMMON_DIR/replay-${worktree}, so users can have multiple\n      replay sessions, and so we know which worktrees are associated\n      with which replay operations.\n\n  - [1] https://lore.kernel.org/git/pull.749.v3.git.git.1586044818132.gitgitgadget@gmail.com/\n  - [2] https://github.com/martinvonz/jj/discussions/49\n  - [3] https://lore.kernel.org/git/CABPp-BE48=97k_3tnNqXPjSEfA163F8hoE+HY0Zvz1SWB2B8EA@mail.gmail.com/\n\n# Important limitations\n\n* The code exits with code 1 if there are any conflict. No\n  resumability. No nice output. No interactivity. No special exit code\n  depending on the reason.\n\n* When a commit becomes empty as it is replayed, it is still replayed\n  as an empty commit, instead of being dropped.\n\n* No replaying merges, nor root commits. Only regular commits.\n\n* Signed commits are not properly handled. It's not clear what to do\n  to such commits when replaying on the server side.\n\n* Notes associated with replayed commits are not updated nor\n  duplicated. (Thanks to Phillip Wood for noticing.)\n\n# Commit overview\n\n* 1/14 t6429: remove switching aspects of fast-rebase\n\n    Preparatory commit to make it easier to later replace the\n    fast-rebase test-tool by `git replay` without breaking existing\n    tests.\n\n* 2/14 replay: introduce new builtin\n\n    This creates a minimal `git replay` command by moving the code\n    from the `fast-rebase` test helper from `t/helper/` into\n    `builtin/` and doing some renames and a few other needed changes.\n    Since v7, there is only a synopsys change in the doc, and the\n    corresponding usage message change, as suggested by Dscho.\n\n* - 3/14 replay: start using parse_options API\n  - 4/14 replay: die() instead of failing assert()\n  - 5/14 replay: introduce pick_regular_commit()\n  - 6/14 replay: change rev walking options\n  - 7/14 replay: add an important FIXME comment about gpg signing\n  - 8/14 replay: remove progress and info output\n  - 9/14 replay: remove HEAD related sanity check\n\n    These slowly change the command to make it behave more like\n    regular commands and to start cleaning up its output.\n\n* 10/14 replay: make it a minimal server side command\n\n    After the cleaning up in previous commits, it's now time to\n    radically change the way it works by stopping it to do ref\n    updates, to update the index and worktree, to consider HEAD as\n    special. Instead just make it output commands that should be\n    passed to `git update-ref --stdin`.\n\n* 11/14 replay: use standard revision ranges\n\n    Start adding new interesting features and also documentation and\n    tests, as the interface of the command is cristalizing into its\n    final form.\n\n* - 12/14 replay: add --advance or 'cherry-pick' mode\n  - 13/14 replay: add --contained to rebase contained branches\n\n    Add new options and features to the command.\n\n* 14/14 replay: stop assuming replayed branches do not diverge\n\n    This adds another interesting feature, as well as related\n    documentation and tests.\n\n# Notes about `fast-rebase`, tests and documentation\n\nThe `fast-rebase` test-tool helper was developed by Elijah to\nexperiment with a rebasing tool that would be developed from scratch\nbased on his merge-ort work, could be used to test that merge-ort\nwork, and would not have the speed and interface limitations of `git\nrebase` or `git cherry-pick`.\n\nThis `fast-rebase` helper was used before this series in:\n\nt6429-merge-sequence-rename-caching.sh\n\nSo when `git replay` is created from `fast-rebase` in patch 2/14, the\nt6429 test script is also converted to use `git replay`. This ensures\nthat `git replay` doesn't break too badly during the first 10 patches\nin this patch series.\n\nTests and documentation are introduced specifically for `git replay`\nas soon as patch 2/14, but they are not much improved since around\n11/14 as it doesn't make much sense to document and test behavior that\nwe know is going to change soon.\n\n# Possibly controversial issues \n\n* bare or not bare: this series works towards a plumbing command with\n  the end goal of it being usable and used first on bare repos,\n  contrary to existing commands like `git rebase` and `git\n  cherry-pick`. The tests check that the command works on both bare\n  and non-bare repo though.\n\n* exit status: a successful, non-conflicted replay exits with code\n  0. When the replay has conflicts, the exit status is 1. If the\n  replay is not able to complete (or start) due to some kind of error,\n  the exit status is something other than 0 or 1. There are a few\n  tests checking that. It has been suggested in an internal review\n  that conflicts might want to get a more specific error code as an\n  error code of 1 might be quite easy to return by accident. It\n  doesn't seem to me from their docs (which might want to be improved,\n  I didn't look at the code) that other commands like `git merge` and\n  `git rebase` exit with a special error code in case of conflict.\n\n* make worktree and index changes optional: commit 10/14 stops\n  updating the index and worktree, but it might be better especially\n  for cli users to make that optional. The issue is that this would\n  make the command more complex while we are developing a number of\n  important features so that the command can be used on bare repos. It\n  seems that this should rather be done in an iterative improvement\n  after the important features have landed.\n\n* --advance and --contained: these two advanced options might not\n  belong to this first series and could perhaps be added in a followup\n  series in separate commits. On the other hand the code for\n  --contained seems involved with the code of --advance and it's nice\n  to see soon that git replay can indeed do cherry-picking and rebase\n  many refs at once, and this way fullfil these parts of its promise.\n\n* replaying diverging branches: 14/14 the last patch in the series,\n  which allow replaying diverging branches, can be seen as a\n  fundamental fix or alternatively as adding an interesting\n  feature. So it's debatable if it should be in its own patch along\n  with its own tests as in this series, or if it should be merged into\n  a previous patch and which one.\n\n* only 2 patches: this patch series can be seen from a high level\n  point of view as 1) introducing the new `git replay` command, and 2)\n  using `git replay` to replace, and get rid of, the fast-rebase\n  test-tool command. The fact that not much of the original\n  fast-rebase code and interface is left would agree with that point\n  of view. On the other hand, fast-rebase can also be seen as a first\n  iteration towards `git replay`. So it can also make sense to see how\n  `git replay` evolved from it.\n\n# Changes between v7 and v8\n\nThanks to Dscho, Linus Arver, Dragan Simic, Elijah, Junio, Derrick\nStolee, Phillip Wood, Calvin Wan and Toon Claes for their suggestions\non the previous versions! The only few changes compared to v7 are:\n\n* The patch series was rebased onto master at 564d0252ca (Git 2.43,\n  2023-11-20). This is to make it stand on a stable base.\n\n* In patch 2/14 (replay: introduce new builtin), there is a synopsys\n  change in the doc, and the corresponding usage message change as\n  suggested by Dscho. This is just about replacing \" # EXPERIMENTAL\"\n  at the end of both the synopsys and usage message with\n  \"(EXPERIMENTAL!) \" at the beginning of them.\n\nCI tests seem to pass according to:\n\nhttps://github.com/chriscool/git/actions/runs/6979770154\n\n(Sorry I am not waiting more than 20 minutes for the 3 last ones to\nfinish.)\n\n# Range-diff between v7 and v8\n\n(A single change was made in patch 2/14, but unfortunately as the\nlines changed in that patch are also changed by other patches later,\nit looks like there are more changes in subsequent patches.)\n\n\n 1:  cddcd967b2 =  1:  18fd9b0d5d t6429: remove switching aspects of fast-rebase\n 2:  c8476fb093 !  2:  fc6bdf4de4 replay: introduce new builtin\n    @@ Documentation/git-replay.txt (new)\n     +SYNOPSIS\n     +--------\n     +[verse]\n    -+'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n    ++(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>\n     +\n     +DESCRIPTION\n     +-----------\n    @@ builtin/replay.c: int cmd__fast_rebase(int argc, const char **argv)\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    -+          printf(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\\n\");\n    ++          printf(\"usage: (EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\\n\");\n                exit(129);\n        }\n      \n 3:  43322abd1e !  3:  e96a66c352 replay: start using parse_options API\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        int ret = 0;\n      \n     -  if (argc == 2 && !strcmp(argv[1], \"-h\")) {\n    --          printf(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\\n\");\n    +-          printf(\"usage: (EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\\n\");\n     -          exit(129);\n     +  const char * const replay_usage[] = {\n    -+          N_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n    ++          N_(\"(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\"),\n     +          NULL\n     +  };\n     +  struct option replay_options[] = {\n 4:  6524c7f045 =  4:  f819d283d9 replay: die() instead of failing assert()\n 5:  05d0efa3cb =  5:  68bbcf9492 replay: introduce pick_regular_commit()\n 6:  c7a5aad3d6 =  6:  72221c647e replay: change rev walking options\n 7:  01f35f924b =  7:  f54d8fce22 replay: add an important FIXME comment about gpg signing\n 8:  1498b24bad =  8:  e50cc22522 replay: remove progress and info output\n 9:  6786fc147b =  9:  0c5ea3d18e replay: remove HEAD related sanity check\n10:  9a24dbb530 = 10:  9fc636fc3d replay: make it a minimal server side command\n11:  ad6ca2fbef ! 11:  2096bcad79 replay: use standard revision ranges\n    @@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new\n      SYNOPSIS\n      --------\n      [verse]\n    --'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n    -+'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n    +-(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>\n    ++(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...\n      \n      DESCRIPTION\n      -----------\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        int ret = 0;\n      \n        const char * const replay_usage[] = {\n    --          N_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n    -+          N_(\"git replay --onto <newbase> <revision-range>... # EXPERIMENTAL\"),\n    +-          N_(\"(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\"),\n    ++          N_(\"(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>...\"),\n                NULL\n        };\n        struct option replay_options[] = {\n12:  081864ed5f ! 12:  d5414806ef replay: add --advance or 'cherry-pick' mode\n    @@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new\n      SYNOPSIS\n      --------\n      [verse]\n    --'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n    -+'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n    +-(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...\n    ++(EXPERIMENTAL!) 'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n      \n      DESCRIPTION\n      -----------\n    @@ builtin/replay.c: static struct commit *pick_regular_commit(struct commit *pickm\n        int ret = 0;\n      \n        const char * const replay_usage[] = {\n    --          N_(\"git replay --onto <newbase> <revision-range>... # EXPERIMENTAL\"),\n    -+          N_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\"),\n    +-          N_(\"(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>...\"),\n    ++          N_(\"(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n                NULL\n        };\n        struct option replay_options[] = {\n13:  19c4016c7c ! 13:  2a3e521c13 replay: add --contained to rebase contained branches\n    @@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new\n      SYNOPSIS\n      --------\n      [verse]\n    --'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n    -+'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n    +-(EXPERIMENTAL!) 'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n    ++(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\n      \n      DESCRIPTION\n      -----------\n    @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n        int ret = 0;\n      \n        const char * const replay_usage[] = {\n    --          N_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\"),\n    -+          N_(\"git replay ([--contained] --onto <newbase> | --advance <branch>) \"\n    -+             \"<revision-range>... # EXPERIMENTAL\"),\n    +-          N_(\"(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n    ++          N_(\"(EXPERIMENTAL!) git replay \"\n    ++             \"([--contained] --onto <newbase> | --advance <branch>) \"\n    ++             \"<revision-range>...\"),\n                NULL\n        };\n        struct option replay_options[] = {\n14:  29556bcc86 = 14:  93e034faee replay: stop assuming replayed branches do not diverge\n\n\nElijah Newren (14):\n  t6429: remove switching aspects of fast-rebase\n  replay: introduce new builtin\n  replay: start using parse_options API\n  replay: die() instead of failing assert()\n  replay: introduce pick_regular_commit()\n  replay: change rev walking options\n  replay: add an important FIXME comment about gpg signing\n  replay: remove progress and info output\n  replay: remove HEAD related sanity check\n  replay: make it a minimal server side command\n  replay: use standard revision ranges\n  replay: add --advance or 'cherry-pick' mode\n  replay: add --contained to rebase contained branches\n  replay: stop assuming replayed branches do not diverge\n\n .gitignore                               |   1 +\n Documentation/git-replay.txt             | 127 +++++++\n Makefile                                 |   2 +-\n builtin.h                                |   1 +\n builtin/replay.c                         | 446 +++++++++++++++++++++++\n command-list.txt                         |   1 +\n git.c                                    |   1 +\n t/helper/test-fast-rebase.c              | 241 ------------\n t/helper/test-tool.c                     |   1 -\n t/helper/test-tool.h                     |   1 -\n t/t3650-replay-basics.sh                 | 198 ++++++++++\n t/t6429-merge-sequence-rename-caching.sh |  45 ++-\n 12 files changed, 801 insertions(+), 264 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n create mode 100644 builtin/replay.c\n delete mode 100644 t/helper/test-fast-rebase.c\n create mode 100755 t/t3650-replay-basics.sh\n\n\nbase-commit: 564d0252ca632e0264ed670534a51d18a689ef5d\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485107","messageId":"20231124111044.3426007-2-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 01/14] t6429: remove switching aspects of fast-rebase","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:30Z","receivedAt":"2023-11-24T11:11:04Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAt the time t6429 was written, merge-ort was still under development,\ndid not have quite as many tests, and certainly was not widely deployed.\nSince t6429 was exercising some codepaths just a little differently, we\nthought having them also test the \"merge_switch_to_result()\" bits of\nmerge-ort was useful even though they weren't intrinsic to the real\npoint of these tests.\n\nHowever, the value provided by doing extra testing of the\n\"merge_switch_to_result()\" bits has decreased a bit over time, and it's\nactively making it harder to refactor `test-tool fast-rebase` into `git\nreplay`, which we are going to do in following commits.  Dispense with\nthese bits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/helper/test-fast-rebase.c              | 9 +--------\n t/t6429-merge-sequence-rename-caching.sh | 9 +++++++--\n 2 files changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c\nindex cac20a72b3..2bfab66b1b 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/t/helper/test-fast-rebase.c\n@@ -194,7 +194,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n-\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\tmerge_finalize(&merge_opt, &result);\n \n \tif (result.clean < 0)\n \t\texit(128);\n@@ -213,9 +213,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n-\n-\t\tprime_cache_tree(the_repository, the_repository->index,\n-\t\t\t\t result.tree);\n \t} else {\n \t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n@@ -228,10 +225,6 @@ int cmd__fast_rebase(int argc, const char **argv)\n \t\t\tdie(\"Failed to update %s\", argv[4]);\n \t\t}\n \t}\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-\n \tret = (result.clean == 0);\n cleanup:\n \tstrbuf_release(&reflog_msg);\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex d02fa16614..75d3fd2dba 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -72,6 +72,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \t\tgit switch upstream &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n@@ -200,6 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -277,6 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n@@ -356,8 +359,6 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n \t\t#git cherry-pick upstream..topic &&\n \n-\t\tgrep CONFLICT..rename/rename output &&\n-\n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n \t)\n@@ -456,6 +457,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -522,6 +524,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -624,6 +627,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n@@ -682,6 +686,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\texport GIT_TRACE2_PERF &&\n \n \t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit reset --hard topic &&\n \t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485109","messageId":"20231124111044.3426007-3-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 02/14] replay: introduce new builtin","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:31Z","receivedAt":"2023-11-24T11:11:06Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor now, this is just a rename from `t/helper/test-fast-rebase.c` into\n`builtin/replay.c` with minimal changes to make it build appropriately.\n\nLet's add a stub documentation and a stub test script though.\n\nSubsequent commits will flesh out the capabilities of the new command\nand make it a more standard regular builtin.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n .gitignore                                    |  1 +\n Documentation/git-replay.txt                  | 39 ++++++++++++\n Makefile                                      |  2 +-\n builtin.h                                     |  1 +\n .../test-fast-rebase.c => builtin/replay.c    | 29 +++------\n command-list.txt                              |  1 +\n git.c                                         |  1 +\n t/helper/test-tool.c                          |  1 -\n t/helper/test-tool.h                          |  1 -\n t/t3650-replay-basics.sh                      | 60 +++++++++++++++++++\n t/t6429-merge-sequence-rename-caching.sh      | 27 +++------\n 11 files changed, 122 insertions(+), 41 deletions(-)\n create mode 100644 Documentation/git-replay.txt\n rename t/helper/test-fast-rebase.c => builtin/replay.c (87%)\n create mode 100755 t/t3650-replay-basics.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 5e56e471b3..612c0f6a0f 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -135,6 +135,7 @@\n /git-remote-ext\n /git-repack\n /git-replace\n+/git-replay\n /git-request-pull\n /git-rerere\n /git-reset\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nnew file mode 100644\nindex 0000000000..2ca7ca5fd8\n--- /dev/null\n+++ b/Documentation/git-replay.txt\n@@ -0,0 +1,39 @@\n+git-replay(1)\n+=============\n+\n+NAME\n+----\n+git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos too\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>\n+\n+DESCRIPTION\n+-----------\n+\n+Takes a range of commits, specified by <oldbase> and <branch>, and\n+replays them onto a new location (see `--onto` option below).\n+\n+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n+\n+OPTIONS\n+-------\n+\n+--onto <newbase>::\n+\tStarting point at which to create the new commits.  May be any\n+\tvalid commit, and not just an existing branch name.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted replay, the exit status is 0.  When\n+the replay has conflicts, the exit status is 1.  If the replay is not\n+able to complete (or start) due to some kind of error, the exit status\n+is something other than 0 or 1.\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 03adcb5a48..3834bc1544 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -799,7 +799,6 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o\n TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n TEST_BUILTINS_OBJS += test-env-helper.o\n TEST_BUILTINS_OBJS += test-example-decorate.o\n-TEST_BUILTINS_OBJS += test-fast-rebase.o\n TEST_BUILTINS_OBJS += test-find-pack.o\n TEST_BUILTINS_OBJS += test-fsmonitor-client.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n@@ -1290,6 +1289,7 @@ BUILTIN_OBJS += builtin/remote-fd.o\n BUILTIN_OBJS += builtin/remote.o\n BUILTIN_OBJS += builtin/repack.o\n BUILTIN_OBJS += builtin/replace.o\n+BUILTIN_OBJS += builtin/replay.o\n BUILTIN_OBJS += builtin/rerere.o\n BUILTIN_OBJS += builtin/reset.o\n BUILTIN_OBJS += builtin/rev-list.o\ndiff --git a/builtin.h b/builtin.h\nindex d560baa661..28280636da 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -211,6 +211,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix);\n int cmd_remote_ext(int argc, const char **argv, const char *prefix);\n int cmd_remote_fd(int argc, const char **argv, const char *prefix);\n int cmd_repack(int argc, const char **argv, const char *prefix);\n+int cmd_replay(int argc, const char **argv, const char *prefix);\n int cmd_rerere(int argc, const char **argv, const char *prefix);\n int cmd_reset(int argc, const char **argv, const char *prefix);\n int cmd_restore(int argc, const char **argv, const char *prefix);\ndiff --git a/t/helper/test-fast-rebase.c b/builtin/replay.c\nsimilarity index 87%\nrename from t/helper/test-fast-rebase.c\nrename to builtin/replay.c\nindex 2bfab66b1b..1998134683 100644\n--- a/t/helper/test-fast-rebase.c\n+++ b/builtin/replay.c\n@@ -1,17 +1,11 @@\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+ * \"git replay\" builtin command\n  */\n \n #define USE_THE_INDEX_VARIABLE\n-#include \"test-tool.h\"\n+#include \"git-compat-util.h\"\n+\n+#include \"builtin.h\"\n #include \"cache-tree.h\"\n #include \"commit.h\"\n #include \"environment.h\"\n@@ -27,7 +21,8 @@\n #include \"sequencer.h\"\n #include \"setup.h\"\n #include \"strvec.h\"\n-#include \"tree.h\"\n+#include <oidset.h>\n+#include <tree.h>\n \n static const char *short_commit_name(struct commit *commit)\n {\n@@ -94,7 +89,7 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n-int cmd__fast_rebase(int argc, const char **argv)\n+int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n@@ -110,14 +105,8 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\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\tprintf(\"usage: (EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\\n\");\n \t\texit(129);\n \t}\n \n@@ -136,7 +125,7 @@ int cmd__fast_rebase(int argc, const char **argv)\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n \n-\trepo_init_revisions(the_repository, &revs, NULL);\n+\trepo_init_revisions(the_repository, &revs, prefix);\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\ndiff --git a/command-list.txt b/command-list.txt\nindex 54b2a50f5f..c4cd0f352b 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -160,6 +160,7 @@ git-reflog                              ancillarymanipulators           complete\n git-remote                              ancillarymanipulators           complete\n git-repack                              ancillarymanipulators           complete\n git-replace                             ancillarymanipulators           complete\n+git-replay                              plumbingmanipulators\n git-request-pull                        foreignscminterface             complete\n git-rerere                              ancillaryinterrogators\n git-reset                               mainporcelain           history\ndiff --git a/git.c b/git.c\nindex c67e44dd82..7068a184b0 100644\n--- a/git.c\n+++ b/git.c\n@@ -594,6 +594,7 @@ static struct cmd_struct commands[] = {\n \t{ \"remote-fd\", cmd_remote_fd, NO_PARSEOPT },\n \t{ \"repack\", cmd_repack, RUN_SETUP },\n \t{ \"replace\", cmd_replace, RUN_SETUP },\n+\t{ \"replay\", cmd_replay, RUN_SETUP },\n \t{ \"rerere\", cmd_rerere, RUN_SETUP },\n \t{ \"reset\", cmd_reset, RUN_SETUP },\n \t{ \"restore\", cmd_restore, RUN_SETUP | NEED_WORK_TREE },\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 876cd2dc31..37ba996539 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -30,7 +30,6 @@ static struct test_cmd cmds[] = {\n \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n \t{ \"env-helper\", cmd__env_helper },\n \t{ \"example-decorate\", cmd__example_decorate },\n-\t{ \"fast-rebase\", cmd__fast_rebase },\n \t{ \"find-pack\", cmd__find_pack },\n \t{ \"fsmonitor-client\", cmd__fsmonitor_client },\n \t{ \"genrandom\", cmd__genrandom },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 70dd4eba11..8a1a7c63da 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -24,7 +24,6 @@ int cmd__dump_untracked_cache(int argc, const char **argv);\n int cmd__dump_reftable(int argc, const char **argv);\n int cmd__env_helper(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__find_pack(int argc, const char **argv);\n int cmd__fsmonitor_client(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nnew file mode 100755\nindex 0000000000..36c1b5082a\n--- /dev/null\n+++ b/t/t3650-replay-basics.sh\n@@ -0,0 +1,60 @@\n+#!/bin/sh\n+\n+test_description='basic git replay tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+GIT_AUTHOR_NAME=author@name\n+GIT_AUTHOR_EMAIL=bogus@email@address\n+export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\n+\tgit switch -c topic1 &&\n+\ttest_commit C &&\n+\tgit switch -c topic2 &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit switch topic1 &&\n+\ttest_commit F &&\n+\tgit switch -c topic3 &&\n+\ttest_commit G &&\n+\ttest_commit H &&\n+\tgit switch -c topic4 main &&\n+\ttest_commit I &&\n+\ttest_commit J &&\n+\n+\tgit switch -c next main &&\n+\ttest_commit K &&\n+\tgit merge -m \"Merge topic1\" topic1 &&\n+\tgit merge -m \"Merge topic2\" topic2 &&\n+\tgit merge -m \"Merge topic3\" topic3 &&\n+\t>evil &&\n+\tgit add evil &&\n+\tgit commit --amend &&\n+\tgit merge -m \"Merge topic4\" topic4 &&\n+\n+\tgit switch main &&\n+\ttest_commit L &&\n+\ttest_commit M &&\n+\n+\tgit switch -c conflict B &&\n+\ttest_commit C.conflict C.t conflict\n+'\n+\n+test_expect_success 'using replay to rebase two branches, one on top of other' '\n+\tgit switch main &&\n+\n+\tgit replay --onto main topic1 topic2 >result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 75d3fd2dba..7670b72008 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,9 +71,8 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -141,8 +140,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -200,9 +198,8 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -278,9 +275,8 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream~1..topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -356,8 +352,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&\n-\t\t#git cherry-pick upstream..topic &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,9 +451,8 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -523,9 +517,8 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -626,9 +619,8 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -685,9 +677,8 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest-tool fast-rebase --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic &&\n \t\tgit reset --hard topic &&\n-\t\t#git cherry-pick upstream..topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485110","messageId":"20231124111044.3426007-4-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 03/14] replay: start using parse_options API","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:32Z","receivedAt":"2023-11-24T11:11:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of manually parsing arguments, let's start using the parse_options\nAPI. This way this new builtin will look more standard, and in some\nupcoming commits will more easily be able to handle more command line\noptions.\n\nNote that we plan to later use standard revision ranges instead of\nhardcoded \"<oldbase> <branch>\" arguments. When we will use standard\nrevision ranges, it will be easier to check if there are no spurious\narguments if we keep ARGV[0], so let's call parse_options() with\nPARSE_OPT_KEEP_ARGV0 even if we don't need ARGV[0] right now to avoid\nsome useless code churn.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 45 ++++++++++++++++++++++++++++++++-------------\n 1 file changed, 32 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 1998134683..7998f6ed04 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -15,7 +15,7 @@\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n #include \"object-name.h\"\n-#include \"read-cache-ll.h\"\n+#include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n #include \"sequencer.h\"\n@@ -92,6 +92,7 @@ static struct commit *create_commit(struct tree *tree,\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n+\tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n \tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n@@ -105,16 +106,32 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n-\tif (argc == 2 && !strcmp(argv[1], \"-h\")) {\n-\t\tprintf(\"usage: (EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\\n\");\n-\t\texit(129);\n+\tconst char * const replay_usage[] = {\n+\t\tN_(\"(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tNULL\n+\t};\n+\tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"onto\", &onto_name,\n+\t\t\t   N_(\"revision\"),\n+\t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n+\t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n+\n+\tif (!onto_name) {\n+\t\terror(_(\"option --onto is mandatory\"));\n+\t\tusage_with_options(replay_usage, replay_options);\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+\tif (argc != 3) {\n+\t\terror(_(\"bad number of arguments\"));\n+\t\tusage_with_options(replay_usage, replay_options);\n+\t}\n \n-\tonto = peel_committish(argv[2]);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\tonto = peel_committish(onto_name);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \t/* Sanity check */\n \tif (repo_get_oid(the_repository, \"HEAD\", &head))\n@@ -126,6 +143,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tBUG(\"Could not read index\");\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n+\n \trevs.verbose_header = 1;\n \trevs.max_parents = 1;\n \trevs.cherry_mark = 1;\n@@ -134,7 +152,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n@@ -197,8 +216,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &last_picked_commit->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -210,8 +229,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t       &last_commit->object.oid,\n \t\t\t       &head,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[4]);\n-\t\t\tdie(\"Failed to update %s\", argv[4]);\n+\t\t\terror(_(\"could not update %s\"), argv[2]);\n+\t\t\tdie(\"Failed to update %s\", argv[2]);\n \t\t}\n \t}\n \tret = (result.clean == 0);\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485111","messageId":"20231124111044.3426007-5-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 04/14] replay: die() instead of failing assert()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:33Z","receivedAt":"2023-11-24T11:11:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIt's not a good idea for regular Git commands to use an assert() to\ncheck for things that could happen but are not supported.\n\nLet's die() with an explanation of the issue instead.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 7998f6ed04..f48c5ed255 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -179,7 +179,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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+\n+\t\tif (!commit->parents)\n+\t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n+\t\tif (commit->parents->next)\n+\t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n+\n \t\tbase = commit->parents->item;\n \n \t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485112","messageId":"20231124111044.3426007-6-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 05/14] replay: introduce pick_regular_commit()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:34Z","receivedAt":"2023-11-24T11:11:10Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's refactor the code to handle a regular commit (a commit that is\nneither a root commit nor a merge commit) into a single function instead\nof keeping it inside cmd_replay().\n\nThis is good for separation of concerns, and this will help further work\nin the future to replay merge commits.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 54 ++++++++++++++++++++++++++++++------------------\n 1 file changed, 34 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex f48c5ed255..d039467cd4 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -89,6 +89,35 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+static struct commit *pick_regular_commit(struct commit *pickme,\n+\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  struct merge_options *merge_opt,\n+\t\t\t\t\t  struct merge_result *result)\n+{\n+\tstruct commit *base;\n+\tstruct tree *pickme_tree, *base_tree;\n+\n+\tbase = pickme->parents->item;\n+\n+\tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n+\tbase_tree = repo_get_commit_tree(the_repository, base);\n+\n+\tmerge_opt->branch2 = short_commit_name(pickme);\n+\tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n+\n+\tmerge_incore_nonrecursive(merge_opt,\n+\t\t\t\t  base_tree,\n+\t\t\t\t  result->tree,\n+\t\t\t\t  pickme_tree,\n+\t\t\t\t  result);\n+\n+\tfree((char*)merge_opt->ancestor);\n+\tmerge_opt->ancestor = NULL;\n+\tif (!result->clean)\n+\t\treturn NULL;\n+\treturn create_commit(result->tree, pickme, last_commit);\n+}\n+\n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n@@ -100,7 +129,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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 tree *head_tree;\n \tstruct merge_result result;\n \tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n@@ -175,7 +204,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tresult.tree = head_tree;\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *base;\n+\t\tstruct commit *pick;\n \n \t\tfprintf(stderr, \"Rebasing %s...\\r\",\n \t\t\toid_to_hex(&commit->object.oid));\n@@ -185,26 +214,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tbase = commit->parents->item;\n-\n-\t\tnext_tree = repo_get_commit_tree(the_repository, commit);\n-\t\tbase_tree = repo_get_commit_tree(the_repository, 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\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!pick)\n \t\t\tbreak;\n+\t\tlast_commit = pick;\n \t\tlast_picked_commit = commit;\n-\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485113","messageId":"20231124111044.3426007-7-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 06/14] replay: change rev walking options","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:35Z","receivedAt":"2023-11-24T11:11:11Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's force the rev walking options we need after calling\nsetup_revisions() instead of before.\n\nThis might override some user supplied rev walking command line options\nthough. So let's detect that and warn users by:\n\n  a) setting the desired values, before setup_revisions(),\n  b) checking after setup_revisions() whether these values differ from\n     the desired values,\n  c) if so throwing a warning and setting the desired values again.\n\nWe want the command to work from older commits to newer ones by default.\nAlso we don't want history simplification, as we want to deal with all\nthe commits in the affected range.\n\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 48 +++++++++++++++++++++++++++++++++++++++++-------\n 1 file changed, 41 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex d039467cd4..2f664218be 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -173,22 +173,56 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n-\trevs.verbose_header = 1;\n-\trevs.max_parents = 1;\n-\trevs.cherry_mark = 1;\n-\trevs.limited = 1;\n+\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n+\n+\t/*\n+\t * Set desired values for rev walking options here. If they\n+\t * are changed by some user specified option in setup_revisions()\n+\t * below, we will detect that below and then warn.\n+\t *\n+\t * TODO: In the future we might want to either die(), or allow\n+\t * some options changing these values if we think they could\n+\t * be useful.\n+\t */\n \trevs.reverse = 1;\n-\trevs.right_only = 1;\n \trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n \trevs.topo_order = 1;\n-\n-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n+\trevs.simplify_history = 0;\n \n \tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n \t\tret = error(_(\"unhandled options\"));\n \t\tgoto cleanup;\n \t}\n \n+\t/*\n+\t * Detect and warn if we override some user specified rev\n+\t * walking options.\n+\t */\n+\tif (revs.reverse != 1) {\n+\t\twarning(_(\"some rev walking options will be overridden as \"\n+\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n+\t\t\t\"reverse\");\n+\t\trevs.reverse = 1;\n+\t}\n+\tif (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {\n+\t\twarning(_(\"some rev walking options will be overridden as \"\n+\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n+\t\t\t\"sort_order\");\n+\t\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\t}\n+\tif (revs.topo_order != 1) {\n+\t\twarning(_(\"some rev walking options will be overridden as \"\n+\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n+\t\t\t\"topo_order\");\n+\t\trevs.topo_order = 1;\n+\t}\n+\tif (revs.simplify_history != 0) {\n+\t\twarning(_(\"some rev walking options will be overridden as \"\n+\t\t\t  \"'%s' bit in 'struct rev_info' will be forced\"),\n+\t\t\t\"simplify_history\");\n+\t\trevs.simplify_history = 0;\n+\t}\n+\n \tstrvec_clear(&rev_walk_args);\n \n \tif (prepare_revision_walk(&revs) < 0) {\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485114","messageId":"20231124111044.3426007-8-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 07/14] replay: add an important FIXME comment about gpg signing","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:36Z","receivedAt":"2023-11-24T11:11:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want to be able to handle signed commits in some way in the future,\nbut we are not ready to do it now. So for the time being let's just add\na FIXME comment to remind us about it.\n\nThese are different ways we could handle them:\n\n  - in case of a cli user and if there was an interactive mode, we could\n    perhaps ask if the user wants to sign again\n  - we could add an option to just fail if there are signed commits\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 2f664218be..384bb4ddd3 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -62,7 +62,7 @@ static struct commit *create_commit(struct tree *tree,\n \tstruct object *obj;\n \tstruct commit_list *parents = NULL;\n \tchar *author;\n-\tchar *sign_commit = NULL;\n+\tchar *sign_commit = NULL; /* FIXME: cli users might want to sign again */\n \tstruct commit_extra_header *extra;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *out_enc = get_commit_output_encoding();\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485115","messageId":"20231124111044.3426007-9-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 08/14] replay: remove progress and info output","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:37Z","receivedAt":"2023-11-24T11:11:14Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command will be changed in a follow up commit, so that it\nwill not update refs directly, but instead it will print on stdout a\nlist of commands that can be consumed by `git update-ref --stdin`.\n\nWe don't want this output to be polluted by its current low value\noutput, so let's just remove the latter.\n\nIn the future, when the command gets an option to update refs by\nitself, it will make a lot of sense to display a progress meter, but\nwe are not there yet.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c | 7 +------\n 1 file changed, 1 insertion(+), 6 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 384bb4ddd3..ca3867dc57 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -232,7 +232,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\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.show_rename_progress = 0;\n \tmerge_opt.branch1 = \"HEAD\";\n \thead_tree = repo_get_commit_tree(the_repository, onto);\n \tresult.tree = head_tree;\n@@ -240,9 +240,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \twhile ((commit = get_revision(&revs))) {\n \t\tstruct commit *pick;\n \n-\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n-\t\t\toid_to_hex(&commit->object.oid));\n-\n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n@@ -261,7 +258,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\texit(128);\n \n \tif (result.clean) {\n-\t\tfprintf(stderr, \"\\nDone.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n \t\t\t    oid_to_hex(&last_commit->object.oid));\n@@ -275,7 +271,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t} else {\n-\t\tfprintf(stderr, \"\\nAborting: Hit a conflict.\\n\");\n \t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485116","messageId":"20231124111044.3426007-10-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 09/14] replay: remove HEAD related sanity check","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:38Z","receivedAt":"2023-11-24T11:11:16Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want replay to be a command that can be used on the server side on\nany branch, not just the current one, so we are going to stop updating\nHEAD in a future commit.\n\nA \"sanity check\" that makes sure we are replaying the current branch\ndoesn't make sense anymore. Let's remove it.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 8 +-------\n t/t3650-replay-basics.sh | 2 --\n 2 files changed, 1 insertion(+), 9 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex ca3867dc57..bdec2f2b97 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -123,7 +123,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct object_id head;\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct strvec rev_walk_args = STRVEC_INIT;\n \tstruct rev_info revs;\n@@ -162,11 +161,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\t/* Sanity check */\n-\tif (repo_get_oid(the_repository, \"HEAD\", &head))\n-\t\tdie(_(\"Cannot read HEAD\"));\n-\tassert(oideq(&onto->object.oid, &head));\n-\n \trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n \tif (repo_read_index(the_repository) < 0)\n \t\tBUG(\"Could not read index\");\n@@ -275,7 +269,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n \t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n \t\t\t       &last_commit->object.oid,\n-\t\t\t       &head,\n+\t\t\t       &onto->object.oid,\n \t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n \t\t\terror(_(\"could not update %s\"), argv[2]);\n \t\t\tdie(\"Failed to update %s\", argv[2]);\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 36c1b5082a..b5b9f9ade2 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -48,8 +48,6 @@ test_expect_success 'setup' '\n '\n \n test_expect_success 'using replay to rebase two branches, one on top of other' '\n-\tgit switch main &&\n-\n \tgit replay --onto main topic1 topic2 >result &&\n \n \tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485117","messageId":"20231124111044.3426007-11-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 10/14] replay: make it a minimal server side command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:39Z","receivedAt":"2023-11-24T11:11:18Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe want this command to be a minimal command that just does server side\npicking of commits, displaying the results on stdout for higher level\nscripts to consume.\n\nSo let's simplify it:\n  * remove the worktree and index reading/writing,\n  * remove the ref (and reflog) updating,\n  * remove the assumptions tying us to HEAD, since (a) this is not a\n    rebase and (b) we want to be able to pick commits in a bare repo,\n    i.e. to/from branches that are not checked out and not the main\n    branch,\n  * remove unneeded includes,\n  * handle rebasing multiple branches by printing on stdout the update\n    ref commands that should be performed.\n\nThe output can be piped into `git update-ref --stdin` for the ref\nupdates to happen.\n\nIn the future to make it easier for users to use this command\ndirectly maybe an option can be added to automatically pipe its output\ninto `git update-ref`.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             |  5 +-\n builtin/replay.c                         | 78 ++++++++----------------\n t/t3650-replay-basics.sh                 | 19 +++++-\n t/t6429-merge-sequence-rename-caching.sh | 39 +++++++-----\n 4 files changed, 72 insertions(+), 69 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 2ca7ca5fd8..267282d92a 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -15,7 +15,10 @@ DESCRIPTION\n -----------\n \n Takes a range of commits, specified by <oldbase> and <branch>, and\n-replays them onto a new location (see `--onto` option below).\n+replays them onto a new location (see `--onto` option below). Leaves\n+the working tree and the index untouched, and updates no references.\n+The output of this command is meant to be used as input to\n+`git update-ref --stdin`, which would update the relevant branches.\n \n THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n \ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex bdec2f2b97..bfccbbbfea 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -6,11 +6,7 @@\n #include \"git-compat-util.h\"\n \n #include \"builtin.h\"\n-#include \"cache-tree.h\"\n-#include \"commit.h\"\n #include \"environment.h\"\n-#include \"gettext.h\"\n-#include \"hash.h\"\n #include \"hex.h\"\n #include \"lockfile.h\"\n #include \"merge-ort.h\"\n@@ -18,8 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"sequencer.h\"\n-#include \"setup.h\"\n #include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n@@ -102,6 +96,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n+\tmerge_opt->branch1 = short_commit_name(last_commit);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -122,15 +117,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n-\tstruct lock_file lock = LOCK_INIT;\n+\tstruct commit *last_commit = NULL;\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 *head_tree;\n \tstruct merge_result result;\n-\tstruct strbuf reflog_msg = STRBUF_INIT;\n \tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n@@ -161,10 +153,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tonto = peel_committish(onto_name);\n \tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index(the_repository) < 0)\n-\t\tBUG(\"Could not read index\");\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n@@ -227,58 +215,44 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\tmerge_opt.branch1 = \"HEAD\";\n-\thead_tree = repo_get_commit_tree(the_repository, onto);\n-\tresult.tree = head_tree;\n+\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n-\t\tstruct commit *pick;\n+\t\tconst struct name_decoration *decoration;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tpick = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n-\t\tif (!pick)\n+\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tif (!last_commit)\n \t\t\tbreak;\n-\t\tlast_commit = pick;\n-\t\tlast_picked_commit = commit;\n+\n+\t\tdecoration = get_name_decoration(&commit->object);\n+\t\tif (!decoration)\n+\t\t\tcontinue;\n+\n+\t\twhile (decoration) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\t\tprintf(\"update %s %s %s\\n\",\n+\t\t\t\t       decoration->name,\n+\t\t\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t\t\t       oid_to_hex(&commit->object.oid));\n+\t\t\t}\n+\t\t\tdecoration = decoration->next;\n+\t\t}\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n+\tret = result.clean;\n \n-\tif (result.clean < 0)\n-\t\texit(128);\n-\n-\tif (result.clean) {\n-\t\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid),\n-\t\t\t    oid_to_hex(&last_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, branch_name.buf,\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &last_picked_commit->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n-\t\t\tdie(_(\"unable to update HEAD\"));\n-\t} else {\n-\t\tstrbuf_addf(&reflog_msg, \"rebase progress up to %s\",\n-\t\t\t    oid_to_hex(&last_picked_commit->object.oid));\n-\t\tif (update_ref(reflog_msg.buf, \"HEAD\",\n-\t\t\t       &last_commit->object.oid,\n-\t\t\t       &onto->object.oid,\n-\t\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n-\t\t\terror(_(\"could not update %s\"), argv[2]);\n-\t\t\tdie(\"Failed to update %s\", argv[2]);\n-\t\t}\n-\t}\n-\tret = (result.clean == 0);\n cleanup:\n-\tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n-\treturn ret;\n+\n+\t/* Return */\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret ? 0 : 1;\n }\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex b5b9f9ade2..3567c98362 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -47,12 +47,29 @@ test_expect_success 'setup' '\n \ttest_commit C.conflict C.t conflict\n '\n \n+test_expect_success 'setup bare' '\n+\tgit clone --bare . bare\n+'\n+\n test_expect_success 'using replay to rebase two branches, one on top of other' '\n \tgit replay --onto main topic1 topic2 >result &&\n \n+\ttest_line_count = 1 result &&\n+\n \tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n \ttest_write_lines E D M L B A >expect &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n+\tgit -C bare replay --onto main topic1 topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n '\n \n test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 7670b72008..099aefeffc 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,8 +71,9 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked-files &&\n \t\ttest_line_count = 2 tracked-files &&\n@@ -140,7 +141,9 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls\n@@ -198,8 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 2 tracked &&\n@@ -275,8 +279,9 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgit ls-files >tracked &&\n \t\ttest_line_count = 4 tracked &&\n@@ -451,8 +456,9 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n@@ -517,8 +523,9 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 3 calls &&\n@@ -619,8 +626,9 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 1 calls &&\n@@ -677,8 +685,9 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic &&\n-\t\tgit reset --hard topic &&\n+\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit update-ref --stdin <out &&\n+\t\tgit checkout topic &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls &&\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485118","messageId":"20231124111044.3426007-12-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 11/14] replay: use standard revision ranges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:40Z","receivedAt":"2023-11-24T11:11:19Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nInstead of the fixed \"<oldbase> <branch>\" arguments, the replay\ncommand now accepts \"<revision-range>...\" arguments in a similar\nway as many other Git commands. This makes its interface more\nstandard and more flexible.\n\nThis also enables many revision related options accepted and\neaten by setup_revisions(). If the replay command was a high level\none or had a high level mode, it would make sense to restrict some\nof the possible options, like those generating non-contiguous\nhistory, as they could be confusing for most users.\n\nAlso as the interface of the command is now mostly finalized,\nwe can add more documentation and more testcases to make sure\nthe command will continue to work as designed in the future.\n\nWe only document the rev-list related options among all the\nrevision related options that are now accepted, as the rev-list\nrelated ones are probably the most useful for now.\n\nHelped-by: Dragan Simic <dsimic@manjaro.org>\nHelped-by: Linus Arver <linusa@google.com>\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt             | 58 ++++++++++++++++++++++--\n builtin/replay.c                         | 21 ++-------\n t/t3650-replay-basics.sh                 | 12 ++++-\n t/t6429-merge-sequence-rename-caching.sh | 18 ++++----\n 4 files changed, 77 insertions(+), 32 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex 267282d92a..f7b232caa2 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,16 +9,16 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n SYNOPSIS\n --------\n [verse]\n-(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>\n+(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...\n \n DESCRIPTION\n -----------\n \n-Takes a range of commits, specified by <oldbase> and <branch>, and\n-replays them onto a new location (see `--onto` option below). Leaves\n+Takes ranges of commits and replays them onto a new location. Leaves\n the working tree and the index untouched, and updates no references.\n The output of this command is meant to be used as input to\n-`git update-ref --stdin`, which would update the relevant branches.\n+`git update-ref --stdin`, which would update the relevant branches\n+(see the OUTPUT section below).\n \n THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.\n \n@@ -28,6 +28,30 @@ OPTIONS\n --onto <newbase>::\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n++\n+The update-ref command(s) in the output will update the branch(es) in\n+the revision range to point at the new commits, similar to the way how\n+`git rebase --update-refs` updates multiple branches in the affected\n+range.\n+\n+<revision-range>::\n+\tRange of commits to replay; see \"Specifying Ranges\" in\n+\tlinkgit:git-rev-parse and the \"Commit Limiting\" options below.\n+\n+include::rev-list-options.txt[]\n+\n+OUTPUT\n+------\n+\n+When there are no conflicts, the output of this command is usable as\n+input to `git update-ref --stdin`.  It is of the form:\n+\n+\tupdate refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+\tupdate refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+\tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+\n+where the number of refs updated depends on the arguments passed and\n+the shape of the history being replayed.\n \n EXIT STATUS\n -----------\n@@ -37,6 +61,32 @@ the replay has conflicts, the exit status is 1.  If the replay is not\n able to complete (or start) due to some kind of error, the exit status\n is something other than 0 or 1.\n \n+EXAMPLES\n+--------\n+\n+To simply rebase `mybranch` onto `target`:\n+\n+------------\n+$ git replay --onto target origin/main..mybranch\n+update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n+------------\n+\n+When calling `git replay`, one does not need to specify a range of\n+commits to replay using the syntax `A..B`; any range expression will\n+do:\n+\n+------------\n+$ git replay --onto origin/main ^base branch1 branch2 branch3\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n+------------\n+\n+This will simultaneously rebase `branch1`, `branch2`, and `branch3`,\n+all commits they have since `base`, playing them on top of\n+`origin/main`. These three branches may have commits on top of `base`\n+that they have in common, but that does not need to be the case.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex bfccbbbfea..3d5e00147b 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,7 +14,6 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n-#include \"strvec.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -118,16 +117,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct commit *onto;\n \tconst char *onto_name = NULL;\n \tstruct commit *last_commit = NULL;\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 merge_result result;\n-\tstruct strbuf branch_name = STRBUF_INIT;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\"),\n+\t\tN_(\"(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -145,18 +142,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tif (argc != 3) {\n-\t\terror(_(\"bad number of arguments\"));\n-\t\tusage_with_options(replay_usage, replay_options);\n-\t}\n-\n \tonto = peel_committish(onto_name);\n-\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[2]);\n \n \trepo_init_revisions(the_repository, &revs, prefix);\n \n-\tstrvec_pushl(&rev_walk_args, \"\", argv[2], \"--not\", argv[1], NULL);\n-\n \t/*\n \t * Set desired values for rev walking options here. If they\n \t * are changed by some user specified option in setup_revisions()\n@@ -171,8 +160,9 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \trevs.topo_order = 1;\n \trevs.simplify_history = 0;\n \n-\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {\n-\t\tret = error(_(\"unhandled options\"));\n+\targc = setup_revisions(argc, argv, &revs, NULL);\n+\tif (argc > 1) {\n+\t\tret = error(_(\"unrecognized argument: %s\"), argv[1]);\n \t\tgoto cleanup;\n \t}\n \n@@ -205,8 +195,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\trevs.simplify_history = 0;\n \t}\n \n-\tstrvec_clear(&rev_walk_args);\n-\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -248,7 +236,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tret = result.clean;\n \n cleanup:\n-\tstrbuf_release(&branch_name);\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 3567c98362..a1da4f9ef9 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -52,7 +52,7 @@ test_expect_success 'setup bare' '\n '\n \n test_expect_success 'using replay to rebase two branches, one on top of other' '\n-\tgit replay --onto main topic1 topic2 >result &&\n+\tgit replay --onto main topic1..topic2 >result &&\n \n \ttest_line_count = 1 result &&\n \n@@ -68,8 +68,16 @@ test_expect_success 'using replay to rebase two branches, one on top of other' '\n '\n \n test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '\n-\tgit -C bare replay --onto main topic1 topic2 >result-bare &&\n+\tgit -C bare replay --onto main topic1..topic2 >result-bare &&\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to rebase with a conflict' '\n+\ttest_expect_code 1 git replay --onto topic1 B..conflict\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase with a conflict' '\n+\ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n+'\n+\n test_done\ndiff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh\nindex 099aefeffc..0f39ed0d08 100755\n--- a/t/t6429-merge-sequence-rename-caching.sh\n+++ b/t/t6429-merge-sequence-rename-caching.sh\n@@ -71,7 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '\n \n \t\tgit switch upstream &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -201,7 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -279,7 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -357,7 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\ttest_must_fail git replay --onto HEAD upstream~1 topic >output &&\n+\t\ttest_must_fail git replay --onto HEAD upstream~1..topic >output &&\n \n \t\tgrep region_enter.*diffcore_rename trace.output >calls &&\n \t\ttest_line_count = 2 calls\n@@ -456,7 +456,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -523,7 +523,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -626,7 +626,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n@@ -685,7 +685,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '\n \t\tGIT_TRACE2_PERF=\"$(pwd)/trace.output\" &&\n \t\texport GIT_TRACE2_PERF &&\n \n-\t\tgit replay --onto HEAD upstream~1 topic >out &&\n+\t\tgit replay --onto HEAD upstream~1..topic >out &&\n \t\tgit update-ref --stdin <out &&\n \t\tgit checkout topic &&\n \n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485119","messageId":"20231124111044.3426007-13-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 12/14] replay: add --advance or 'cherry-pick' mode","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:41Z","receivedAt":"2023-11-24T11:11:21Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere is already a 'rebase' mode with `--onto`. Let's add an 'advance' or\n'cherry-pick' mode with `--advance`. This new mode will make the target\nbranch advance as we replay commits onto it.\n\nThe replayed commits should have a single tip, so that it's clear where\nthe target branch should be advanced. If they have more than one tip,\nthis new mode will error out.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt |  41 ++++++--\n builtin/replay.c             | 185 +++++++++++++++++++++++++++++++++--\n t/t3650-replay-basics.sh     |  34 +++++++\n 3 files changed, 243 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex f7b232caa2..c4c64f955a 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n SYNOPSIS\n --------\n [verse]\n-(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...\n+(EXPERIMENTAL!) 'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -29,14 +29,25 @@ OPTIONS\n \tStarting point at which to create the new commits.  May be any\n \tvalid commit, and not just an existing branch name.\n +\n-The update-ref command(s) in the output will update the branch(es) in\n-the revision range to point at the new commits, similar to the way how\n-`git rebase --update-refs` updates multiple branches in the affected\n-range.\n+When `--onto` is specified, the update-ref command(s) in the output will\n+update the branch(es) in the revision range to point at the new\n+commits, similar to the way how `git rebase --update-refs` updates\n+multiple branches in the affected range.\n+\n+--advance <branch>::\n+\tStarting point at which to create the new commits; must be a\n+\tbranch name.\n++\n+When `--advance` is specified, the update-ref command(s) in the output\n+will update the branch passed as an argument to `--advance` to point at\n+the new commits (in other words, this mimics a cherry-pick operation).\n \n <revision-range>::\n-\tRange of commits to replay; see \"Specifying Ranges\" in\n-\tlinkgit:git-rev-parse and the \"Commit Limiting\" options below.\n+\tRange of commits to replay. More than one <revision-range> can\n+\tbe passed, but in `--advance <branch>` mode, they should have\n+\ta single tip, so that it's clear where <branch> should point\n+\tto. See \"Specifying Ranges\" in linkgit:git-rev-parse and the\n+\t\"Commit Limiting\" options below.\n \n include::rev-list-options.txt[]\n \n@@ -51,7 +62,9 @@ input to `git update-ref --stdin`.  It is of the form:\n \tupdate refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}\n \n where the number of refs updated depends on the arguments passed and\n-the shape of the history being replayed.\n+the shape of the history being replayed.  When using `--advance`, the\n+number of refs updated is always one, but for `--onto`, it can be one\n+or more (rebasing multiple branches simultaneously is supported).\n \n EXIT STATUS\n -----------\n@@ -71,6 +84,18 @@ $ git replay --onto target origin/main..mybranch\n update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}\n ------------\n \n+To cherry-pick the commits from mybranch onto target:\n+\n+------------\n+$ git replay --advance target origin/main..mybranch\n+update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH}\n+------------\n+\n+Note that the first two examples replay the exact same commits and on\n+top of the exact same new base, they only differ in that the first\n+provides instructions to make mybranch point at the new commits and\n+the second provides instructions to make target point at them.\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 3d5e00147b..f26806d7e2 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -14,6 +14,7 @@\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"strmap.h\"\n #include <oidset.h>\n #include <tree.h>\n \n@@ -82,6 +83,146 @@ static struct commit *create_commit(struct tree *tree,\n \treturn (struct commit *)obj;\n }\n \n+struct ref_info {\n+\tstruct commit *onto;\n+\tstruct strset positive_refs;\n+\tstruct strset negative_refs;\n+\tint positive_refexprs;\n+\tint negative_refexprs;\n+};\n+\n+static void get_ref_information(struct rev_cmdline_info *cmd_info,\n+\t\t\t\tstruct ref_info *ref_info)\n+{\n+\tint i;\n+\n+\tref_info->onto = NULL;\n+\tstrset_init(&ref_info->positive_refs);\n+\tstrset_init(&ref_info->negative_refs);\n+\tref_info->positive_refexprs = 0;\n+\tref_info->negative_refexprs = 0;\n+\n+\t/*\n+\t * When the user specifies e.g.\n+\t *   git replay origin/main..mybranch\n+\t *   git replay ^origin/next mybranch1 mybranch2\n+\t * we want to be able to determine where to replay the commits.  In\n+\t * these examples, the branches are probably based on an old version\n+\t * of either origin/main or origin/next, so we want to replay on the\n+\t * newest version of that branch.  In contrast we would want to error\n+\t * out if they ran\n+\t *   git replay ^origin/master ^origin/next mybranch\n+\t *   git replay mybranch~2..mybranch\n+\t * the first of those because there's no unique base to choose, and\n+\t * the second because they'd likely just be replaying commits on top\n+\t * of the same commit and not making any difference.\n+\t */\n+\tfor (i = 0; i < cmd_info->nr; i++) {\n+\t\tstruct rev_cmdline_entry *e = cmd_info->rev + i;\n+\t\tstruct object_id oid;\n+\t\tconst char *refexpr = e->name;\n+\t\tchar *fullname = NULL;\n+\t\tint can_uniquely_dwim = 1;\n+\n+\t\tif (*refexpr == '^')\n+\t\t\trefexpr++;\n+\t\tif (repo_dwim_ref(the_repository, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)\n+\t\t\tcan_uniquely_dwim = 0;\n+\n+\t\tif (e->flags & BOTTOM) {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->negative_refs, fullname);\n+\t\t\tif (!ref_info->negative_refexprs)\n+\t\t\t\tref_info->onto = lookup_commit_reference_gently(the_repository,\n+\t\t\t\t\t\t\t\t\t\t&e->item->oid, 1);\n+\t\t\tref_info->negative_refexprs++;\n+\t\t} else {\n+\t\t\tif (can_uniquely_dwim)\n+\t\t\t\tstrset_add(&ref_info->positive_refs, fullname);\n+\t\t\tref_info->positive_refexprs++;\n+\t\t}\n+\n+\t\tfree(fullname);\n+\t}\n+}\n+\n+static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n+\t\t\t\t  const char *onto_name,\n+\t\t\t\t  const char **advance_name,\n+\t\t\t\t  struct commit **onto,\n+\t\t\t\t  struct strset **update_refs)\n+{\n+\tstruct ref_info rinfo;\n+\n+\tget_ref_information(cmd_info, &rinfo);\n+\tif (!rinfo.positive_refexprs)\n+\t\tdie(_(\"need some commits to replay\"));\n+\tif (onto_name && *advance_name)\n+\t\tdie(_(\"--onto and --advance are incompatible\"));\n+\telse if (onto_name) {\n+\t\t*onto = peel_committish(onto_name);\n+\t\tif (rinfo.positive_refexprs <\n+\t\t    strset_get_size(&rinfo.positive_refs))\n+\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t} else if (*advance_name) {\n+\t\tstruct object_id oid;\n+\t\tchar *fullname = NULL;\n+\n+\t\t*onto = peel_committish(*advance_name);\n+\t\tif (repo_dwim_ref(the_repository, *advance_name, strlen(*advance_name),\n+\t\t\t     &oid, &fullname, 0) == 1) {\n+\t\t\t*advance_name = fullname;\n+\t\t} else {\n+\t\t\tdie(_(\"argument to --advance must be a reference\"));\n+\t\t}\n+\t\tif (rinfo.positive_refexprs > 1)\n+\t\t\tdie(_(\"cannot advance target with multiple sources because ordering would be ill-defined\"));\n+\t} else {\n+\t\tint positive_refs_complete = (\n+\t\t\trinfo.positive_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.positive_refs));\n+\t\tint negative_refs_complete = (\n+\t\t\trinfo.negative_refexprs ==\n+\t\t\tstrset_get_size(&rinfo.negative_refs));\n+\t\t/*\n+\t\t * We need either positive_refs_complete or\n+\t\t * negative_refs_complete, but not both.\n+\t\t */\n+\t\tif (rinfo.negative_refexprs > 0 &&\n+\t\t    positive_refs_complete == negative_refs_complete)\n+\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\tif (negative_refs_complete) {\n+\t\t\tstruct hashmap_iter iter;\n+\t\t\tstruct strmap_entry *entry;\n+\n+\t\t\tif (rinfo.negative_refexprs == 0)\n+\t\t\t\tdie(_(\"all positive revisions given must be references\"));\n+\t\t\telse if (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine whether this is an --advance or --onto operation\"));\n+\t\t\telse if (rinfo.positive_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot advance target with multiple source branches because ordering would be ill-defined\"));\n+\n+\t\t\t/* Only one entry, but we have to loop to get it */\n+\t\t\tstrset_for_each_entry(&rinfo.negative_refs,\n+\t\t\t\t\t      &iter, entry) {\n+\t\t\t\t*advance_name = entry->key;\n+\t\t\t}\n+\t\t} else { /* positive_refs_complete */\n+\t\t\tif (rinfo.negative_refexprs > 1)\n+\t\t\t\tdie(_(\"cannot implicitly determine correct base for --onto\"));\n+\t\t\tif (rinfo.negative_refexprs == 1)\n+\t\t\t\t*onto = rinfo.onto;\n+\t\t}\n+\t}\n+\tif (!*advance_name) {\n+\t\t*update_refs = xcalloc(1, sizeof(**update_refs));\n+\t\t**update_refs = rinfo.positive_refs;\n+\t\tmemset(&rinfo.positive_refs, 0, sizeof(**update_refs));\n+\t}\n+\tstrset_clear(&rinfo.negative_refs);\n+\tstrset_clear(&rinfo.positive_refs);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n \t\t\t\t\t  struct commit *last_commit,\n \t\t\t\t\t  struct merge_options *merge_opt,\n@@ -114,20 +255,26 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n {\n-\tstruct commit *onto;\n+\tconst char *advance_name = NULL;\n+\tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n-\tstruct commit *last_commit = NULL;\n+\n \tstruct rev_info revs;\n+\tstruct commit *last_commit = NULL;\n \tstruct commit *commit;\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n+\tstruct strset *update_refs = NULL;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>...\"),\n+\t\tN_(\"(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n+\t\tOPT_STRING(0, \"advance\", &advance_name,\n+\t\t\t   N_(\"branch\"),\n+\t\t\t   N_(\"make replay advance given branch\")),\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n@@ -137,13 +284,11 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, replay_options, replay_usage,\n \t\t\t     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);\n \n-\tif (!onto_name) {\n-\t\terror(_(\"option --onto is mandatory\"));\n+\tif (!onto_name && !advance_name) {\n+\t\terror(_(\"option --onto or --advance is mandatory\"));\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n-\tonto = peel_committish(onto_name);\n-\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \t/*\n@@ -195,6 +340,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\trevs.simplify_history = 0;\n \t}\n \n+\tdetermine_replay_mode(&revs.cmdline, onto_name, &advance_name,\n+\t\t\t      &onto, &update_refs);\n+\n+\tif (!onto) /* FIXME: Should handle replaying down to root commit */\n+\t\tdie(\"Replaying down to root commit is not supported yet!\");\n+\n \tif (prepare_revision_walk(&revs) < 0) {\n \t\tret = error(_(\"error preparing revisions\"));\n \t\tgoto cleanup;\n@@ -203,6 +354,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n+\n \tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n \twhile ((commit = get_revision(&revs))) {\n@@ -217,12 +369,15 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Update any necessary branches */\n+\t\tif (advance_name)\n+\t\t\tcontinue;\n \t\tdecoration = get_name_decoration(&commit->object);\n \t\tif (!decoration)\n \t\t\tcontinue;\n-\n \t\twhile (decoration) {\n-\t\t\tif (decoration->type == DECORATION_REF_LOCAL) {\n+\t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n+\t\t\t    strset_contains(update_refs, decoration->name)) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\n@@ -232,10 +387,22 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\t/* In --advance mode, advance the target ref */\n+\tif (result.clean == 1 && advance_name) {\n+\t\tprintf(\"update %s %s %s\\n\",\n+\t\t       advance_name,\n+\t\t       oid_to_hex(&last_commit->object.oid),\n+\t\t       oid_to_hex(&onto->object.oid));\n+\t}\n+\n \tmerge_finalize(&merge_opt, &result);\n \tret = result.clean;\n \n cleanup:\n+\tif (update_refs) {\n+\t\tstrset_clear(update_refs);\n+\t\tfree(update_refs);\n+\t}\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex a1da4f9ef9..68a87e7803 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -80,4 +80,38 @@ test_expect_success 'using replay on bare repo to rebase with a conflict' '\n \ttest_expect_code 1 git -C bare replay --onto topic1 B..conflict\n '\n \n+test_expect_success 'using replay to perform basic cherry-pick' '\n+\t# The differences between this test and previous ones are:\n+\t#   --advance vs --onto\n+\t# 2nd field of result is refs/heads/main vs. refs/heads/topic2\n+\t# 4th field of result is hash for main instead of hash for topic2\n+\n+\tgit replay --advance main topic1..topic2 >result &&\n+\n+\ttest_line_count = 1 result &&\n+\n+\tgit log --format=%s $(cut -f 3 -d \" \" result) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/main \" >expect &&\n+\tprintf \"%s \" $(cut -f 3 -d \" \" result) >>expect &&\n+\tgit rev-parse main >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to perform basic cherry-pick' '\n+\tgit -C bare replay --advance main topic1..topic2 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n+test_expect_success 'replay on bare repo fails with both --advance and --onto' '\n+\ttest_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare\n+'\n+\n+test_expect_success 'replay fails when both --advance and --onto are omitted' '\n+\ttest_must_fail git replay topic1..topic2 >result\n+'\n+\n test_done\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485120","messageId":"20231124111044.3426007-14-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 13/14] replay: add --contained to rebase contained branches","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:42Z","receivedAt":"2023-11-24T11:11:22Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet's add a `--contained` option that can be used along with\n`--onto` to rebase all the branches contained in the <revision-range>\nargument.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-replay.txt | 12 +++++++++++-\n builtin/replay.c             | 14 ++++++++++++--\n t/t3650-replay-basics.sh     | 29 +++++++++++++++++++++++++++++\n 3 files changed, 52 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-replay.txt b/Documentation/git-replay.txt\nindex c4c64f955a..f6c269c62d 100644\n--- a/Documentation/git-replay.txt\n+++ b/Documentation/git-replay.txt\n@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t\n SYNOPSIS\n --------\n [verse]\n-(EXPERIMENTAL!) 'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n+(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\n \n DESCRIPTION\n -----------\n@@ -96,6 +96,16 @@ top of the exact same new base, they only differ in that the first\n provides instructions to make mybranch point at the new commits and\n the second provides instructions to make target point at them.\n \n+What if you have a stack of branches, one depending upon another, and\n+you'd really like to rebase the whole set?\n+\n+------------\n+$ git replay --contained --onto origin/main origin/main..tipbranch\n+update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}\n+update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}\n+update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}\n+------------\n+\n When calling `git replay`, one does not need to specify a range of\n commits to replay using the syntax `A..B`; any range expression will\n do:\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex f26806d7e2..df14657e2f 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -258,6 +258,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tconst char *advance_name = NULL;\n \tstruct commit *onto = NULL;\n \tconst char *onto_name = NULL;\n+\tint contained = 0;\n \n \tstruct rev_info revs;\n \tstruct commit *last_commit = NULL;\n@@ -268,7 +269,9 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n-\t\tN_(\"(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n+\t\tN_(\"(EXPERIMENTAL!) git replay \"\n+\t\t   \"([--contained] --onto <newbase> | --advance <branch>) \"\n+\t\t   \"<revision-range>...\"),\n \t\tNULL\n \t};\n \tstruct option replay_options[] = {\n@@ -278,6 +281,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"replay onto given commit\")),\n+\t\tOPT_BOOL(0, \"contained\", &contained,\n+\t\t\t N_(\"advance all branches contained in revision-range\")),\n \t\tOPT_END()\n \t};\n \n@@ -289,6 +294,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(replay_usage, replay_options);\n \t}\n \n+\tif (advance_name && contained)\n+\t\tdie(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t    \"--advance\", \"--contained\");\n+\n \trepo_init_revisions(the_repository, &revs, prefix);\n \n \t/*\n@@ -377,7 +386,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t\t\tcontinue;\n \t\twhile (decoration) {\n \t\t\tif (decoration->type == DECORATION_REF_LOCAL &&\n-\t\t\t    strset_contains(update_refs, decoration->name)) {\n+\t\t\t    (contained || strset_contains(update_refs,\n+\t\t\t\t\t\t\t  decoration->name))) {\n \t\t\t\tprintf(\"update %s %s %s\\n\",\n \t\t\t\t       decoration->name,\n \t\t\t\t       oid_to_hex(&last_commit->object.oid),\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex 68a87e7803..d6286f9580 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -114,4 +114,33 @@ test_expect_success 'replay fails when both --advance and --onto are omitted' '\n \ttest_must_fail git replay topic1..topic2 >result\n '\n \n+test_expect_success 'using replay to also rebase a contained branch' '\n+\tgit replay --contained --onto main main..topic3 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines H G F C M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic1 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic1 >>expect &&\n+\tprintf \"update refs/heads/topic3 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic3 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to also rebase a contained branch' '\n+\tgit -C bare replay --contained --onto main main..topic3 >result-bare &&\n+\ttest_cmp expect result-bare\n+'\n+\n test_done\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485121","messageId":"20231124111044.3426007-15-christian.couder@gmail.com","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"[PATCH v8 14/14] replay: stop assuming replayed branches do not diverge","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2023-11-24T11:10:43Z","receivedAt":"2023-11-24T11:11:24Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe replay command is able to replay multiple branches but when some of\nthem are based on other replayed branches, their commit should be\nreplayed onto already replayed commits.\n\nFor this purpose, let's store the replayed commit and its original\ncommit in a key value store, so that we can easily find and reuse a\nreplayed commit instead of the original one.\n\nCo-authored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replay.c         | 44 ++++++++++++++++++++++++++--------\n t/t3650-replay-basics.sh | 52 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 86 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex df14657e2f..6bc4b47f09 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -223,20 +223,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,\n \tstrset_clear(&rinfo.positive_refs);\n }\n \n+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,\n+\t\t\t\t    struct commit *commit,\n+\t\t\t\t    struct commit *fallback)\n+{\n+\tkhint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);\n+\tif (pos == kh_end(replayed_commits))\n+\t\treturn fallback;\n+\treturn kh_value(replayed_commits, pos);\n+}\n+\n static struct commit *pick_regular_commit(struct commit *pickme,\n-\t\t\t\t\t  struct commit *last_commit,\n+\t\t\t\t\t  kh_oid_map_t *replayed_commits,\n+\t\t\t\t\t  struct commit *onto,\n \t\t\t\t\t  struct merge_options *merge_opt,\n \t\t\t\t\t  struct merge_result *result)\n {\n-\tstruct commit *base;\n+\tstruct commit *base, *replayed_base;\n \tstruct tree *pickme_tree, *base_tree;\n \n \tbase = pickme->parents->item;\n+\treplayed_base = mapped_commit(replayed_commits, base, onto);\n \n+\tresult->tree = repo_get_commit_tree(the_repository, replayed_base);\n \tpickme_tree = repo_get_commit_tree(the_repository, pickme);\n \tbase_tree = repo_get_commit_tree(the_repository, base);\n \n-\tmerge_opt->branch1 = short_commit_name(last_commit);\n+\tmerge_opt->branch1 = short_commit_name(replayed_base);\n \tmerge_opt->branch2 = short_commit_name(pickme);\n \tmerge_opt->ancestor = xstrfmt(\"parent of %s\", merge_opt->branch2);\n \n@@ -250,7 +263,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,\n \tmerge_opt->ancestor = NULL;\n \tif (!result->clean)\n \t\treturn NULL;\n-\treturn create_commit(result->tree, pickme, last_commit);\n+\treturn create_commit(result->tree, pickme, replayed_base);\n }\n \n int cmd_replay(int argc, const char **argv, const char *prefix)\n@@ -266,6 +279,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tstruct merge_options merge_opt;\n \tstruct merge_result result;\n \tstruct strset *update_refs = NULL;\n+\tkh_oid_map_t *replayed_commits;\n \tint ret = 0;\n \n \tconst char * const replay_usage[] = {\n@@ -363,21 +377,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \tinit_merge_options(&merge_opt, the_repository);\n \tmemset(&result, 0, sizeof(result));\n \tmerge_opt.show_rename_progress = 0;\n-\n-\tresult.tree = repo_get_commit_tree(the_repository, onto);\n \tlast_commit = onto;\n+\treplayed_commits = kh_init_oid_map();\n \twhile ((commit = get_revision(&revs))) {\n \t\tconst struct name_decoration *decoration;\n+\t\tkhint_t pos;\n+\t\tint hr;\n \n \t\tif (!commit->parents)\n \t\t\tdie(_(\"replaying down to root commit is not supported yet!\"));\n \t\tif (commit->parents->next)\n \t\t\tdie(_(\"replaying merge commits is not supported yet!\"));\n \n-\t\tlast_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);\n+\t\tlast_commit = pick_regular_commit(commit, replayed_commits, onto,\n+\t\t\t\t\t\t  &merge_opt, &result);\n \t\tif (!last_commit)\n \t\t\tbreak;\n \n+\t\t/* Record commit -> last_commit mapping */\n+\t\tpos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);\n+\t\tif (hr == 0)\n+\t\t\tBUG(\"Duplicate rewritten commit: %s\\n\",\n+\t\t\t    oid_to_hex(&commit->object.oid));\n+\t\tkh_value(replayed_commits, pos) = last_commit;\n+\n \t\t/* Update any necessary branches */\n \t\tif (advance_name)\n \t\t\tcontinue;\n@@ -406,13 +429,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)\n \t}\n \n \tmerge_finalize(&merge_opt, &result);\n-\tret = result.clean;\n-\n-cleanup:\n+\tkh_destroy_oid_map(replayed_commits);\n \tif (update_refs) {\n \t\tstrset_clear(update_refs);\n \t\tfree(update_refs);\n \t}\n+\tret = result.clean;\n+\n+cleanup:\n \trelease_revisions(&revs);\n \n \t/* Return */\ndiff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh\nindex d6286f9580..389670262e 100755\n--- a/t/t3650-replay-basics.sh\n+++ b/t/t3650-replay-basics.sh\n@@ -143,4 +143,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch\n \ttest_cmp expect result-bare\n '\n \n+test_expect_success 'using replay to rebase multiple divergent branches' '\n+\tgit replay --onto main ^topic1 topic2 topic4 >result &&\n+\n+\ttest_line_count = 2 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\tgit log --format=%s $(head -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines E D M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgit log --format=%s $(tail -n 1 new-branch-tips) >actual &&\n+\ttest_write_lines J I M L B A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tprintf \"update refs/heads/topic2 \" >expect &&\n+\tprintf \"%s \" $(head -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic2 >>expect &&\n+\tprintf \"update refs/heads/topic4 \" >>expect &&\n+\tprintf \"%s \" $(tail -n 1 new-branch-tips) >>expect &&\n+\tgit rev-parse topic4 >>expect &&\n+\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '\n+\tgit -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&\n+\n+\ttest_line_count = 4 result &&\n+\tcut -f 3 -d \" \" result >new-branch-tips &&\n+\n+\t>expect &&\n+\tfor i in 2 1 3 4\n+\tdo\n+\t\tprintf \"update refs/heads/topic$i \" >>expect &&\n+\t\tprintf \"%s \" $(grep topic$i result | cut -f 3 -d \" \") >>expect &&\n+\t\tgit -C bare rev-parse topic$i >>expect || return 1\n+\tdone &&\n+\n+\ttest_cmp expect result &&\n+\n+\ttest_write_lines F C M L B A >expect1 &&\n+\ttest_write_lines E D C M L B A >expect2 &&\n+\ttest_write_lines H G F C M L B A >expect3 &&\n+\ttest_write_lines J I M L B A >expect4 &&\n+\n+\tfor i in 1 2 3 4\n+\tdo\n+\t\tgit -C bare log --format=%s $(grep topic$i result | cut -f 3 -d \" \") >actual &&\n+\t\ttest_cmp expect$i actual || return 1\n+\tdone\n+'\n+\n test_done\n-- \n2.43.0.14.g93e034faee\n\n"},{"id":"485140","messageId":"6bfe1541-54dd-ca6b-e930-94d3038060f1@gmx.de","threadId":"59560","inReplyTo":"20231124111044.3426007-1-christian.couder@gmail.com","subject":"Re: [PATCH v8 00/14] Introduce new `git replay` command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-11-25T00:02:20Z","receivedAt":"2023-11-25T00:02:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Fri, 24 Nov 2023, Christian Couder wrote:\n\n> # Changes between v7 and v8\n>\n> Thanks to Dscho, Linus Arver, Dragan Simic, Elijah, Junio, Derrick\n> Stolee, Phillip Wood, Calvin Wan and Toon Claes for their suggestions\n> on the previous versions! The only few changes compared to v7 are:\n>\n> * The patch series was rebased onto master at 564d0252ca (Git 2.43,\n>   2023-11-20). This is to make it stand on a stable base.\n>\n> * In patch 2/14 (replay: introduce new builtin), there is a synopsys\n>   change in the doc, and the corresponding usage message change as\n>   suggested by Dscho. This is just about replacing \" # EXPERIMENTAL\"\n>   at the end of both the synopsys and usage message with\n>   \"(EXPERIMENTAL!) \" at the beginning of them.\n\nThank you so much for going all the way to where we are now.\n\n> CI tests seem to pass according to:\n>\n> https://github.com/chriscool/git/actions/runs/6979770154\n>\n> (Sorry I am not waiting more than 20 minutes for the 3 last ones to\n> finish.)\n\nYes, our test suite takes too long, and I fear that there are quite a few\ndevelopers ignoring it as a consequence.\n\n> # Range-diff between v7 and v8\n>\n> (A single change was made in patch 2/14, but unfortunately as the\n> lines changed in that patch are also changed by other patches later,\n> it looks like there are more changes in subsequent patches.)\n\nRight, the lines added in 2/14 are changed multiple times over the course\nof the patch series.\n\n>  1:  cddcd967b2 =  1:  18fd9b0d5d t6429: remove switching aspects of fast-rebase\n>  2:  c8476fb093 !  2:  fc6bdf4de4 replay: introduce new builtin\n>     @@ Documentation/git-replay.txt (new)\n>      +SYNOPSIS\n>      +--------\n>      +[verse]\n>     -+'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n>     ++(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>\n>      +\n>      +DESCRIPTION\n>      +-----------\n>     @@ builtin/replay.c: int cmd__fast_rebase(int argc, const char **argv)\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>     -+          printf(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\\n\");\n>     ++          printf(\"usage: (EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\\n\");\n>                 exit(129);\n>         }\n>\n>  3:  43322abd1e !  3:  e96a66c352 replay: start using parse_options API\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>         int ret = 0;\n>\n>      -  if (argc == 2 && !strcmp(argv[1], \"-h\")) {\n>     --          printf(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\\n\");\n>     +-          printf(\"usage: (EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\\n\");\n>      -          exit(129);\n>      +  const char * const replay_usage[] = {\n>     -+          N_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n>     ++          N_(\"(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\"),\n>      +          NULL\n>      +  };\n>      +  struct option replay_options[] = {\n>  4:  6524c7f045 =  4:  f819d283d9 replay: die() instead of failing assert()\n>  5:  05d0efa3cb =  5:  68bbcf9492 replay: introduce pick_regular_commit()\n>  6:  c7a5aad3d6 =  6:  72221c647e replay: change rev walking options\n>  7:  01f35f924b =  7:  f54d8fce22 replay: add an important FIXME comment about gpg signing\n>  8:  1498b24bad =  8:  e50cc22522 replay: remove progress and info output\n>  9:  6786fc147b =  9:  0c5ea3d18e replay: remove HEAD related sanity check\n> 10:  9a24dbb530 = 10:  9fc636fc3d replay: make it a minimal server side command\n> 11:  ad6ca2fbef ! 11:  2096bcad79 replay: use standard revision ranges\n>     @@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new\n>       SYNOPSIS\n>       --------\n>       [verse]\n>     --'git replay' --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\n>     -+'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n>     +-(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>\n>     ++(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...\n>\n>       DESCRIPTION\n>       -----------\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>         int ret = 0;\n>\n>         const char * const replay_usage[] = {\n>     --          N_(\"git replay --onto <newbase> <oldbase> <branch> # EXPERIMENTAL\"),\n>     -+          N_(\"git replay --onto <newbase> <revision-range>... # EXPERIMENTAL\"),\n>     +-          N_(\"(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>\"),\n>     ++          N_(\"(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>...\"),\n>                 NULL\n>         };\n>         struct option replay_options[] = {\n> 12:  081864ed5f ! 12:  d5414806ef replay: add --advance or 'cherry-pick' mode\n>     @@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new\n>       SYNOPSIS\n>       --------\n>       [verse]\n>     --'git replay' --onto <newbase> <revision-range>... # EXPERIMENTAL\n>     -+'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n>     +-(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...\n>     ++(EXPERIMENTAL!) 'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n>\n>       DESCRIPTION\n>       -----------\n>     @@ builtin/replay.c: static struct commit *pick_regular_commit(struct commit *pickm\n>         int ret = 0;\n>\n>         const char * const replay_usage[] = {\n>     --          N_(\"git replay --onto <newbase> <revision-range>... # EXPERIMENTAL\"),\n>     -+          N_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\"),\n>     +-          N_(\"(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>...\"),\n>     ++          N_(\"(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n>                 NULL\n>         };\n>         struct option replay_options[] = {\n> 13:  19c4016c7c ! 13:  2a3e521c13 replay: add --contained to rebase contained branches\n>     @@ Documentation/git-replay.txt: git-replay - EXPERIMENTAL: Replay commits on a new\n>       SYNOPSIS\n>       --------\n>       [verse]\n>     --'git replay' (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n>     -+'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\n>     +-(EXPERIMENTAL!) 'git replay' (--onto <newbase> | --advance <branch>) <revision-range>...\n>     ++(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...\n>\n>       DESCRIPTION\n>       -----------\n>     @@ builtin/replay.c: int cmd_replay(int argc, const char **argv, const char *prefix\n>         int ret = 0;\n>\n>         const char * const replay_usage[] = {\n>     --          N_(\"git replay (--onto <newbase> | --advance <branch>) <revision-range>... # EXPERIMENTAL\"),\n>     -+          N_(\"git replay ([--contained] --onto <newbase> | --advance <branch>) \"\n>     -+             \"<revision-range>... # EXPERIMENTAL\"),\n>     +-          N_(\"(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>...\"),\n>     ++          N_(\"(EXPERIMENTAL!) git replay \"\n>     ++             \"([--contained] --onto <newbase> | --advance <branch>) \"\n>     ++             \"<revision-range>...\"),\n>                 NULL\n>         };\n>         struct option replay_options[] = {\n> 14:  29556bcc86 = 14:  93e034faee replay: stop assuming replayed branches do not diverge\n\nThe range-diff looks excellent!\n\nThank you for addressing all of my concerns, I am very much in favor of\ngetting this version into git/git's main branch.\n\nThank you,\nJohannes\n"}]}