From: Siddharth Asthana Date: Sat, 14 Mar 2026 07:18:55 GMT Subject: Re: [PATCH RFC] git-replay: implement subcommands Message-ID: <5370f3b2-2c4a-4d9d-904d-2a8f6094b6e1@gmail.com> In-Reply-To: <87v7ezsnqf.fsf@iotcl.com> On 13/03/26 21:52, Toon Claes wrote: > Justin Tobler writes: > >> On 26/03/09 08:30PM, Toon Claes wrote: >>> git-replay(1) has various operation modes. The mode depends on which of >>> the options `--onto`, `--advance`, or `--revert` is given. These options >>> are mutually exclusive. This usage pattern is counterintuitive and >>> uncommon for Git commands to behave this way. >>> >>> Implement subcommands into git-replay(1): >>> >>> * `rebase`: This replaces what `--onto=` used to do. >> >> I'm a bit confused by this. It appears that the "rebase" subcommand >> still requires the `--onto` option so it doesn't seem to really be >> replacing anything. I assume we are tyring to break these operations >> into distinct categories which seems reasonable. > > Okay, maybe I should be more verbose about the problem I'm trying to > solve. > > Let's start with the existing option `--onto`. This implies a "rebase" > operation. The argument to this option is a revision which acts as the > base for the rebase. On this base the commits in the revision-range are > replayed. > > In the end, git-replay(1) takes the ref from the upper boundary of this > revision-range, and updates that to the result of the rebase. > > A usage example: > > $ git replay --onto=master master..my-branch > > With option --ref-action=print, you'll get: > > update refs/heads/my-branch aaabbbccc 000111222 > > (using abbreviated OIDs for simplification in this email) > > So 000111222 would be the commit my-branch is pointing to before the > git-replay(1), and aaabbbccc the new commit. > > Now looking at `--advance`, this works differently: > > $ git replay --advance=other-branch master..my-branch > > With option --ref-action=print, you'll get: > > update refs/heads/other-branch 888999000 444555666 > > As you can see, the argument of --advance is the ref that gets updated. > > So this command would be identical to: > > $ git replay --advance=other-branch master..000111222 > > If we try to do use the commit OID in the revision-range when using > --onto: > > $ git replay --onto=master master..000111222 > > Nothing (noticable) happens. git-replay(1) does the replay, but doesn't > know which ref to update. > > This assymetry between --onto and --revert & --advance is the main issue > I'm trying to resolve with this proposal. > > >>> * `pick`: This replaces what `--advance=` used to do. >>> * `revert`: This replaces what `--revert=` used to do. >>> >>> Option `--onto` is still accepted. It's mandatory for the `rebase` >>> subcommand and needs to be used in the exact same way. >>> >>> Option `--ref` is added and required for the `pick` and `revert` >>> subcommands. This replaces what `--advance` and `--revert` used to do, >>> but as a single uniform option for all subcommands. >>> >>> The `rebase` subcommand also accepts option `--ref`, and when given this >>> is the ref that's updated with the outcome of the git-replay(1) command. >>> Thus following commands are identical: >>> >>> $ git replay rebase --onto=master master..branch-1 >>> >>> $ git replay rebase --onto=master master..branch-1^{0} --ref=refs/heads/branch-1 >>> >>> In the second example the upper boundary of the revision range is peeled >>> down to a commit (using '^{0}'). Without option `--ref`, git-replay(1) >>> doesn't know which ref to update, that's why `--ref` is passed >>> explicitly. >>> >>> For the subcommands `pick` and `revert` it's also possible to combine >>> `--ref` and `--onto`. Here are again two identical examples: >>> >>> $ git replay pick --onto=branch-1 master..aabbccdd >>> >>> $ git replay pick --onto=branch-1^{0} master..aabbccdd --ref=refs/heads/branch-1 >>> >>> In the latter the argument for `--onto` is peeled down to a commit, so >>> the command doesn't know which ref to update. To inform git-replay(1) >>> which refs should be updated, it's passed explicitly as option `--ref`. >>> >>> Signed-off-by: Toon Claes >>> --- >>> In the patch series by Siddharth Asthana[1] the option `--revert` is >>> added to git-replay(1). This is implemented as option `--revert`, next >>> to the existing options `--advance` and `--onto`. >>> >>> The usage of these options is mutually exclusive, so the user can only >>> use one of them, and depending on which one, git-replay(1) selects a >>> "mode of operating". >>> >>> Various people have raised this behavior is somewhat confusing. In this >>> series we attempt to make the usage of git-replay(1) more intuitive and >>> user-friendly by implementing the modes as subcommands. >> >> Ok, subcommands for git-replay(1) seem like they could be a good fit >> here. > > <3 > >> >>> This patch is submitted as an RFC to gather feedback about the design. >>> All changes are implemented as a single patch right now, and thus >>> reviewing the changes might be challenging. When we got people aligned >>> on the direction, I'll work toward cleaner patches. >>> >>> These changes are based on 'master' at 864f55e190 (The second batch, >>> 2026-02-09) with the patches of Siddharth[1] applied: 'sa/replay-revert' >>> at f79189a653 (replay: add --revert mode to reverse commit changes, >>> 2026-02-19) >>> >>> [1]: 20260218234215.89326-3-siddharthasthana31@gmail.com >>> --- >>> Documentation/git-replay.adoc | 124 ++++++++++++++++---------- >>> builtin/replay.c | 150 ++++++++++++++++++++++++------- >>> replay.c | 66 +++++++------- >>> replay.h | 31 +++---- >>> t/t3650-replay-basics.sh | 199 +++++++++++++++++++++++------------------- >>> 5 files changed, 349 insertions(+), 221 deletions(-) >>> >>> diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc >>> index ffdf790278..a7e8dac23f 100644 >>> --- a/Documentation/git-replay.adoc >>> +++ b/Documentation/git-replay.adoc >>> @@ -8,8 +8,13 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t >>> >>> SYNOPSIS >>> -------- >>> -[verse] >>> -(EXPERIMENTAL!) 'git replay' ([--contained] --onto | --advance | --revert ) [--ref-action[=]] ... >> >> Do we intent to remove the experimental marker? > > Yeah, I did. I don't like them in this synopsis. It seems git-replay(1) > is the only one doing this, so I'd like to get rid of it. > > Doing this should end up in a separate commit. > >>> +[synopsis] >>> +git replay rebase --onto [--ref ] [--contained] >>> + [--ref-action[=]] >>> +git replay pick --ref [--onto ] >>> + [--ref-action[=]] >>> +git replay revert --ref [--onto ] >>> + [--ref-action[=]] >> >> Subcommands with required options like this feel quite bad IMO and I'm >> not sure it makes it much more intuitive. > > Okay, I did some looking up, and maybe you're right, I couldn't find any > other command that has required options. It seems all commands have some > kind of "default behavior" when no options are given. > >> I guess subcommands do make it easier to convey which options pertain >> to which operation. Maybe it would be better if required arguments >> remained positional though? > > I'm not sure that's better: > > [synopsis] > git replay rebase [--ref ] [--contained] > [--ref-action[=]] > git replay pick [--onto ] > [--ref-action[=]] > git replay revert [--onto ] > [--ref-action[=]] > > I don't think is better because the required argument for 'rebase' is > used differently than 'pick' and 'revert', as explained above. > > I guess my main gripe with the current options is the naming: `--onto` > to rebase, `--advance` to cherry-pick, and `--revert` to revert. And > while the last one does sound intuitive, the argument to that option is > the branch you want to replay the reverted commits onto. So the argument > isn't *what* you're reverting, it's *where* you're reverting to. > > With my proposal I wanted to make that more clear. > > This proposal is trying to be not too disruptive (for example, for > rebase you only need to a add `rebase`), but that's maybe not a good > idea. So an alternative could be: on top of this proposal, make both > `--onto` and `--ref` required. In various cases the user will provide > the exact same argument to both options, but since git-replay(1) is a > plumbing command, we can consider this is acceptable? > > And now, while writing this, I was thinking about yet another proposal. > Because --advance and --revert are pretty similar. Why is --revert not > an additional option you can add when using --advance. So instead of: > > git replay --revert=my-branch master..other-branch > git replay --advance=my-branch --revert master..other-branch > > @Siddharth, have you considered that? Yeah, I though about this early on but it didn't feel right. --advance means "cherry-pick onto this branch and move it forward", so combining it with --revert reads weird -- you would be "advancing" by reverting. They also work quite differently under the hood. Revert uses newest-first ordering (revs.reverse = 0) while advance uses oldest-first, and revert sets author to the current user (author = NULL in create_commit) instead of preserving the original. So they really are different modes, kind of like how cherry-pick and revert are separate commands even though the merge machinery is shared. On the subcommand RFC -- I think this is a good direction and it would clean up the asymmetry you pointed out between --onto and --advance/--revert. My v4 should be a clean base for it (which your RFC already builds on). Thanks, Siddharth > >> Also, using these subcommands appears to be required now which is a >> breaking change compared to before. The command is experimental, so this >> may be fine, but should probably be more directly mentioned. > > Sure, I can do that when I clean up the commits. >