Git Merge –no-ff makes copy of commits

I had a curious git behaviour today, and I want to ask if this is normal. Now, this is the situation (new commits are added to the bottom):

br1  br2
  A
    \ 
     B
     C
     D

Now, when I make

  • ! master -> master (non-fast-forward) on a new up-to-date branch
  • What is the difference between `git merge` and `git merge --no-ff`?
  • Git fast forward merge: Any chance to find the person to blame?
  • Git gives 'non-fast-forward updates' error even after 'git pull'
  • Undo git fast forward merge
  • What does it mean that a Git push can not be fast foward merged?
  • git checkout br1
    git merge --no-ff br2
    

    It becomes like this (at least what git log –graph tells me):

    br1  br2
      A
      | \ 
      |  B
      |  C
      |  D
      | /
      E
    

    OK.. Now, weird thing is; now I call “git status”; it says I’m 4 commits ahead of the remote branch. How is this happening? Shouldn’t I be only one commit ahead?

    And the peculiar thing is, when I check from the Stash (the Git Web UI, basically) it confirms this “4 commits” status and I see the same commits (B C and D) under “br1” and “br2” both…

    I assumed when I used “–no-ff” parameters, the commits at “br2” (B C D) won’t be copied to the “br1” and only merge commit created. Am I mistaken here?

  • Send pull request to GitHub
  • For how long can you restore/recover a deleted branch on GitHub?
  • Apply a diff on a specific file from a merge commit in git
  • XCode / Git, status of an updated file is no updated in source control
  • Can I create a symbolic link automatically after git clone
  • Using Github for Windows to work with own private Git through SSH
  • 3 Solutions collect form web for “Git Merge –no-ff makes copy of commits”

    I think you’re misunderstanding how branches work in Git. Branches are lightweight, which means nothing gets copied when you create a new branch or merge one branch into another.

    A branch is a reference that points to a commit. Since every commit is linked to its previous commits, when you point to one commit you are effectively pointing to the history of that commit.

    In your example, E is a merge commit that combines two branches together, creating a single history. This means that after merging br2 into br1, br1 (which is now a reference to E) will know about the history of br2 (which is still a reference to D), because D, C and B are part of the history of E.

    Your local br1 is a reference to E, while your remote br1 is still a reference to A. Therefore, there are four commits (B, C, D and E) that are new to the history of br1.

    Update: The poster changed the question after I composed this answer. This update answers to one of the questions the rest of the answer doesn’t cover.

    OK.. Now, weird thing is; now I call “git status”; it says I’m 4 commits ahead of the remote branch. How is this happening? Shouldn’t I be only one commit ahead?

    After you merged (--no-ff) the commits A and D into E, the local branch is 4 commits ahead of the remote branch (I suppose the remote branch still points to A). This is correct.

    Let’s count the commits that are on the local branch (now pointing to E) and aren’t on the remote branch (pointing to A). E is obviously one of them. E, being a merge commit, has two parents: A (existing on the remote branch) and D (not existing on the remote branch). When git pushes E to the remote server it needs to push both its parents (otherwise E is invalid on the remote server).

    D requires C (its parent) that requires B that requires A. A already exists on the remote server and the chain ends here. B, C, D and E do not exist on the remote server. All of them must be pushed in order to keep the consistency of the data on the remote server.


    The rest of the response treats the original question that was not very clear defined. The poster made references to git log --graph and (probably) Atlassian Stash. Both these tools (and all the other git interfaces I know) present the most recent commit first in the commit history. This answer uses the same convention.

    git merge --no-ff doesn’t have any effect in the situation you described.

    Having br1 as current branch, git merge br2 brings into br1 the changes introduced by the commits that are reachable from br2 and are not reachable from br1. Depending on the relative position of br1 regarding br2, git merge can create a new commit (that contains all the changes introduced by the commits unreachable from br1) or it can just move the br1 branch head into a new position (and doesn’t create any new commit).

    In your situation, br2 is behind br1 with 1 commit and because A is a merge commit, all the commits accessible from br2 can also be reached from br1.


    Anyway, git merge --no-ff is used in a different situation.

    Let’s say we have this graph:

     B <-- br2
     |
     C
     |
     D
     |
     E <-- br1
     |
    ...
    

    The branch br2 was created from branch br1 (both were at commit E at that time) then br2 was checked out and three new commits (D, C and B) were added.

    In this situation, the commands:

    git checkout br1
    git merge br2
    

    do not create a new branch. The branch br1 is moved to commit B (where br2 is) and that’s all. Now all the commits that are reachable from br2 are also accessible from br1. br2 was successfully merged into br1.

    This is how the graph looks like:

    br1 --> B <-- br2
            |
            C
            |
            D
            |
            E
            |
           ...
    

    This kind of merge is called fast-forward because the branch br1 was moved “fast forward” from its previous position to a new position (probably opposed to branch b2 that moved from E to B one commit at a time). It is possible only if br2 is the only one child path of br1 (starting from E, each commit has a single child commit and the path reaches B).

    Here comes --no-ff into play. If you want this merge to create a merge commit that contains all the changes introduced by commits D, C and B then you run:

    git checkout br1
    git merge --no-ff br2
    

    The presence of --no-ff option prohibits the fast forwarding of branch b1 and forces the creation of a new merge commit. The graph will look like this:

     A <-- br1
     |\
     | B <-- br2
     | |
     | C
     | |
     | D
     |/
     E
     |
    ...
    

    Technically, the 4 commits are actually part of br1. If you delete br2, the commits will still be part of br1. The difference comes when you are resetting the merge commit. If you do a hard reset you will go back to the commit just before the merge commit i.e. E. This is especially useful when you want to rollback. So yes, you are four commits ahead, but as long as you are not resetting br1, you should be fine.

    Also -no-ff is not to stop git from copying the commits. It is to keep the commits separate from the tree of br1. That way you maintain a history of branches and the context in which the commits were made, rather than a mass of commits all related to different features

    Git Baby is a git and github fan, let's start git clone.