Moving a git branch to a new base

Thursday 10 February 2022

Suppose you have some work on a git branch that you started from one branch, and you want to move that work to be based on a different branch, as if you had started from there originally. The git rebase command gives you the tools to do it, but it’s complicated, and I can never remember the details, so I finally figured it out and made an alias to do it.

In this post I’ll use an example where I’m working on branch “my-work”, which I started from “old-home” and I want to move it to “new-home”. My initial state looks like this:

% git log --oneline --decorate --graph --all
* a8ab80e (HEAD -> my-work) my work edit 2
* 5f66562 my work edit 1
| * 9682932 (old-home) old-home edit 3
|/
* 98d09e4 old-home edit 2
* a6fa334 old-home edit 1
| * e409e95 (new-home) new-home edit 2
| * 7d3ca09 new-home edit 1
|/
* 1b008d5 (main) main edit 2
* e9152aa main edit 1

The git tool to perform a move like this is “git rebase --onto”. The general form of the command needs three pieces of information, but the third is the branch to move, which defaults to the current branch, so we can just omit that. The other two items are: where you want to snip the current branch from, and where you want to graft it onto. The command looks like this:

git rebase --onto=<graft-onto> <snip-from> [<branch-to-move>]

In our example, we want to snip the branch from the point where it started on old-home. The “git merge-base” command can tell us the commit where two branches diverge, so it’s perfect for this:

% git merge-base old-home @
98d09e4b4d3ca45e3e03cf27386f7dd01f0662a8

Putting this together in one command looks like this:

% git rebase --onto new-home $(git merge-base old-home @)
Successfully rebased and updated refs/heads/my-work.

The result is just what we wanted:

% git log --oneline --decorate --graph --all
* df22106 (HEAD -> my-work) my work edit 2
* 9ac8707 my work edit 1
* e409e95 (new-home) new-home edit 2
* 7d3ca09 new-home edit 1
| * 9682932 (old-home) old-home edit 3
| * 98d09e4 old-home edit 2
| * a6fa334 old-home edit 1
|/
* 1b008d5 (main) main edit 2
* e9152aa main edit 1

But that command is complicated to type, so I made an alias in my .gitconfig:

[alias]
movebranch = "!f() { \
    : git checkout ; \
    git rebase --onto $2 $(git merge-base $1 @); \
}; f"

Now our branch move command would look like this:

% git movebranch old-home new-home
Successfully rebased and updated refs/heads/my-work.

And the result is the same:

% git log --oneline --decorate --graph --all
* df22106 (HEAD -> my-work) my work edit 2
* 9ac8707 my work edit 1
* e409e95 (new-home) new-home edit 2
* 7d3ca09 new-home edit 1
| * 9682932 (old-home) old-home edit 3
| * 98d09e4 old-home edit 2
| * a6fa334 old-home edit 1
|/
* 1b008d5 (main) main edit 2
* e9152aa main edit 1
» 6 reactions

Comments

[gravatar]

Thanks for sharing. I feel a bit better about my git frustrations knowing that you struggled to remember how to use it too. :)

[gravatar]

Hi, I’d love to get this working, but on my widows machina I always get this error:

$ git movebranch main staging f() { : git checkout git rebase –onto $2 $(git merge-base $1 @) }; f: -c: line 2: syntax error: unexpected end of file

I am suspicious of the $(git merge-base $1 @) but despite my best efforts, I can’t get it working. Any tips?

[gravatar]

Philip: I ran into the same problem with git 2.34.1. It looks like there’s a missing semi-colon. But after adding it, the “git checkout” line was doing something that meant the alias didn’t _do_ anything.

I ended up with the following, which seems to work:

[alias]
  movebranch = "!f() { \
    git rebase --onto $2 $(git merge-base $1 @); \
}; f"
[gravatar]

@Rebecca: you are right, there was a missing semicolon (now fixed). That first line starting with a colon is an indication of how to autocomplete the command. The movebranch alias will tab-complete the same way “git checkout” does.

[gravatar]

@Ned: the updated alias needs _another_ semicolon.

git rebase –onto $2 $(git merge-base $1 @) \

Should be:

git rebase –onto $2 $(git merge-base $1 @); \

[gravatar]

Yes, you are right! My Python brain wants to skip semicolons, my bad.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.