Rebasing a tree (a commit/branch and all its children) [duplicate]

In these cases, a nice trick is to merge (join) all the branches to be
moved into a final commit node. After that, use rebase with the
--preserve-merges option for moving the resulting enclosed subtree
(set of branches).

Creating a closed subtree that contains all the branches, exposes
2 nodes (start and end) that are used as input parameters for the rebase
command.

The end of the closed subtree is an artificial node that may
be deleted after moving the tree, as well as the other nodes that
may have been created for merging other branches.

Let’s see the following case.

The developer wants to insert a new commit (master) into
other 3 development branches (b11, b2, b3). One of these (b11) is
a merge of a feature branch b12, both based on b1. The other 2
branches (b2, b3) diverge.

Of course the developer could cherry-pick that new commit into
each one of these branches, but the developer may prefer not to
have the same commit in 3 different branches, but just 1 commit
before the branches diverge.

* baa687d (HEAD -> master) new common commit
| * b507c23 (b11) b11
| *   41849d9 Merge branch 'b12' into b11
| |\
| | * 20459a3 (b12) b12
| * | 1f74dd9 b11
| * | 554afac b11
| * | 67d80ab b11
| "https://stackoverflow.com/" * b1cbb4e b11
| * 18c8802 (b1) b1
"https://stackoverflow.com/" * 7b4e404 (b2) b2
| | * 6ec272b (b3) b3
| | * c363c43 b2 h
| "https://stackoverflow.com/" * eabe01f header
|/
* 9b4a890 (mirror/master) initial
* 887d11b init

For that, the first step is to create a common merge commit that
includes the 3 branches. For that a temporary branch called pack
is used.

Merging into pack may create conflicts, but that is not important
since these merges will later be discarded. Just instruct git to
automatically solve them, adding options -s recursive -Xours.

$ git checkout -b pack b11 # create new branch at 'b11' to avoid losing original refs
$ git merge -s recursive -Xours b2 # merges b2 into pack
$ git merge -s recursive -Xours b3 # merges b3 into pack

This is the whole tree after merging everything into the pack branch:

*   b35a4a7 (HEAD -> pack) Merge branch 'b3' into pack
|\
| * 6ec272b (b3) b3
| * c363c43 b2 h
* |   60c9b7c Merge branch 'b2' into pack
|\ \
| * | 7b4e404 (b2) b2
| "https://stackoverflow.com/" * eabe01f header
* | b507c23 (b11) b11
* |   41849d9 Merge branch 'b12' into b11
|\ \
| * | 20459a3 (b12) b12
* | | 1f74dd9 b11
* | | 554afac b11
* | | 67d80ab b11
|/ /
* | b1cbb4e b11
* | 18c8802 (b1) b1
"https://stackoverflow.com/" * baa687d (master) new common commit
|/
* 9b4a890 initial
* 887d11b init

Now it’s time to move the subtree that has been created. For that
the following command is used:

$ git rebase --preserve-merges --onto master master^ pack

The reference master^ means the commit before master (master‘s
parent), 9b4a890 in this case. This commit is NOT rebased, it is
the origin of the 3 rebased branches. And of course, pack is the final reference of the
whole subtree.

There may be some merge conflicts during the rebase. In case there had
already been conflicts before doing the merge these will arise again. Be
sure to solve them the same way. For the the artificial commits created
for merging into the temporary node pack, don’t bother and
solve them automatically.

After the rebase, that would be the resulting tree:

*   95c8d3d (HEAD -> pack) Merge branch 'b3' into pack
|\
| * d304281 b3
| * ed66668 b2 h
* |   b8756ee Merge branch 'b2' into pack
|\ \
| * | 8d82257 b2
| "https://stackoverflow.com/" * e133de9 header
* | f2176e2 b11
* |   321356e Merge branch 'b12' into b11
|\ \
| * | c919951 b12
* | | 8b3055f b11
* | | 743fac2 b11
* | | a14be49 b11
|/ /
* | 3fad600 b11
* | c7d72d6 b1
|/
* baa687d (master) new common commit
|
* 9b4a890 initial
* 887d11b init

Sometimes the old branch references may not be relocated (even if
the tree relocates without them). In that case you can recover
or change some reference by hand.

It’s also time to undo the pre-rebase merges that made possible
rebasing the whole tree. After some delete, reset/checkout, this
is the tree:

* f2176e2 (HEAD -> b11) b11
*   321356e Merge branch 'b12' into b11
|\
| * c919951 (b12) b12
* | 8b3055f b11
* | 743fac2 b11
* | a14be49 b11
|/
* 3fad600 b11
* c7d72d6 (b1) b1
| * d304281 (b3) b3
| * ed66668 b2 h
| | * 8d82257 (b2) b2
| "https://stackoverflow.com/" * e133de9 header
|/
* baa687d (mirror/master, mirror/HEAD, master) new common commit
* 9b4a890 initial
* 887d11b init

Which is exactly what the developer wanted to achieve: the commit
is shared by the 3 branches.

Leave a Comment