Skip to content

Commit 97e3436

Browse files
committed
Merge branch 'tc/replay-ref' into seen
The experimental `git replay` command learned the `--ref=<ref>` option to allow specifying which ref to update, overriding the default behavior. Seems to break CI. cf. <xmqqjyuynv99.fsf@gitster.g> * tc/replay-ref: replay: allow to specify a ref with option --ref replay: use stuck form in documentation and help message builtin/replay: mark options as not negatable
2 parents 8a81e7d + 23d83f8 commit 97e3436

5 files changed

Lines changed: 157 additions & 34 deletions

File tree

Documentation/git-replay.adoc

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t
99
SYNOPSIS
1010
--------
1111
[verse]
12-
(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) [--ref-action[=<mode>]] <revision-range>
12+
(EXPERIMENTAL!) 'git replay' ([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)
13+
[--ref=<ref>] [--ref-action=<mode>] <revision-range>
1314

1415
DESCRIPTION
1516
-----------
@@ -26,23 +27,23 @@ THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
2627
OPTIONS
2728
-------
2829

29-
--onto <newbase>::
30+
--onto=<newbase>::
3031
Starting point at which to create the new commits. May be any
3132
valid commit, and not just an existing branch name.
3233
+
3334
When `--onto` is specified, the branch(es) in the revision range will be
3435
updated to point at the new commits, similar to the way `git rebase --update-refs`
3536
updates multiple branches in the affected range.
3637

37-
--advance <branch>::
38+
--advance=<branch>::
3839
Starting point at which to create the new commits; must be a
3940
branch name.
4041
+
4142
The history is replayed on top of the <branch> and <branch> is updated to
4243
point at the tip of the resulting history. This is different from `--onto`,
4344
which uses the target only as a starting point without updating it.
4445

45-
--revert <branch>::
46+
--revert=<branch>::
4647
Starting point at which to create the reverted commits; must be a
4748
branch name.
4849
+
@@ -65,6 +66,16 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
6566
Update all branches that point at commits in
6667
<revision-range>. Requires `--onto`.
6768

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+
6879
--ref-action[=<mode>]::
6980
Control how references are updated. The mode can be:
7081
+
@@ -79,8 +90,8 @@ The default mode can be configured via the `replay.refAction` configuration vari
7990

8091
<revision-range>::
8192
Range of commits to replay; see "Specifying Ranges" in
82-
linkgit:git-rev-parse[1]. In `--advance <branch>` or
83-
`--revert <branch>` mode, the range should have a single tip,
93+
linkgit:git-rev-parse[1]. In `--advance=<branch>` or
94+
`--revert=<branch>` mode, the range should have a single tip,
8495
so that it's clear to which tip the advanced or reverted
8596
<branch> should point. Any commits in the range whose changes
8697
are already present in the branch the commits are being
@@ -127,22 +138,22 @@ EXAMPLES
127138
To simply rebase `mybranch` onto `target`:
128139

129140
------------
130-
$ git replay --onto target origin/main..mybranch
141+
$ git replay --onto=target origin/main..mybranch
131142
------------
132143

133144
The refs are updated atomically and no output is produced on success.
134145

135146
To see what would be updated without actually updating:
136147

137148
------------
138-
$ git replay --ref-action=print --onto target origin/main..mybranch
149+
$ git replay --ref-action=print --onto=target origin/main..mybranch
139150
update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}
140151
------------
141152

142153
To cherry-pick the commits from mybranch onto target:
143154

144155
------------
145-
$ git replay --advance target origin/main..mybranch
156+
$ git replay --advance=target origin/main..mybranch
146157
------------
147158

148159
Note that the first two examples replay the exact same commits and on
@@ -154,7 +165,7 @@ What if you have a stack of branches, one depending upon another, and
154165
you'd really like to rebase the whole set?
155166

156167
------------
157-
$ git replay --contained --onto origin/main origin/main..tipbranch
168+
$ git replay --contained --onto=origin/main origin/main..tipbranch
158169
------------
159170

160171
All three branches (`branch1`, `branch2`, and `tipbranch`) are updated
@@ -165,7 +176,7 @@ commits to replay using the syntax `A..B`; any range expression will
165176
do:
166177

167178
------------
168-
$ git replay --onto origin/main ^base branch1 branch2 branch3
179+
$ git replay --onto=origin/main ^base branch1 branch2 branch3
169180
------------
170181

171182
This will simultaneously rebase `branch1`, `branch2`, and `branch3`,
@@ -176,7 +187,7 @@ that they have in common, but that does not need to be the case.
176187
To revert commits on a branch:
177188

178189
------------
179-
$ git replay --revert main topic~2..topic
190+
$ git replay --revert=main topic~2..topic
180191
------------
181192

182193
This reverts the last two commits from `topic`, creating revert commits on
@@ -188,6 +199,16 @@ NOTE: For reverting an entire merge request as a single commit (rather than
188199
commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE`
189200
which can avoid unnecessary merge conflicts.
190201

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+
191212
GIT
192213
---
193214
Part of the linkgit:git[1] suite

builtin/replay.c

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,25 +84,33 @@ int cmd_replay(int argc,
8484

8585
const char *const replay_usage[] = {
8686
N_("(EXPERIMENTAL!) git replay "
87-
"([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) "
88-
"[--ref-action[=<mode>]] <revision-range>"),
87+
"([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
88+
"[--ref=<ref>] [--ref-action=<mode>] <revision-range>"),
8989
NULL
9090
};
9191
struct option replay_options[] = {
92-
OPT_STRING(0, "advance", &opts.advance,
93-
N_("branch"),
94-
N_("make replay advance given branch")),
95-
OPT_STRING(0, "onto", &opts.onto,
96-
N_("revision"),
97-
N_("replay onto given commit")),
9892
OPT_BOOL(0, "contained", &opts.contained,
9993
N_("update all branches that point at commits in <revision-range>")),
100-
OPT_STRING(0, "revert", &opts.revert,
101-
N_("branch"),
102-
N_("revert commits onto given branch")),
103-
OPT_STRING(0, "ref-action", &ref_action,
104-
N_("mode"),
105-
N_("control ref update behavior (update|print)")),
94+
OPT_STRING_F(0, "onto", &opts.onto,
95+
N_("revision"),
96+
N_("replay onto given commit"),
97+
PARSE_OPT_NONEG),
98+
OPT_STRING_F(0, "advance", &opts.advance,
99+
N_("branch"),
100+
N_("make replay advance given branch"),
101+
PARSE_OPT_NONEG),
102+
OPT_STRING_F(0, "revert", &opts.revert,
103+
N_("branch"),
104+
N_("revert commits onto given branch"),
105+
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),
110+
OPT_STRING_F(0, "ref-action", &ref_action,
111+
N_("mode"),
112+
N_("control ref update behavior (update|print)"),
113+
PARSE_OPT_NONEG),
106114
OPT_END()
107115
};
108116

@@ -122,6 +130,8 @@ int cmd_replay(int argc,
122130
opts.contained, "--contained");
123131
die_for_incompatible_opt2(!!opts.revert, "--revert",
124132
opts.contained, "--contained");
133+
die_for_incompatible_opt2(!!opts.ref, "--ref",
134+
!!opts.contained, "--contained");
125135

126136
/* Parse ref action mode from command line or config */
127137
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
@@ -358,13 +358,15 @@ int replay_revisions(struct rev_info *revs,
358358
struct commit *last_commit = NULL;
359359
struct commit *commit;
360360
struct commit *onto = NULL;
361-
struct merge_options merge_opt;
361+
struct merge_options merge_opt = { 0 };
362362
struct merge_result result = {
363363
.clean = 1,
364364
};
365365
bool detached_head;
366366
char *advance;
367367
char *revert;
368+
const char *ref;
369+
struct object_id old_oid;
368370
enum replay_mode mode = REPLAY_MODE_PICK;
369371
int ret;
370372

@@ -375,6 +377,27 @@ int replay_revisions(struct rev_info *revs,
375377
set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto,
376378
&detached_head, &advance, &revert, &onto, &update_refs);
377379

380+
if (opts->ref) {
381+
struct object_id oid;
382+
383+
if (update_refs && strset_get_size(update_refs) > 1) {
384+
ret = error(_("'--ref' cannot be used with multiple revision ranges"));
385+
goto out;
386+
}
387+
if (check_refname_format(opts->ref, 0) || !starts_with(opts->ref, "refs/")) {
388+
ret = error(_("'%s' is not a valid refname"), opts->ref);
389+
goto out;
390+
}
391+
ref = opts->ref;
392+
if (!refs_read_ref(get_main_ref_store(revs->repo), opts->ref, &oid))
393+
oidcpy(&old_oid, &oid);
394+
else
395+
oidclr(&old_oid, revs->repo->hash_algo);
396+
} else {
397+
ref = advance ? advance : revert;
398+
oidcpy(&old_oid, &onto->object.oid);
399+
}
400+
378401
if (prepare_revision_walk(revs) < 0) {
379402
ret = error(_("error preparing revisions"));
380403
goto out;
@@ -406,7 +429,7 @@ int replay_revisions(struct rev_info *revs,
406429
kh_value(replayed_commits, pos) = last_commit;
407430

408431
/* Update any necessary branches */
409-
if (advance || revert)
432+
if (ref)
410433
continue;
411434

412435
for (decoration = get_name_decoration(&commit->object);
@@ -440,13 +463,9 @@ int replay_revisions(struct rev_info *revs,
440463
goto out;
441464
}
442465

443-
/* In --advance or --revert mode, update the target ref */
444-
if (advance || revert) {
445-
const char *ref = advance ? advance : revert;
446-
replay_result_queue_update(out, ref,
447-
&onto->object.oid,
466+
if (ref)
467+
replay_result_queue_update(out, ref, &old_oid,
448468
&last_commit->object.oid);
449-
}
450469

451470
ret = 0;
452471

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
@@ -499,4 +499,70 @@ test_expect_success 'git replay --revert incompatible with --advance' '
499499
test_grep "cannot be used together" error
500500
'
501501

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

0 commit comments

Comments
 (0)