Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

This is my current git tree:

A - H (master)
|
\- B - C - D - E (feature)
           |
           \- F (new)
           |
           \- G (other)

And I'd like to rebase the side branch so that it depends on H rather than A:

A - H (master)
    |
    \- B'- C'- D'- E'(feature)
               |
               \- F'(new)
               |
               \- G'(other)

Easy concept, hard to do automatically, it seems. This has already been asked here and here, but the proposed solutions are not working for me.

First, as pointed out in the former, the git branch output is not trivial to parse when the current branch is there (there is a * prepended). But that's not a stopper, in my case I can easily provide the names feature, new and other by hand, or make sure the current branch is master.

Then I tried with these commands:

git rebase --committer-date-is-author-date --preserve-merges --onto master feature^ feature
git rebase --committer-date-is-author-date --preserve-merges --onto master feature^ new
git rebase --committer-date-is-author-date --preserve-merges --onto master feature^ other

and I end up with:

A - H (master)
    |
    \- E'(feature)
    |
    \- B' - C' - D' - F'(new)
    |
    \- B" - C" - D" - G'(other)

Definitely not what I want! Or, if I use B^ instead of feature^, then I also get the B - C - D history in the feature branch.

So, any further suggestion on how to get this done more or less automatically?

EDIT: It sort of works with this:

git checkout feature
git merge other
git merge new
git rebase -p master feature

Now at least the tree looks correct, I just have to move the branch heads to their right commits before the merges... which could be done with:

git checkout master
git branch -f new feature^2
git branch -f feature feature^1
git branch -f other feature^2
git branch -f feature feature^1
like image 382
Jellby Avatar asked Jun 26 '13 08:06

Jellby


People also ask

What happens when you rebase a branch?

The Rebase Option This moves the entire feature branch to begin on the tip of the main branch, effectively incorporating all of the new commits in main . But, instead of using a merge commit, rebasing re-writes the project history by creating brand new commits for each commit in the original branch.

What does rebasing a commit do?

What is git rebase? From a content perspective, rebasing is changing the base of your branch from one commit to another making it appear as if you'd created your branch from a different commit. Internally, Git accomplishes this by creating new commits and applying them to the specified base.

Does rebasing change the commit hash?

Note that before Git 2.29 (Q4 2020), Git could also change the hash of the commit onto which you are rebasing in the todo message, commit which should not change (since you are replaying old commits onto this one).

What is the golden rule of rebasing?

The Golden Rule of Rebasing reads: “Never rebase while you're on a public branch.” This way, no one else will be pushing other changes, and no commits that aren't in your local repo will exist on the remote branch.


1 Answers

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
| |/
| * b1cbb4e b11
| * 18c8802 (b1) b1
|/
| * 7b4e404 (b2) b2
| | * 6ec272b (b3) b3
| | * c363c43 b2 h
| |/
| * 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
| |/
| * 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
|/
| * 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
| |/
| * 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
| |/
| * 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.

like image 123
carnicer Avatar answered Oct 21 '22 08:10

carnicer