0

Given a repo with a master and a feature branch and commits C0 through C5 as below:

C0 <-- C1 <-- C2 <------ ???   master
       \                /
        C3 <-- C4 <-- C5      feature

When I merge, what happens at the ??? point?

Do I get one commit C6, i.e. a "squash" of the merge as a single commit:

C0 <-- C1 <-- C2 <------ C6    master
       \                /
        C3 <-- C4 <-- C5      feature

Or a "replay", i.e. C6 <-- C3' <-- C4' <-- C5' (with the ' because these commits now also contain C2):

C0 <-- C1 <-- C2 <------ C6 <-- C3' <-- C4' <-- C5'   master
       \                /
        C3 <-- C4 <-- C5                              feature

Is it the same on GitHub when merging a box-standard pull request from the same repo's branch?

Christian
  • 6,070
  • 11
  • 53
  • 103
  • 4
    Have you tried it? – Lasse V. Karlsen Aug 17 '17 at 11:36
  • Also, a "squash" is the collapsing of history, since you're not getting rid of history, you're not squashing. A commit is simply a snapshot of what the repository looks like at that point, a merge is one way to create such a commit by integrating two sets of changes and forming a new snapshot. – Lasse V. Karlsen Aug 17 '17 at 11:38
  • Neither of both happens. You get the difference from C5 to C1 and C2 to C1. – ckruczek Aug 17 '17 at 11:38
  • I don't see how merging could replay all those commits on top of `master`. That could only happen from a fast-forward, which isn't possible in your example, or maybe by some sort of rebase. – Tim Biegeleisen Aug 17 '17 at 11:39
  • I checked on our project's history on github, and it looks like replay - but the manual always depicts it like a [squash](https://git-scm.com/docs/git-merge) (replay, then commit), so I'm confused. – Christian Aug 17 '17 at 11:39
  • Possible duplicate of [How does 'git merge' work in details?](https://stackoverflow.com/questions/14961255/how-does-git-merge-work-in-details) – phd Aug 17 '17 at 14:29

3 Answers3

3

Supposing you use the command git merge without the --squash option, what happens is that Git creates a C6 commit, called a merge commit, which is a like a snapshot of your tracked files after merging their contents. The default strategy is the recursive, which merges by taking into account what changed between C5 and C1, and between C2 and C1.

Since this is a merge commit, its parent would be C5 and C2.

Your confusion probably comes from the fact that you are not considering what commits are: they are just pointers to a state of a tree of files. They don't store differences (let's skip compression done by the gc for simplicity), they store states. So Git would merge your files, save them, hash them and create a single hash (representing a tree object) that allows us to reconstruct that state, which is what your C6 commit will point to. Git From The Inside Out does a wonderful job at explaining this whole process.

If, on the other hand, you had used git merge --squash, Git would also create a commit with the state after the merge, but the resulting commit would be like a normal commit, without two parents. See Git: To squash or not to squash? for more information.

Finally, this "replaying" process is done by rebase, but not in the way you described. Read Merging vs. Rebasing to see how merge and rebase differ.

Samir Aguiar
  • 2,509
  • 2
  • 18
  • 32
2

What you get is neither a squash nor a replay of the commits.

To get a squash you could use the --squash option to merge. Then you'd get

C0 <-- C1 <-- C2 <-- C3C4C5   master
       \                
        C3 <-- C4 <-- C5      feature

where C3C4C5 is a single commit with one parent whose diff from C2 is equal to C5s diff from C1 (barring conflicts).

The replay the changes from the branch, you could perform a rebasing operation. This might yield

C0 <-- C1 <-- C2 <-- C3' <-- C4' <-- C5'   master    featurE_ref
       \                
        C3 <-- C4 <-- C5      feature_ref_used_to_be_here

(or some variation, depending exactly what you do). In this case commit C3' differs form C2 exactly as C3 differs form C1, etc.

But in the case of a merge you don't get either of those. You get

C0 <-- C1 <-- C2 <------ M     master
       \                /
        C3 <-- C4 <-- C5      feature

'M' is a single commit, but unlike a C3C4C5 from the squash, M has two parents. Its TREE (content) differs from C2 in the same way that C5s TREE differs form C1, so it's similar to a squash. But it generally is interpreted as not introducing changes (except in the case of merge conflicts or evil merges); rather it marks that the changes from C5 and its ancestors are introduced into master at this point. Hence by default git log doesn't display a patch for M but does include commits C3, C4, and C5

Hasturkun
  • 35,395
  • 6
  • 71
  • 104
Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52
1

You get neither of both....directly.

First of all a merge in git is working like this:

  1. Find the common ancestor of both heads, in your case this is C1
  2. Merge the both heads in terms of 'find the difference between those and the common ancestor and put it together, wherever I am not able to do this deterministic I am asking the user`
  3. As I am finished I create a new commit, the merge commit, which has C2 and C5 as a common parent

You can ofcourse squash (git merge --squash) when you merge. This is explained in this excellent answer.

ckruczek
  • 2,361
  • 2
  • 20
  • 23