Skip to content

Commit 23d83f8

Browse files
To1negitster
authored andcommitted
replay: allow to specify a ref with option --ref
When option '--onto' is passed to git-replay(1), the command will update refs from the <revision-range> passed to the command. When using option '--advance' or '--revert', the argument of that option is a ref that will be updated. To enable users to specify which ref to update, add option '--ref'. When using option '--ref', the refs described above are left untouched and instead the argument of this option is updated instead. Because this introduces code paths in replay.c that jump to `out` before init_basic_merge_options() is called on `merge_opt`, zero-initialize the struct. Signed-off-by: Toon Claes <toon@iotcl.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 6542cac commit 23d83f8

5 files changed

Lines changed: 128 additions & 10 deletions

File tree

Documentation/git-replay.adoc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
(EXPERIMENTAL!) 'git replay' ([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)
13-
[--ref-action=<mode>] <revision-range>
13+
[--ref=<ref>] [--ref-action=<mode>] <revision-range>
1414

1515
DESCRIPTION
1616
-----------
@@ -66,6 +66,16 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
6666
Update all branches that point at commits in
6767
<revision-range>. Requires `--onto`.
6868

69+
--ref=<ref>::
70+
Override which reference is updated with the result of the replay.
71+
The ref must be fully qualified.
72+
When used with `--onto`, the `<revision-range>` should have a
73+
single tip and only the specified reference is updated instead of
74+
inferring refs from the revision range.
75+
When used with `--advance` or `--revert`, the specified reference is
76+
updated instead of the branch given to those options.
77+
This option is incompatible with `--contained`.
78+
6979
--ref-action[=<mode>]::
7080
Control how references are updated. The mode can be:
7181
+
@@ -189,6 +199,16 @@ NOTE: For reverting an entire merge request as a single commit (rather than
189199
commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE`
190200
which can avoid unnecessary merge conflicts.
191201

202+
To replay onto a specific commit while updating a different reference:
203+
204+
------------
205+
$ git replay --onto=112233 --ref=refs/heads/mybranch aabbcc..ddeeff
206+
------------
207+
208+
This replays the range `aabbcc..ddeeff` onto commit `112233` and updates
209+
`refs/heads/mybranch` to point at the result. This can be useful when you want
210+
to use bare commit IDs instead of branch names.
211+
192212
GIT
193213
---
194214
Part of the linkgit:git[1] suite

builtin/replay.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ int cmd_replay(int argc,
8585
const char *const replay_usage[] = {
8686
N_("(EXPERIMENTAL!) git replay "
8787
"([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
88-
"[--ref-action=<mode>] <revision-range>"),
88+
"[--ref=<ref>] [--ref-action=<mode>] <revision-range>"),
8989
NULL
9090
};
9191
struct option replay_options[] = {
@@ -103,6 +103,10 @@ int cmd_replay(int argc,
103103
N_("branch"),
104104
N_("revert commits onto given branch"),
105105
PARSE_OPT_NONEG),
106+
OPT_STRING_F(0, "ref", &opts.ref,
107+
N_("branch"),
108+
N_("reference to update with result"),
109+
PARSE_OPT_NONEG),
106110
OPT_STRING_F(0, "ref-action", &ref_action,
107111
N_("mode"),
108112
N_("control ref update behavior (update|print)"),
@@ -126,6 +130,8 @@ int cmd_replay(int argc,
126130
opts.contained, "--contained");
127131
die_for_incompatible_opt2(!!opts.revert, "--revert",
128132
opts.contained, "--contained");
133+
die_for_incompatible_opt2(!!opts.ref, "--ref",
134+
!!opts.contained, "--contained");
129135

130136
/* Parse ref action mode from command line or config */
131137
ref_mode = get_ref_action_mode(repo, ref_action);

replay.c

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -347,13 +347,15 @@ int replay_revisions(struct rev_info *revs,
347347
struct commit *last_commit = NULL;
348348
struct commit *commit;
349349
struct commit *onto = NULL;
350-
struct merge_options merge_opt;
350+
struct merge_options merge_opt = { 0 };
351351
struct merge_result result = {
352352
.clean = 1,
353353
};
354354
bool detached_head;
355355
char *advance;
356356
char *revert;
357+
const char *ref;
358+
struct object_id old_oid;
357359
enum replay_mode mode = REPLAY_MODE_PICK;
358360
int ret;
359361

@@ -364,6 +366,27 @@ int replay_revisions(struct rev_info *revs,
364366
set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto,
365367
&detached_head, &advance, &revert, &onto, &update_refs);
366368

369+
if (opts->ref) {
370+
struct object_id oid;
371+
372+
if (update_refs && strset_get_size(update_refs) > 1) {
373+
ret = error(_("'--ref' cannot be used with multiple revision ranges"));
374+
goto out;
375+
}
376+
if (check_refname_format(opts->ref, 0) || !starts_with(opts->ref, "refs/")) {
377+
ret = error(_("'%s' is not a valid refname"), opts->ref);
378+
goto out;
379+
}
380+
ref = opts->ref;
381+
if (!refs_read_ref(get_main_ref_store(revs->repo), opts->ref, &oid))
382+
oidcpy(&old_oid, &oid);
383+
else
384+
oidclr(&old_oid, revs->repo->hash_algo);
385+
} else {
386+
ref = advance ? advance : revert;
387+
oidcpy(&old_oid, &onto->object.oid);
388+
}
389+
367390
/* FIXME: Should allow replaying commits with the first as a root commit */
368391

369392
if (prepare_revision_walk(revs) < 0) {
@@ -399,7 +422,7 @@ int replay_revisions(struct rev_info *revs,
399422
kh_value(replayed_commits, pos) = last_commit;
400423

401424
/* Update any necessary branches */
402-
if (advance || revert)
425+
if (ref)
403426
continue;
404427

405428
for (decoration = get_name_decoration(&commit->object);
@@ -433,13 +456,9 @@ int replay_revisions(struct rev_info *revs,
433456
goto out;
434457
}
435458

436-
/* In --advance or --revert mode, update the target ref */
437-
if (advance || revert) {
438-
const char *ref = advance ? advance : revert;
439-
replay_result_queue_update(out, ref,
440-
&onto->object.oid,
459+
if (ref)
460+
replay_result_queue_update(out, ref, &old_oid,
441461
&last_commit->object.oid);
442-
}
443462

444463
ret = 0;
445464

replay.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ struct replay_revisions_options {
2424
*/
2525
const char *onto;
2626

27+
/*
28+
* Reference to update with the result of the replay. This will not
29+
* update any refs from `onto`, `advance`, or `revert`. Ignores
30+
* `contained`.
31+
*/
32+
const char *ref;
33+
2734
/*
2835
* Starting point at which to create revert commits; must be a branch
2936
* name. The branch will be updated to point to the revert commits.

t/t3650-replay-basics.sh

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,4 +495,70 @@ test_expect_success 'git replay --revert incompatible with --advance' '
495495
test_grep "cannot be used together" error
496496
'
497497

498+
test_expect_success 'using --onto with --ref' '
499+
git branch test-ref-onto topic2 &&
500+
test_when_finished "git branch -D test-ref-onto" &&
501+
502+
git replay --ref-action=print --onto=main --ref=refs/heads/test-ref-onto topic1..topic2 >result &&
503+
504+
test_line_count = 1 result &&
505+
test_grep "^update refs/heads/test-ref-onto " result &&
506+
507+
git log --format=%s $(cut -f 3 -d " " result) >actual &&
508+
test_write_lines E D M L B A >expect &&
509+
test_cmp expect actual
510+
'
511+
512+
test_expect_success 'using --advance with --ref' '
513+
git branch test-ref-advance main &&
514+
git branch test-ref-target main &&
515+
test_when_finished "git branch -D test-ref-advance test-ref-target" &&
516+
517+
git replay --ref-action=print --advance=test-ref-advance --ref=refs/heads/test-ref-target topic1..topic2 >result &&
518+
519+
test_line_count = 1 result &&
520+
test_grep "^update refs/heads/test-ref-target " result
521+
'
522+
523+
test_expect_success 'using --revert with --ref' '
524+
git branch test-ref-revert topic4 &&
525+
git branch test-ref-revert-target topic4 &&
526+
test_when_finished "git branch -D test-ref-revert test-ref-revert-target" &&
527+
528+
git replay --ref-action=print --revert=test-ref-revert --ref=refs/heads/test-ref-revert-target topic4~1..topic4 >result &&
529+
530+
test_line_count = 1 result &&
531+
test_grep "^update refs/heads/test-ref-revert-target " result
532+
'
533+
534+
test_expect_success '--ref is incompatible with --contained' '
535+
test_must_fail git replay --onto=main --ref=refs/heads/main --contained topic1..topic2 2>err &&
536+
test_grep "cannot be used together" err
537+
'
538+
539+
test_expect_success '--ref with nonexistent fully-qualified ref' '
540+
test_when_finished "git update-ref -d refs/heads/new-branch" &&
541+
542+
git replay --onto=main --ref=refs/heads/new-branch topic1..topic2 &&
543+
544+
git log --format=%s -2 new-branch >actual &&
545+
test_write_lines E D >expect &&
546+
test_cmp expect actual
547+
'
548+
549+
test_expect_success '--ref must be a valid refname' '
550+
test_must_fail git replay --onto=main --ref="refs/heads/bad..ref" topic1..topic2 2>err &&
551+
test_grep "is not a valid refname" err
552+
'
553+
554+
test_expect_success '--ref requires fully qualified ref' '
555+
test_must_fail git replay --onto=main --ref=main topic1..topic2 2>err &&
556+
test_grep "is not a valid refname" err
557+
'
558+
559+
test_expect_success '--onto with --ref rejects multiple revision ranges' '
560+
test_must_fail git replay --onto=main --ref=refs/heads/topic2 ^topic1 topic2 topic4 2>err &&
561+
test_grep "cannot be used with multiple revision ranges" err
562+
'
563+
498564
test_done

0 commit comments

Comments
 (0)