Unfortunately,
git rebase
just doesn't cut it for this. A while back I wrote git rebase-tree
that would rebase a whole branched tree from one root to another (bringing all the branches along with), but it had a black eye in conflict handling, requiring a prompt for "continue/skip/abort". So I would need to open up a second terminal to git status
and resolve the conflict, etc (or more typically, run git conflicts
in emacs).
I had been meaning to redo git rebase-tree
with its own --continue
flag, but my experience with another custom function (git diff-mail
, which I use at work to prevent the changelist mailer from counting all the earlier changes that were already counted in a previous changelist) taught me that this can cause problems, as I often needed to git rebase --continue; diff-mail --continue
, plus --abort
often got confusing and sometimes actually clobbered my real changes (fortunately they've been easy to reconstruct so far). But it occurred to me that git rebase
already has a queue, and with some clever manipulation, I can make it do what I want.
Enter
git ir
.
This function takes a list of branches, a commit to rebase them onto, and optionally a commit to rebase them from (in case they're already on top of the destination). It sets up an interactive rebase session but completely ignores the plan
git
prepares, instead using its own plan that includes a few additional commands in the rebase plan.
# Extended Commands: # !, exec! = mandatory command (the rest of the line), reinserted on failure # b, branch = sets the named branch to the current commit # (, push = pushes the current commit onto the stack # ), pop = pops the current commit from the stackThe initial plan is effectively equivalent to
git rebase-tree
(though slightly more permissive). But with some quick edits (adding or removing parentheses, for instance) a tree of single-commit branches all off the same base can be instantly converted into a chain of dependent branches, and vice versa.
Once the initial plan is complete, it's ordinary
git rebase
the rest of the way, so when conflicts arise, it's back to the normal git rebase --continue
(or --skip
or --abort
) workflow to handle them! Aborting happens for free. Moreover, no branches are moved until all commits have been successful rebased, so aborting before that brings everything exactly back to where it started. Finally, if you don't want it to be interactive, just call it with ::
(which I alias to 'EDITOR=: '
).
4 comments:
Let me see if I understand what's going on.
Let's say my repo has a master branch, as well as A, B, and C branches, and then A.1 and A.2 based off of A, and A.1.a and A.1.b based off of A.1.
To sync up B with upstream, you'd pull origin from master, checkout B, and then git rebase master.
You can do that with A, too, but then you run into problems with A.1, etc., as all of their commits are based off of the original A and not the rebased A.
Does git ir --onto master while on A automatically rebase all of its children? Or is the syntax different…the two usage strings in your code don't match :-), so I'm a bit confused.
It also appears that git imerge has a potential solution to this problem (search for "rebase with history"), though it isn't its main focus, and I've not used it.
Great work.
Only problem I ran into is that it barfs with "Could not execute editor." if the GIT_EDITOR environment variable isn't set.
@David - I'm not sure what things looked like back in March, but now I would write `git ir --onto master --from master@{1} --all`.
@Patrick - Git tries to be smart with $EDITOR - it has at least three different environment variables it uses, with some priority ordering (GIT_EDITOR, EDITOR, and something else), and so I haven't found a good way to reproduce this faithfully - I'm open to suggestions.
Post a Comment