Search code examples
gitmergesquash

Git squash and merge


Could anyone please tell me what the difference between these two commands:

git merge --squash

and

git merge --no-ff 

Solution

  • I think your question indicates a bit of a misunderstanding. --no-ff and --squash are not opposites, rather they are subtly different operations. Keep that in mind while reading.

    git merge --squash

    The help page for merge says the following about --squash:

    --squash and --no-squash

    Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit or move the HEAD, nor record $GIT_DIR/MERGE_HEAD to cause the next git commit command to create a merge commit. This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus).

    With --no-squash perform the merge and commit the result. This option can be used to override --squash.

    That's a bit confusing, and requires some knowledge about the internals of git. To start, we need to understand the difference between a regular commit and a merge commit. A regular commit has one parent, and is simply a changeset to apply to the commit before it:

    A --> B --> C
    

    A merge commit has multiple parents, and it a place in the tree where you've brought two or more lineages together:

    A --> B --> F
               /
    C --> D - /
    

    See how A, B, C, and D are regular commits, but F is a merge commit, since it has multiple parents (B and D)? This is what git merge --no-ff would produce. It forces Git to create a merge commit to bring two histories together.

    git merge --squash would do something a little different. It prevents Git from creating a merge commit, but still pulls in the changes C and D made, so your tree looks like this:

    A --> B --> F'
    
    C --> D 
    

    F' contains changes C and D made, but there's no sign of the fact you merged two trees in the repository.

    git merge --no-ff

    --no-ff is a slightly different operation. It forces Git into creating a merge commit even if it's not really necessary. For reference, here's what the manual has to say about --no-ff and it's opposite --ff-only:

    --no-ff

    Create a merge commit even when the merge resolves as a fast-forward.

    --ff-only

    Refuse to merge and exit with a non-zero status unless the current HEAD is already up-to-date or the merge can be resolved as a fast-forward.

    To understand, it's best to look at an example:

    A --> B --> C --> D --> E
          |                 |
        'master'          'topic'
    

    If you had this tree, were on the master branch, and ran git merge, Git would perform what's called a "fast-forward" merge. Since there's no divergence between the two histories, Git can just move the master branch up to where topic sits without doing anything interesting. It would look like this:

    A --> B --> C --> D --> E
                            |
                         'topic'
                         'master'
    

    With both topic and master pointing to the same branch. Now, some workflow policies require that you create a merge commit every time you merge back into master. This keeps the history of branches around. You'll get arguments either way on how it should be done, but I won't get into those here.

    If you used git merge --no-ff on that same tree instead, it would force git to create a merge commit, giving you a tree like this:

                       'master'
                           |
    A --> B -------------> F
           \              /
            C --> D --> E
                        |
                     'topic'
    

    Where F is the new merge commit --no-ff force Git to create.