7

Please can somebody inform me what Git does in terms of history when merging two branches.

If I have two branches which are both actively being developed on and both contain commits, which is similar to the following timeline:

Branch #1: ----(branch)----C1----------C2-------(merge)------C5
                  \                             /
                   \                           /
                    \                         /
Branch #2:           -----------C3----------C4

How does the history for Branch 1 look at C5 (commit #5) once both branches have been merged? I am under the impression Git will merge all history to give me the following:

Branch #1: ----------------C1----C3----C2----C4--------------C5

Is this the correct understanding?

If so, in the event of an emergency, how do I undo the merge, because surely all history from branch #2 will be entwined with branch #1's history.

Enrico Campidoglio
  • 56,676
  • 12
  • 126
  • 154
keldar
  • 6,152
  • 10
  • 52
  • 82
  • Git doesn't normally flatten history in a merge, so you normally won't see a linear history given the situation that you've presented...branch 1's history will look like the whole tree you have in your first code box. –  Oct 13 '15 at 10:58
  • Possible duplicate of [Revert to a previous Git commit](http://stackoverflow.com/a/4114122/456814). –  Oct 13 '15 at 11:02
  • Possible duplicate of [Undo a Git merge?](http://stackoverflow.com/q/2389361/456814). –  Oct 13 '15 at 11:03

5 Answers5

8

tl;dr

Having a linear history between two branches can only be done by rebasing one branch on top of the other before merging. If you just merge two branches that have diverged, Git will join the two lines of history by creating a merge commit.

Merging with merge commits

In Git, a commit normally holds a reference to one parent. A merge commit is a special kind of commit that references two or more parents.

In your example, the merge commit C5 has two parents:

  1. The first parent is C2, that is the commit on the branch where the other branch was merged to, i.e. branch1
  2. The second parent is C4, that is the commit on the branch that was merged, i.e. branch2.

When you do git log on branch1 Git will follow both lines of history showing you this:

C1--C2--C5 <- branch1
        /
  C3--C4 <- branch2

If you do git log on branch2, the lines of history will be swapped:

     C3--C4 <- branch2
         /
C1--C2--C5 <- branch1

Merging without merge commits

The scenario in which the commits between the two branches appear to be in the same line of history – with no merge commit involved – is the result of a rebase.

Rebasing branch2 on top of branch1 is an operation that's conceptually different from merging the two branches.

While merging is done with:

git checkout branch1
git merge branch2

Rebasing is done with:

git checkout branch2
git rebase branch1

which basically means:

Find all the commits that are reachable from branch2 but not from branch1 – in this case C3 and C4 – and apply them on top of the latest commit in branch1.

So the final history will look like this:

C1--C2--C3--C4 <- branch2
     ^
     branch1

Notice that branch1 still points to C2 – the same commit as it did before the rebase. Since the history between the two branches is now on the same line, merging them with:

git checkout branch1
git merge branch2

won't create a merge commit. Instead, Git will simply move the branch1 forward so that it points to the same commit as branch2. This operation is called a fast-forward:

To phrase that another way, when you try to merge one commit with a commit that can be reached by following the first commit’s history, Git simplifies things by moving the pointer forward because there is no divergent work to merge together – this is called a “fast-forward.”

Undoing a merge

The way you undo a merge will vary depending on whether the merge was done through a merge commit or not.

If there's a merge commit, you can undo the merge by simply moving branch1 to point to the merge commit's first parent:

git checkout branch1       # branch1 points at the merge commit C5
git reset --hard branch1^  # branch1 now points at C2

If the merge was done after a rebase, things are a bit trickier. You basically need to restore branch1 to point to the commit it did before the merge. If merging was the latest operation you did, you can use the special reference ORIG_HEAD:

git reset --hard ORIG_HEAD

Otherwise you'll have to resort to the reflog:

git checkout branch1
git reflog                 # Find the commit that branch1 pointed to before the merge
git reset --hard HEAD@{n}  # Move branch1 to point to that entry in the reflog
Community
  • 1
  • 1
Enrico Campidoglio
  • 56,676
  • 12
  • 126
  • 154
2

A merge will actually not create what you describe, a merge will create a commit with two parents commits. Inside that commit the changes of branch#2 will be replayed on your main branch. This explains what happens: https://git-scm.com/docs/git-merge

What you describe is what (sort of) happens when you rebase branch#2 onto branch#1: https://git-scm.com/docs/git-rebase. You would get C1, C2, C3, C4, C5 with a rebase however. Commits are not ordered by time when rebasing. When you rebase branch#2 onto branch#1 each commit in #2 is applied to #1 at that point.

Undoing a rebase or a merge is as simple as resetting your head to before the merge commit in case of a commit and to resetting to the last commit in your #1 branch before you started rebasing.

Finally, I do not know your exact setup of course, but I would be merging #2 into #1 often if there is a lot of active development in both branches, since doing this only sporadically will likely end up in lots of conflicts.

hoppa
  • 3,011
  • 18
  • 21
1

First you need to understand that a branch in git is just a tag. In your example, your branch #1 will be set to C1 then C2 then C5 (after the merge) when your branch #2 will be set to C3 then C4.

Then, if I understand correctly, When git say "a commit belong to branch X", it means that this commit is the current commit tagged by the branch OR an ancestor, direct of indirect, of this commit. So yes, After the merge, All commit to C1 to C belong to branch#1

Finally, for undo the merge, you have two possibilities :

  • if your merge hasn't be pulled by another developper, you can always reset your branch #1. Resetting is in fact simply moving the tag of your branch to another commit. In you case, it will be done with git reset C2, after having done a checkout on branch #1. If you have local modification you want to get rid of, you can do git reset --hard C2. You must be careful, as reset is one of the rare command in git with the ability to lose your work

  • if you merge has already been shared, you still have the possibility to do a revert. A revert is a ew commit, with modification erasing the modification of your merge. It is not a perfect solution, as the history will be kept.

More information on these operations here : https://www.atlassian.com/git/tutorials/undoing-changes

I will also suggest reading this ebook : https://git-scm.com/book/en/v2, at least the first three chapters, which give a good understanding of the concepts used in git.

Teocali
  • 2,725
  • 2
  • 23
  • 39
0

To undo merge, you can use: git checkout banrch#1; git reset --hard c2-hash

Nguyen Sy Thanh Son
  • 5,300
  • 1
  • 23
  • 33
0

Is this the correct understanding?

Post-merge, branch #1's history will look as you've posted in your question although the underlying merge mechanism is not that simple.

Merging branch-2 into branch-1 will 'clutter' (not sure if this is the right word) the history of branch-1. An alternative is to do a squash merge which combines all commits in branch-2 (which aren't in branch-1) and merges them as a single commit in branch-1. This way, branch-1's history remains clean and if you ever need to back-out changes from branch-2, you just need to revert a single commit (the squashed merge commit).

Note: Views on using squash merge differ drastically from person-to-person and from team-to-team. So better read up on it before actually using it on your production repository. In some cases, a cleaner branch history might be preferable whereas a more elaborate history (generated by a non-squash merge) might be preferred in other cases. Some related articles and links:

Community
  • 1
  • 1
nhylated
  • 1,575
  • 13
  • 19