replay: add --contained to rebase contained branches

Let's add a `--contained` option that can be used along with `--onto` to rebase all the branches contained in the <revision-range> argument. 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 c4611130f47242af19fbd8eca2be039742c122b1
3 files changed +52 -3
Documentation/git-replay.txt
+11 -1
@@ -9,7 +9,7 @@ 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> | --advance <branch>) <revision-range>...
12 +(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>...
13
14 DESCRIPTION
15 -----------
@@ -96,6 +96,16 @@ top of the exact same new base, they only differ in that the first
96 provides instructions to make mybranch point at the new commits and
97 the second provides instructions to make target point at them.
98
99 +What if you have a stack of branches, one depending upon another, and
100 +you'd really like to rebase the whole set?
101 +
102 +------------
103 +$ git replay --contained --onto origin/main origin/main..tipbranch
104 +update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH}
105 +update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH}
106 +update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH}
107 +------------
108 +
109 When calling `git replay`, one does not need to specify a range of
110 commits to replay using the syntax `A..B`; any range expression will
111 do:
builtin/replay.c
+12 -2
@@ -258,6 +258,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
258 const char *advance_name = NULL;
259 struct commit *onto = NULL;
260 const char *onto_name = NULL;
261 + int contained = 0;
262
263 struct rev_info revs;
264 struct commit *last_commit = NULL;
@@ -268,7 +269,9 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
269 int ret = 0;
270
271 const char * const replay_usage[] = {
271 - N_("(EXPERIMENTAL!) git replay (--onto <newbase> | --advance <branch>) <revision-range>..."),
272 + N_("(EXPERIMENTAL!) git replay "
273 + "([--contained] --onto <newbase> | --advance <branch>) "
274 + "<revision-range>..."),
275 NULL
276 };
277 struct option replay_options[] = {
@@ -278,6 +281,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
281 OPT_STRING(0, "onto", &onto_name,
282 N_("revision"),
283 N_("replay onto given commit")),
284 + OPT_BOOL(0, "contained", &contained,
285 + N_("advance all branches contained in revision-range")),
286 OPT_END()
287 };
288
@@ -289,6 +294,10 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
294 usage_with_options(replay_usage, replay_options);
295 }
296
297 + if (advance_name && contained)
298 + die(_("options '%s' and '%s' cannot be used together"),
299 + "--advance", "--contained");
300 +
301 repo_init_revisions(the_repository, &revs, prefix);
302
303 /*
@@ -377,7 +386,8 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
386 continue;
387 while (decoration) {
388 if (decoration->type == DECORATION_REF_LOCAL &&
380 - strset_contains(update_refs, decoration->name)) {
389 + (contained || strset_contains(update_refs,
390 + decoration->name))) {
391 printf("update %s %s %s\n",
392 decoration->name,
393 oid_to_hex(&last_commit->object.oid),
t/t3650-replay-basics.sh
+29
@@ -114,4 +114,33 @@ test_expect_success 'replay fails when both --advance and --onto are omitted' '
114 test_must_fail git replay topic1..topic2 >result
115 '
116
117 +test_expect_success 'using replay to also rebase a contained branch' '
118 + git replay --contained --onto main main..topic3 >result &&
119 +
120 + test_line_count = 2 result &&
121 + cut -f 3 -d " " result >new-branch-tips &&
122 +
123 + git log --format=%s $(head -n 1 new-branch-tips) >actual &&
124 + test_write_lines F C M L B A >expect &&
125 + test_cmp expect actual &&
126 +
127 + git log --format=%s $(tail -n 1 new-branch-tips) >actual &&
128 + test_write_lines H G F C M L B A >expect &&
129 + test_cmp expect actual &&
130 +
131 + printf "update refs/heads/topic1 " >expect &&
132 + printf "%s " $(head -n 1 new-branch-tips) >>expect &&
133 + git rev-parse topic1 >>expect &&
134 + printf "update refs/heads/topic3 " >>expect &&
135 + printf "%s " $(tail -n 1 new-branch-tips) >>expect &&
136 + git rev-parse topic3 >>expect &&
137 +
138 + test_cmp expect result
139 +'
140 +
141 +test_expect_success 'using replay on bare repo to also rebase a contained branch' '
142 + git -C bare replay --contained --onto main main..topic3 >result-bare &&
143 + test_cmp expect result-bare
144 +'
145 +
146 test_done