Hi Hilco,

On Mon, 19 Feb 2018, Hilco Wijbenga wrote:

> On Mon, Feb 19, 2018 at 2:36 PM, brian m. carlson
> <sand...@crustytoothpaste.net> wrote:
> > On Mon, Feb 19, 2018 at 11:35:25AM -0800, Hilco Wijbenga wrote:
> >> So a scenario like this:
> >>
> >> my-branch : X -> A -> B -> C -> D -> E -> F -> G
> >> base-branch : X -> Y
> >>
> >> git rebase --onto base-branch HEAD~7
> >> commit A --> conflicts
> >> ... lots of work ...
> >> commit B --> conflicts
> >> ... lots of work ...
> >> commit C (Git handles conflicts)
> >> commit D (no conflict)
> >> commit E --> conflicts
> >> ... er, that should have been fixed in commit C
> >>
> >> How do I keep all the work I did for commits A and B? I get the
> >> impression that rerere does not help here because I did not finish the
> >> rebase succesfully (and that makes perfect sense, of course). Is there
> >> a way at this point in the rebase to "go back" to commit C (so without
> >> "git rebase --abort")?
> >
> > What I do in this case is I unstage all the changes from the index, make
> > the change that should have gone into commit C, use git commit --fixup
> > (or --squash), and then restage the rest of the changes and continue
> > with the rebase.  I can then use git rebase -i --autosquash afterwards
> > to insert the commit into the right place.
> 
> Yes, that's essentially what I end up doing too. Obviously, in cases
> like this, Murphy likes to drop by so commit D will have made changes
> to the same files as commit C and you can't cleanly move the fix-up
> commit to commit C. :-( I had hoped there might be an easier/cleaner
> way to do it.

I am a heavy user of interactive rebase. Which is unsurprising, given that
I implemented the first version of it.

And I find myself in the same situation quite often (as of recent, more
often when doing complicated rebases involving non-linear commit
topologies, but I digress).

Being that familiar with the internals of the interactive rebase command
gives me the opportunity to do "clever" things.

My number one strategy is to mix interactive rebase with cherry-pick: I
`git reset --hard HEAD^` (which refers to the rewritten C), fix up the
commit as intended, then look at the latest interactive rebase command
listed in the output of `git status`, then determine the commit range of
commits I want to replay on top and then call `git cherry-pick <range>`
(in your case, that would be `git cherry-pick C..E`.

Admittedly, this strategy is a bit cumbersome because a lot of
book-keeping is performed by my working memory instead of Git.

When I am particularly tired and overworked (and therefore know that my
working memory is less useful than usual), I therefore resort to my
second-favorite strategy: U use the `done` file.

I literally copy parts of $GIT_DIR/rebase-merge/done to the beginning of
$GIT_DIR/rebase-merge/git-rebase-todo (the most convenient way to open the
latter is `git rebase --edit-todo`). In your case, those would be the
`pick` lines cherry-picking D and E. Then, as before, `git reset --hard
<commit>` (where I look up the `<commit>` using an aliased version of `git
log --graph --oneline --left-right --boundary`), amend the commit, and
then `git rebase --continue`.

It might be even possible to design a new subcommand for the interactive
rebase to facilitate a variation of this strategy (possibly even making
use of the fact that the interactive rebase accumulates mappings between
the original commits and the rewritten ones in
$GIT_DIR/rebase-merge/rewritten-list, intended for use in the post-rewrite
hook).

Ciao,
Johannes

Reply via email to