replay: make it a minimal server side command

We want this command to be a minimal command that just does server side picking of commits, displaying the results on stdout for higher level scripts to consume. So let's simplify it: * remove the worktree and index reading/writing, * remove the ref (and reflog) updating, * remove the assumptions tying us to HEAD, since (a) this is not a rebase and (b) we want to be able to pick commits in a bare repo, i.e. to/from branches that are not checked out and not the main branch, * remove unneeded includes, * handle rebasing multiple branches by printing on stdout the update ref commands that should be performed. The output can be piped into `git update-ref --stdin` for the ref updates to happen. In the future to make it easier for users to use this command directly maybe an option can be added to automatically pipe its output into `git update-ref`. 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 81613be31e0bedf8709fa0962f1b6f85dcb053a2
4 files changed +72 -69
Documentation/git-replay.txt
+4 -1
@@ -15,7 +15,10 @@ 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).
18 +replays them onto a new location (see `--onto` option below). Leaves
19 +the working tree and the index untouched, and updates no references.
20 +The output of this command is meant to be used as input to
21 +`git update-ref --stdin`, which would update the relevant branches.
22
23 THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
24
builtin/replay.c
+26 -52
@@ -6,11 +6,7 @@
6 #include "git-compat-util.h"
7
8 #include "builtin.h"
9 -#include "cache-tree.h"
10 -#include "commit.h"
9 #include "environment.h"
12 -#include "gettext.h"
13 -#include "hash.h"
10 #include "hex.h"
11 #include "lockfile.h"
12 #include "merge-ort.h"
@@ -18,8 +14,6 @@
14 #include "parse-options.h"
15 #include "refs.h"
16 #include "revision.h"
21 -#include "sequencer.h"
22 -#include "setup.h"
17 #include "strvec.h"
18 #include <oidset.h>
19 #include <tree.h>
@@ -102,6 +96,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,
96 pickme_tree = repo_get_commit_tree(the_repository, pickme);
97 base_tree = repo_get_commit_tree(the_repository, base);
98
99 + merge_opt->branch1 = short_commit_name(last_commit);
100 merge_opt->branch2 = short_commit_name(pickme);
101 merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
102
@@ -122,15 +117,12 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
117 {
118 struct commit *onto;
119 const char *onto_name = NULL;
125 - struct commit *last_commit = NULL, *last_picked_commit = NULL;
126 - struct lock_file lock = LOCK_INIT;
120 + struct commit *last_commit = NULL;
121 struct strvec rev_walk_args = STRVEC_INIT;
122 struct rev_info revs;
123 struct commit *commit;
124 struct merge_options merge_opt;
131 - struct tree *head_tree;
125 struct merge_result result;
133 - struct strbuf reflog_msg = STRBUF_INIT;
126 struct strbuf branch_name = STRBUF_INIT;
127 int ret = 0;
128
@@ -161,10 +153,6 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
153 onto = peel_committish(onto_name);
154 strbuf_addf(&branch_name, "refs/heads/%s", argv[2]);
155
164 - repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);
165 - if (repo_read_index(the_repository) < 0)
166 - BUG("Could not read index");
167 -
156 repo_init_revisions(the_repository, &revs, prefix);
157
158 strvec_pushl(&rev_walk_args, "", argv[2], "--not", argv[1], NULL);
@@ -227,58 +215,44 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
215 init_merge_options(&merge_opt, the_repository);
216 memset(&result, 0, sizeof(result));
217 merge_opt.show_rename_progress = 0;
230 - merge_opt.branch1 = "HEAD";
231 - head_tree = repo_get_commit_tree(the_repository, onto);
232 - result.tree = head_tree;
218 + result.tree = repo_get_commit_tree(the_repository, onto);
219 last_commit = onto;
220 while ((commit = get_revision(&revs))) {
235 - struct commit *pick;
221 + const struct name_decoration *decoration;
222
223 if (!commit->parents)
224 die(_("replaying down to root commit is not supported yet!"));
225 if (commit->parents->next)
226 die(_("replaying merge commits is not supported yet!"));
227
242 - pick = pick_regular_commit(commit, last_commit, &merge_opt, &result);
243 - if (!pick)
228 + last_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);
229 + if (!last_commit)
230 break;
245 - last_commit = pick;
246 - last_picked_commit = commit;
231 +
232 + decoration = get_name_decoration(&commit->object);
233 + if (!decoration)
234 + continue;
235 +
236 + while (decoration) {
237 + if (decoration->type == DECORATION_REF_LOCAL) {
238 + printf("update %s %s %s\n",
239 + decoration->name,
240 + oid_to_hex(&last_commit->object.oid),
241 + oid_to_hex(&commit->object.oid));
242 + }
243 + decoration = decoration->next;
244 + }
245 }
246
247 merge_finalize(&merge_opt, &result);
248 + ret = result.clean;
249
251 - if (result.clean < 0)
252 - exit(128);
253 -
254 - if (result.clean) {
255 - strbuf_addf(&reflog_msg, "finish rebase %s onto %s",
256 - oid_to_hex(&last_picked_commit->object.oid),
257 - oid_to_hex(&last_commit->object.oid));
258 - if (update_ref(reflog_msg.buf, branch_name.buf,
259 - &last_commit->object.oid,
260 - &last_picked_commit->object.oid,
261 - REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
262 - error(_("could not update %s"), argv[2]);
263 - die("Failed to update %s", argv[2]);
264 - }
265 - if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0)
266 - die(_("unable to update HEAD"));
267 - } else {
268 - strbuf_addf(&reflog_msg, "rebase progress up to %s",
269 - oid_to_hex(&last_picked_commit->object.oid));
270 - if (update_ref(reflog_msg.buf, "HEAD",
271 - &last_commit->object.oid,
272 - &onto->object.oid,
273 - REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
274 - error(_("could not update %s"), argv[2]);
275 - die("Failed to update %s", argv[2]);
276 - }
277 - }
278 - ret = (result.clean == 0);
250 cleanup:
280 - strbuf_release(&reflog_msg);
251 strbuf_release(&branch_name);
252 release_revisions(&revs);
283 - return ret;
253 +
254 + /* Return */
255 + if (ret < 0)
256 + exit(128);
257 + return ret ? 0 : 1;
258 }
t/t3650-replay-basics.sh
+18 -1
@@ -47,12 +47,29 @@ test_expect_success 'setup' '
47 test_commit C.conflict C.t conflict
48 '
49
50 +test_expect_success 'setup bare' '
51 + git clone --bare . 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 &&
56
57 + test_line_count = 1 result &&
58 +
59 git log --format=%s $(cut -f 3 -d " " result) >actual &&
60 test_write_lines E D M L B A >expect &&
55 - test_cmp expect actual
61 + test_cmp expect actual &&
62 +
63 + printf "update refs/heads/topic2 " >expect &&
64 + printf "%s " $(cut -f 3 -d " " result) >>expect &&
65 + git rev-parse topic2 >>expect &&
66 +
67 + test_cmp expect result
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 &&
72 + test_cmp expect result-bare
73 '
74
75 test_done
t/t6429-merge-sequence-rename-caching.sh
+24 -15
@@ -71,8 +71,9 @@ 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 &&
75 - git reset --hard topic &&
74 + git replay --onto HEAD upstream~1 topic >out &&
75 + git update-ref --stdin <out &&
76 + git checkout topic &&
77
78 git ls-files >tracked-files &&
79 test_line_count = 2 tracked-files &&
@@ -140,7 +141,9 @@ 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
143 - git replay --onto HEAD upstream~1 topic &&
144 + git replay --onto HEAD upstream~1 topic >out &&
145 + git update-ref --stdin <out &&
146 + git checkout topic &&
147
148 grep region_enter.*diffcore_rename trace.output >calls &&
149 test_line_count = 1 calls
@@ -198,8 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' '
201 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
202 export GIT_TRACE2_PERF &&
203
201 - git replay --onto HEAD upstream~1 topic &&
202 - git reset --hard topic &&
204 + git replay --onto HEAD upstream~1 topic >out &&
205 + git update-ref --stdin <out &&
206 + git checkout topic &&
207
208 git ls-files >tracked &&
209 test_line_count = 2 tracked &&
@@ -275,8 +279,9 @@ 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
278 - git replay --onto HEAD upstream~1 topic &&
279 - git reset --hard topic &&
282 + git replay --onto HEAD upstream~1 topic >out &&
283 + git update-ref --stdin <out &&
284 + git checkout topic &&
285
286 git ls-files >tracked &&
287 test_line_count = 4 tracked &&
@@ -451,8 +456,9 @@ 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
454 - git replay --onto HEAD upstream~1 topic &&
455 - git reset --hard topic &&
459 + git replay --onto HEAD upstream~1 topic >out &&
460 + git update-ref --stdin <out &&
461 + git checkout topic &&
462
463 grep region_enter.*diffcore_rename trace.output >calls &&
464 test_line_count = 2 calls &&
@@ -517,8 +523,9 @@ 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
520 - git replay --onto HEAD upstream~1 topic &&
521 - git reset --hard topic &&
526 + git replay --onto HEAD upstream~1 topic >out &&
527 + git update-ref --stdin <out &&
528 + git checkout topic &&
529
530 grep region_enter.*diffcore_rename trace.output >calls &&
531 test_line_count = 3 calls &&
@@ -619,8 +626,9 @@ test_expect_success 'caching renames only on upstream side, part 1' '
626 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
627 export GIT_TRACE2_PERF &&
628
622 - git replay --onto HEAD upstream~1 topic &&
623 - git reset --hard topic &&
629 + git replay --onto HEAD upstream~1 topic >out &&
630 + git update-ref --stdin <out &&
631 + git checkout topic &&
632
633 grep region_enter.*diffcore_rename trace.output >calls &&
634 test_line_count = 1 calls &&
@@ -677,8 +685,9 @@ test_expect_success 'caching renames only on upstream side, part 2' '
685 GIT_TRACE2_PERF="$(pwd)/trace.output" &&
686 export GIT_TRACE2_PERF &&
687
680 - git replay --onto HEAD upstream~1 topic &&
681 - git reset --hard topic &&
688 + git replay --onto HEAD upstream~1 topic >out &&
689 + git update-ref --stdin <out &&
690 + git checkout topic &&
691
692 grep region_enter.*diffcore_rename trace.output >calls &&
693 test_line_count = 2 calls &&