replay: use standard revision ranges

Instead of the fixed "<oldbase> <branch>" arguments, the replay command now accepts "<revision-range>..." arguments in a similar way as many other Git commands. This makes its interface more standard and more flexible. This also enables many revision related options accepted and eaten by setup_revisions(). If the replay command was a high level one or had a high level mode, it would make sense to restrict some of the possible options, like those generating non-contiguous history, as they could be confusing for most users. Also as the interface of the command is now mostly finalized, we can add more documentation and more testcases to make sure the command will continue to work as designed in the future. We only document the rev-list related options among all the revision related options that are now accepted, as the rev-list related ones are probably the most useful for now. Helped-by: Dragan Simic <dsimic@manjaro.org> Helped-by: Linus Arver <linusa@google.com> Co-authored-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Nov 24, 2023 at 12:10 UTC 3916ec307eda00d1aab23cf8d1b6ddc5c4f3601d
4 files changed +77 -32
Documentation/git-replay.txt
+54 -4
@@ -9,16 +9,16 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t
9 SYNOPSIS
10 --------
11 [verse]
12 -(EXPERIMENTAL!) 'git replay' --onto <newbase> <oldbase> <branch>
12 +(EXPERIMENTAL!) 'git replay' --onto <newbase> <revision-range>...
13
14 DESCRIPTION
15 -----------
16
17 -Takes a range of commits, specified by <oldbase> and <branch>, and
18 -replays them onto a new location (see `--onto` option below). Leaves
17 +Takes ranges of commits and replays them onto a new location. Leaves
18 the working tree and the index untouched, and updates no references.
19 The output of this command is meant to be used as input to
21 -`git update-ref --stdin`, which would update the relevant branches.
20 +`git update-ref --stdin`, which would update the relevant branches
21 +(see the OUTPUT section below).
22
23 THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
24
@@ -28,6 +28,30 @@ OPTIONS
28 --onto <newbase>::
29 Starting point at which to create the new commits. May be any
30 valid commit, and not just an existing branch name.
31 ++
32 +The update-ref command(s) in the output will update the branch(es) in
33 +the revision range to point at the new commits, similar to the way how
34 +`git rebase --update-refs` updates multiple branches in the affected
35 +range.
36 +
37 +<revision-range>::
38 + Range of commits to replay; see "Specifying Ranges" in
39 + linkgit:git-rev-parse and the "Commit Limiting" options below.
40 +
41 +include::rev-list-options.txt[]
42 +
43 +OUTPUT
44 +------
45 +
46 +When there are no conflicts, the output of this command is usable as
47 +input to `git update-ref --stdin`. It is of the form:
48 +
49 + update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}
50 + update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}
51 + update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}
52 +
53 +where the number of refs updated depends on the arguments passed and
54 +the shape of the history being replayed.
55
56 EXIT STATUS
57 -----------
@@ -37,6 +61,32 @@ the replay has conflicts, the exit status is 1. If the replay is not
61 able to complete (or start) due to some kind of error, the exit status
62 is something other than 0 or 1.
63
64 +EXAMPLES
65 +--------
66 +
67 +To simply rebase `mybranch` onto `target`:
68 +
69 +------------
70 +$ git replay --onto target origin/main..mybranch
71 +update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}
72 +------------
73 +
74 +When calling `git replay`, one does not need to specify a range of
75 +commits to replay using the syntax `A..B`; any range expression will
76 +do:
77 +
78 +------------
79 +$ git replay --onto origin/main ^base branch1 branch2 branch3
80 +update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}
81 +update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}
82 +update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}
83 +------------
84 +
85 +This will simultaneously rebase `branch1`, `branch2`, and `branch3`,
86 +all commits they have since `base`, playing them on top of
87 +`origin/main`. These three branches may have commits on top of `base`
88 +that they have in common, but that does not need to be the case.
89 +
90 GIT
91 ---
92 Part of the linkgit:git[1] suite
builtin/replay.c
+4 -17
@@ -14,7 +14,6 @@
14 #include "parse-options.h"
15 #include "refs.h"
16 #include "revision.h"
17 -#include "strvec.h"
17 #include <oidset.h>
18 #include <tree.h>
19
@@ -118,16 +117,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
117 struct commit *onto;
118 const char *onto_name = NULL;
119 struct commit *last_commit = NULL;
121 - struct strvec rev_walk_args = STRVEC_INIT;
120 struct rev_info revs;
121 struct commit *commit;
122 struct merge_options merge_opt;
123 struct merge_result result;
126 - struct strbuf branch_name = STRBUF_INIT;
124 int ret = 0;
125
126 const char * const replay_usage[] = {
130 - N_("(EXPERIMENTAL!) git replay --onto <newbase> <oldbase> <branch>"),
127 + N_("(EXPERIMENTAL!) git replay --onto <newbase> <revision-range>..."),
128 NULL
129 };
130 struct option replay_options[] = {
@@ -145,18 +142,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
142 usage_with_options(replay_usage, replay_options);
143 }
144
148 - if (argc != 3) {
149 - error(_("bad number of arguments"));
150 - usage_with_options(replay_usage, replay_options);
151 - }
152 -
145 onto = peel_committish(onto_name);
154 - strbuf_addf(&branch_name, "refs/heads/%s", argv[2]);
146
147 repo_init_revisions(the_repository, &revs, prefix);
148
158 - strvec_pushl(&rev_walk_args, "", argv[2], "--not", argv[1], NULL);
159 -
149 /*
150 * Set desired values for rev walking options here. If they
151 * are changed by some user specified option in setup_revisions()
@@ -171,8 +160,9 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
160 revs.topo_order = 1;
161 revs.simplify_history = 0;
162
174 - if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) {
175 - ret = error(_("unhandled options"));
163 + argc = setup_revisions(argc, argv, &revs, NULL);
164 + if (argc > 1) {
165 + ret = error(_("unrecognized argument: %s"), argv[1]);
166 goto cleanup;
167 }
168
@@ -205,8 +195,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
195 revs.simplify_history = 0;
196 }
197
208 - strvec_clear(&rev_walk_args);
209 -
198 if (prepare_revision_walk(&revs) < 0) {
199 ret = error(_("error preparing revisions"));
200 goto cleanup;
@@ -248,7 +236,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
236 ret = result.clean;
237
238 cleanup:
251 - strbuf_release(&branch_name);
239 release_revisions(&revs);
240
241 /* Return */
t/t3650-replay-basics.sh
+10 -2
@@ -52,7 +52,7 @@ test_expect_success 'setup bare' '
52 '
53
54 test_expect_success 'using replay to rebase two branches, one on top of other' '
55 - git replay --onto main topic1 topic2 >result &&
55 + git replay --onto main topic1..topic2 >result &&
56
57 test_line_count = 1 result &&
58
@@ -68,8 +68,16 @@ test_expect_success 'using replay to rebase two branches, one on top of other' '
68 '
69
70 test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '
71 - git -C bare replay --onto main topic1 topic2 >result-bare &&
71 + git -C bare replay --onto main topic1..topic2 >result-bare &&
72 test_cmp expect result-bare
73 '
74
75 +test_expect_success 'using replay to rebase with a conflict' '
76 + test_expect_code 1 git replay --onto topic1 B..conflict
77 +'
78 +
79 +test_expect_success 'using replay on bare repo to rebase with a conflict' '
80 + test_expect_code 1 git -C bare replay --onto topic1 B..conflict
81 +'
82 +
83 test_done
t/t6429-merge-sequence-rename-caching.sh
+9 -9
@@ -71,7 +71,7 @@ test_expect_success 'caching renames does not preclude finding new ones' '
71
72 git switch upstream &&
73
74 - git replay --onto HEAD upstream~1 topic >out &&
74 + git replay --onto HEAD upstream~1..topic >out &&
75 git update-ref --stdin <out &&
76 git checkout topic &&
77
@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' '
141 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
142 export GIT_TRACE2_PERF &&
143
144 - git replay --onto HEAD upstream~1 topic >out &&
144 + git replay --onto HEAD upstream~1..topic >out &&
145 git update-ref --stdin <out &&
146 git checkout topic &&
147
@@ -201,7 +201,7 @@ test_expect_success 'rename same file identically, then reintroduce it' '
201 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
202 export GIT_TRACE2_PERF &&
203
204 - git replay --onto HEAD upstream~1 topic >out &&
204 + git replay --onto HEAD upstream~1..topic >out &&
205 git update-ref --stdin <out &&
206 git checkout topic &&
207
@@ -279,7 +279,7 @@ test_expect_success 'rename same file identically, then add file to old dir' '
279 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
280 export GIT_TRACE2_PERF &&
281
282 - git replay --onto HEAD upstream~1 topic >out &&
282 + git replay --onto HEAD upstream~1..topic >out &&
283 git update-ref --stdin <out &&
284 git checkout topic &&
285
@@ -357,7 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict'
357 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
358 export GIT_TRACE2_PERF &&
359
360 - test_must_fail git replay --onto HEAD upstream~1 topic >output &&
360 + test_must_fail git replay --onto HEAD upstream~1..topic >output &&
361
362 grep region_enter.*diffcore_rename trace.output >calls &&
363 test_line_count = 2 calls
@@ -456,7 +456,7 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' '
456 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
457 export GIT_TRACE2_PERF &&
458
459 - git replay --onto HEAD upstream~1 topic >out &&
459 + git replay --onto HEAD upstream~1..topic >out &&
460 git update-ref --stdin <out &&
461 git checkout topic &&
462
@@ -523,7 +523,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir
523 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
524 export GIT_TRACE2_PERF &&
525
526 - git replay --onto HEAD upstream~1 topic >out &&
526 + git replay --onto HEAD upstream~1..topic >out &&
527 git update-ref --stdin <out &&
528 git checkout topic &&
529
@@ -626,7 +626,7 @@ test_expect_success 'caching renames only on upstream side, part 1' '
626 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
627 export GIT_TRACE2_PERF &&
628
629 - git replay --onto HEAD upstream~1 topic >out &&
629 + git replay --onto HEAD upstream~1..topic >out &&
630 git update-ref --stdin <out &&
631 git checkout topic &&
632
@@ -685,7 +685,7 @@ test_expect_success 'caching renames only on upstream side, part 2' '
685 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
686 export GIT_TRACE2_PERF &&
687
688 - git replay --onto HEAD upstream~1 topic >out &&
688 + git replay --onto HEAD upstream~1..topic >out &&
689 git update-ref --stdin <out &&
690 git checkout topic &&
691