53

When I work on two different features (on two different branches created from the master) it is quite annoying that I will not have the commit history when I proceed with merging.

I'll explain better. When I finish work on Branch-A, I merge it into master. And that's fine, if I git log I see all the commits I made on Branch-A.

Instead, when I finish work on Branch-B and I try to merge it to master (after that Branch-A has been already merged), I have to specify a commit message for the merging (while for the first branch I have not been asked anything). And after the merging to master, if I type git log, I cannot see the commits of the Branch-B in the history of my master branch

Let's say I have

**Branch A**

commit 09b2unfas9d781n2e
    Add more stuff

commit 8uj8masd89jas898a
    Add stuff

**Branch B**

commit 09b2unfas9d781n2e
    Add feature setting

commit 8uj8masd89jas898a
    Add feature

I finish having

**Master**

commit 6hf6h8hd871udjkdn
Merge: 09b2un 34osd6
    Merge branch 'Branch-B' into master

commit 09b2unfas9d781n2e
    Add more stuff

commit 8uj8masd89jas898a
    Add stuff

commit 34osd62dhc91123j8
    I'm a previous commit from 'master'.
    The last one before branching...

while I would like to obtain something like:

**Master**

commit 09b2unfas9d781n2e
    Add feature setting

commit 8uj8masd89jas898a
    Add feature

commit 09b2unfas9d781n2e
    Add more stuff

commit 8uj8masd89jas898a
    Add stuff

commit 34osd62dhc91123j8
    I'm a previous commit from 'master'.
    The last one before branching...

... that would reflect more exactly the history of the performed commits.

I don't get why I can keep the history from just one of the two branches.

How can I keep everything clear without those merge commits that hide/omit the real history of the merged commits?

2 Answers 2

58

It looks like the first merge was a fast-forward, and the second one was a three-way merge.

Explanation

Git has two versions of merge: fast-forward and three-way. (There are other versions, but that is not what happened here.) The default behavior is to do a fast-forward merge when possible, and otherwise do a three-way merge.

A fast-forward merge can take place when the commit that is being merged has the current position of the branch in its history (you can force this behavior with the option --ff-only, which will cause the merge to fail when fast-forward is impossible). For example:

A - B - C - D <-master
             \
              E - F - G <- branch-a

Executing git merge (with default settings) will result in

A - B - C - D - E - F - G <- branch-a <-master

You will also not get a chance to edit the merge commit because there is none.

A three-way merge when your other branch diverges from master (not just ahead):

A - B - C - D  - E - F - G <-master
                  \
                   E1 - E2 <- branch-b

In this case, Git cannot just move the pointer of master from G to E2 because that will get rid of the changes that were made in F and G. When a three-way merge happens, it creates a commit that has two parents, and also has a commit message. Now, master can be moved to this commit. (Notice that in this situation, master and branch-b do NOT point to the same commit.

A - B - C - D  - E - F - G - H <-master
                  \       /
                   E1 - E2 <- branch-b

If you want to have a linear history then you need to use rebase, but be forewarned that if anybody else has seen your branch commits this may lead to issues that are beyond the scope of this answer. Using rebase will involve two steps, rebasing and then fast-forward merge. So, instead of merging you first execute the following while on branch-b, git rebase master. This creates new commits that are copies of the old commits, i.e., the same change-set, author information and message, but new committer information and parent history. (I call the commits E1' and E2' in the illustration to indicate that they are just copies.) The old commits will exist until they are garbage collected, but will not be reachable unless you look at the reflog.)

A - B - C - D  - E - F - G <-master
                  \       \
                   E1 - E2 \ 
                            E1' - E2' <- branch-b

Executing git checkout master; git merge --ff-only branch-b will now fast-forward your changes into master, thereby giving you a linear history.

A - B - C - D  - E - F - G - E1' -E2' <-master <- branch-b
2
  • 3
    I need merge master to branch-b when I have changes in both. How I can rewrite branch-b like it was started from commit G, not E? But keep commit history of branch-b and commit changes to branch-b that are conflicts. Assuming I'm only one who knows about branch-b.
    – Artem P
    Commented Mar 17, 2017 at 20:51
  • 5
    Nvm it's exact git rebase master.
    – Artem P
    Commented Mar 17, 2017 at 21:28
11

Use rebase instead of merge. From the tutorial:

If you examine the log of a rebased branch, it looks like a linear history: it appears that all the work happened in series, even when it originally happened in parallel.

I imagine that the changes from your Branch-B cannot be merged using fast-forward merging into the master. In such cases a three-way-merge is done:

Instead of just moving the branch pointer forward, Git creates a new snapshot that results from this three-way merge and automatically creates a new commit that points to it. This is referred to as a merge commit, and is special in that it has more than one parent.

I would always rebase my commits before commiting them into the master to keep the linear history.

8
  • 1
    I cannot say why, but rebases change the history of git and may be confusing for others! :)
    – Yan Foto
    Commented Jul 25, 2018 at 11:52
  • 4
    If you rebase only your local working branch, before merging it to master, then there's no confusion - since no one is using your local branch. Not even you, after it's once happily joined the master.
    – userfuser
    Commented Jan 10, 2020 at 12:06
  • 2
    @Kamafeather you were right! Now in 2022 I'm forced to always rebase. And my argument about rebases being confusing was wrong all the way!
    – Yan Foto
    Commented Jul 8, 2022 at 13:00
  • 2
    ❤️ glad it helped evolving your workflow! It daily helps mine, in decluttering and retaining metadata & diffs for debugging git history. – Although at times it requires more work or is tricky with the rewriting of history. IMHO, It's worth the hassle; but your argument wasn't wrong, I understand my git rebase workflow may be confusing (iirc as newbie I couldn't grasp rebasing). To beginners I'd suggest to stick with merge and to never pick commits from my WIP branches (that… I backup onto origin, prefixed as kama/WIP/feature-5, waiting to yell at anybody that touches them 😁). Commented Jul 13, 2022 at 1:27
  • 1
    Lately I also abuse rebasing to reorder/squash/remove/fix previous commits and clean up even more; I like the anarchist power of rebase in spite of the more conformist merge! ✊😎 Commented Jul 13, 2022 at 1:30

Not the answer you're looking for? Browse other questions tagged or ask your own question.