threads / discuss / 57453

rewinding interactive rebases

Subject: rewinding interactive rebases

## tl;dr

4 messages between Feb 21, 2022 and Mar 7, 2022.

replies: 3people: 3as markdown or json

Oswald Buddenhagen· Feb 21, 2022, 19:06 UTC · lore
heya,

this post is basically a continuation of https://public-inbox.org/git/CAE1pOi1XtrWqG7mOdrNt10YoZG0LOAB7i9cc1Gi8oWhULxE57A@mail.gmail.com/ ("Is there any way to "interrupt" a rebase?"), but i thought it's probably better to start a new thread rather than to revive a zombie.

the basic problem is that during significant reworks of (complex) branches, one will inevitably find oneself in the situation of wanting to "go back" in the process. fixup commits aren't always optimal, as subsequent re-ordering may cause conflicts. doing the amend in the right place to start with cuts away one of the conflict resolutions.

the solution to this is actually rather simple, as demonstrated by the attached script.

somebody feels like rewriting this as a nice 'git rebase --rewind' subcommand?

thanks

#! /bin/bash # Copyright (C) 2022 Oswald Buddenhagen <oswald.buddenhagen@gmx.de> # # You may use this file under the terms of the 3-clause BSD license.

gitdir=$(git rev-parse --git-dir) || exit
if ! git diff --quiet HEAD; then
    echo "Working directory or index not clean." >&2
    exit 1
fi
rbdir=$gitdir/rebase-merge
if ! test -d "$rbdir"; then
    echo "No rebase ongoing?" >&2
    exit 1
fi

onto=$(<"$rbdir/onto") || exit todo=$rbdir/git-rebase-todo

git log --reverse --pretty="tformat:pick %h %s" $onto.. > "$todo.new" || exit (echo "break"; echo; cat "$todo") >> "$todo.new" || exit

mv "$todo" "$todo.old" || exit
mv "$todo.new" "$todo" || exit
if ! git rebase --edit-todo; then
    mv "$todo.old" "$todo"
    exit 1
fi
if ! test -s "$todo"; then
    echo "Empty todo; canceling rewind." >&2
    mv "$todo.old" "$todo"
    exit 1
fi
rm "$todo.old"
: > "$rbdir/done"

git reset --hard $onto || exit git rebase --continue || exit

Erik Cervin Edin· Mar 1, 2022, 09:24 UTC · re: Oswald Buddenhagen · lore

Re: rewinding interactive rebases

On Tue, Mar 1, 2022 at 1:21 AM Oswald Buddenhagen <oswald.buddenhagen@gmx.de> wrote:

> did you mean to answer only privately?
No, mea culpa :(
Show 7 quoted lines
> On Mon, Feb 28, 2022 at 10:32:16PM +0100, Erik Cervin Edin wrote:
> >What's the connection to the original thread [...]?
> >
> that depends on what you want to interpret into the original thread,
> given that a bunch of use cases and solutions were mentioned. my
> solution is actually functionally identical to one of the later
> subthreads, as i found after the fact.

Sorry but it's a long thread and at least I am unsure which parts you are referring to and how your script fits into that.

> >Maybe you could explain it a bit in the context of the problem.
> >
> there really isn't much to it. the git log + git reset are the essence.
> just run it in the middle of an interactive rebase to see yourself.

Could you elaborate on the intended/usual flow here? Rebase => Get conflict => Fix conflict => run the script => ..??

Johannes Schindelin· Mar 7, 2022, 16:56 UTC · re: Erik Cervin Edin · lore

Re: rewinding interactive rebases

Hi Erik & Oswald,

it's not really obvious from the text quoted below what is being discussed here, so let me simply top-post some thoughts:

Problem statement: in the middle of an interactive rebase, the user would like to choose a different rebase target, without having to abort the rebase.

Suggested approach: implement support for `git rebase -i --nested --onto <commit> <base-commit>`, i.e. a `--nested` option.

The idea here is to generate a todo list (just like a fresh `git rebase -i --onto <commit> <base-commit>` would have done) and _prepend_ it to the current todo list.

To support a nested `git rebase --abort`, we would probably want to insert some marker after the newly-generated todo list (maybe a specially-formatted comment such as `## END NESTED ##`).

To support nested `git rebase --rebase-merges`, we would need to populate `state.labels` by reading `$GIT_DIR/rebase-merge/refs-to-delete` first thing when generating the todo list.

For good measure, we should probably populate `state.commit2label` with this list, too, looking up the respective OIDs via `refs/rewritten/<label>`.

I realize that this is quite dense information for anybody unfamiliar with the `sequencer.c` code, and that the idea is _not_ fully fleshed out, but I lack the time to implement it and wanted to get even just a half-finished idea out there.

Ciao, Johannes

On Tue, 1 Mar 2022, Erik Cervin Edin wrote:
Show 22 quoted lines
> On Tue, Mar 1, 2022 at 1:21 AM Oswald Buddenhagen
> <oswald.buddenhagen@gmx.de> wrote:
>
> > On Mon, Feb 28, 2022 at 10:32:16PM +0100, Erik Cervin Edin wrote:
> > >What's the connection to the original thread [...]?
> > >
> > that depends on what you want to interpret into the original thread,
> > given that a bunch of use cases and solutions were mentioned. my
> > solution is actually functionally identical to one of the later
> > subthreads, as i found after the fact.
>
> Sorry but it's a long thread and at least I am unsure which parts you
> are referring to and how your script fits into that.
>
> > >Maybe you could explain it a bit in the context of the problem.
> > >
> > there really isn't much to it. the git log + git reset are the essence.
> > just run it in the middle of an interactive rebase to see yourself.
>
> Could you elaborate on the intended/usual flow here?
> Rebase => Get conflict => Fix conflict => run the script => ..??
>
Oswald Buddenhagen· Mar 7, 2022, 19:08 UTC · re: Johannes Schindelin · lore

Re: rewinding interactive rebases

On Mon, Mar 07, 2022 at 05:56:22PM +0100, Johannes Schindelin wrote:
>Problem statement: in the middle of an interactive rebase, the user 
>would like to choose a different rebase target, without having to abort 
>the rebase.
>

it didn't even occur to me to actually change the base (note that my script has no --onto option, even though it wouldn't be that hard to do). that stems from that fact that i have *really* rarely (intentionally) made an interactive rebase without (the equivalent of) --keep-base.

for me, it's about noticing that i need to rewrite some earlier commits again before going forward.

>The idea here is to generate a todo list (just like a fresh `git rebase 
>-i --onto <commit> <base-commit>` would have done) and _prepend_ it to 
>the current todo list.
>
yeah, that's essentially what the script does.
>To support a nested `git rebase --abort`, we would probably want to insert
>some marker after the newly-generated todo list (maybe a
>specially-formatted comment such as `## END NESTED ##`).
>

in the few weeks i've been using the script so far (with a by now 80+ commit series), i'd have found nested abort useful about twice, while repeated "flat" rewinds are par for the course. i'm also slightly worried that i'd lose track of the stack and throw away too much (though strictly speaking that's actually not unique to nesting; it would just amplify the problem). my script already inserts a marker in the form of "break\n\n", and it gets really weird after several rewinds if i don't clean up. to make it less weird (and error-prone), one would have to actually start a new todo list, that is, actually throw the rest of the old todo list on a stack rather than prepending to it. so basically, choose between linear and nested, but don't mix the two.

← back to recent threads