1107

Using gitk log, I could not spot a difference between the two. How can I observe the difference (with a git command or some tool)?

Ankur Agarwal
  • 19,924
  • 32
  • 117
  • 182
user1162226
  • 11,163
  • 3
  • 12
  • 8
  • 4
    Possible duplicate of [Git fast forward VS no fast forward merge](http://stackoverflow.com/questions/6701292/git-fast-forward-vs-no-fast-forward-merge). –  May 29 '14 at 13:53
  • 1
    Related: [Why does git fast-forward merges by default?](http://stackoverflow.com/questions/2850369/why-does-git-fast-forward-merges-by-default). –  May 29 '14 at 13:53
  • 1
    Possible duplicate of [Git fast forward VS no fast forward merge](http://stackoverflow.com/questions/6701292/git-fast-forward-vs-no-fast-forward-merge) – Avijit Gupta Apr 08 '17 at 10:05

7 Answers7

1228

The --no-ff flag prevents git merge from executing a "fast-forward" if it detects that your current HEAD is an ancestor of the commit you're trying to merge. A fast-forward is when, instead of constructing a merge commit, git just moves your branch pointer to point at the incoming commit. This commonly occurs when doing a git pull without any local changes.

However, occasionally you want to prevent this behavior from happening, typically because you want to maintain a specific branch topology (e.g. you're merging in a topic branch and you want to ensure it looks that way when reading history). In order to do that, you can pass the --no-ff flag and git merge will always construct a merge instead of fast-forwarding.

Similarly, if you want to execute a git pull or use git merge in order to explicitly fast-forward, and you want to bail out if it can't fast-forward, then you can use the --ff-only flag. This way you can regularly do something like git pull --ff-only without thinking, and then if it errors out you can go back and decide if you want to merge or rebase.

Lily Ballard
  • 169,315
  • 25
  • 364
  • 333
  • 98
    To more directly answer the OP's question: they aren't always different, but if they are, it's clear from `gitk` or `git log --graph` that the fast-forward merge didn't create a merge commit, while the non-fast-forward one did. – Cascabel Jan 30 '12 at 20:28
  • 14
    It would be nice to expand on the reason for avoiding ff: the author mentioned "specific branch topology" means that in --no-ff case an extra merge commit serves as the marker of the merge. The pros is explicit merge marker with names of the author and the merger. The cons is non-linear history that looks like a set of converging railroad tracks. A possible *psychological* side effect of the merge though is contributors loosing interest due to a longer review process: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.Vf2oMyBVikp – Vlad Sep 19 '15 at 18:33
  • 8
    Would it be fair to say that `--no-ff` from a feature to develop or develop to master is similar to merging a pull request? – Merlin -they-them- Apr 15 '16 at 10:12
  • 12
    @merlinpatt Sure. If you merge a pull request in GitHub it does the equivalent of `--no-ff`. – Lily Ballard Apr 28 '16 at 18:31
  • 2
    This is a good explanation. Man, there's just not enough good git explanations out there for people to understand why clean git history is really, really important (me included!) – dudewad Mar 13 '17 at 21:05
  • "if"? "prevents if"? The flag either prevents or not. It cannot prevent "... if". It is a flag, it just does. Without "if"s – Green Apr 11 '20 at 18:11
  • "git merge will always construct a merge." To be more explicit, a new commit object will be created. – Roland Mar 05 '21 at 16:50
1164

Graphic answer to this question

Here is a site with a clear explanation and graphical illustration of using git merge --no-ff:

difference between git merge --no-ff and git merge

Until I saw this, I was completely lost with git. Using --no-ff allows someone reviewing history to clearly see the branch you checked out to work on. (that link points to github's "network" visualization tool) And here is another great reference with illustrations. This reference complements the first one nicely with more of a focus on those less acquainted with git.


Basic info for newbs like me

If you are like me, and not a Git-guru, my answer here describes handling the deletion of files from git's tracking without deleting them from the local filesystem, which seems poorly documented but often occurrence. Another newb situation is getting current code, which still manages to elude me.


Example Workflow

I updated a package to my website and had to go back to my notes to see my workflow; I thought it useful to add an example to this answer.

My workflow of git commands:

git checkout -b contact-form
(do your work on "contact-form")
git status
git commit -am  "updated form in contact module"
git checkout master
git merge --no-ff contact-form
git branch -d contact-form
git push origin master

Below: actual usage, including explanations.
Note: the output below is snipped; git is quite verbose.

$ git status
# On branch master
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   ecc/Desktop.php
#       modified:   ecc/Mobile.php
#       deleted:    ecc/ecc-config.php
#       modified:   ecc/readme.txt
#       modified:   ecc/test.php
#       deleted:    passthru-adapter.igs
#       deleted:    shop/mickey/index.php
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       ecc/upgrade.php
#       ecc/webgility-config.php
#       ecc/webgility-config.php.bak
#       ecc/webgility-magento.php

Notice 3 things from above:
1) In the output you can see the changes from the ECC package's upgrade, including the addition of new files.
2) Also notice there are two files (not in the /ecc folder) I deleted independent of this change. Instead of confusing those file deletions with ecc, I'll make a different cleanup branch later to reflect those files' deletion.
3) I didn't follow my workflow! I forgot about git while I was trying to get ecc working again.

Below: rather than do the all-inclusive git commit -am "updated ecc package" I normally would, I only wanted to add the files in the /ecc folder. Those deleted files weren't specifically part of my git add, but because they already were tracked in git, I need to remove them from this branch's commit:

$ git checkout -b ecc
$ git add ecc/*
$ git reset HEAD passthru-adapter.igs
$ git reset HEAD shop/mickey/index.php
Unstaged changes after reset:
M       passthru-adapter.igs
M       shop/mickey/index.php

$ git commit -m "Webgility ecc desktop connector files; integrates with Quickbooks"

$ git checkout master
D       passthru-adapter.igs
D       shop/mickey/index.php
Switched to branch 'master'
$ git merge --no-ff ecc
$ git branch -d ecc
Deleted branch ecc (was 98269a2).
$ git push origin master
Counting objects: 22, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (14/14), 59.00 KiB, done.
Total 14 (delta 10), reused 0 (delta 0)
To git@github.com:me/mywebsite.git
   8a0d9ec..333eff5  master -> master



Script for automating the above

Having used this process 10+ times in a day, I have taken to writing batch scripts to execute the commands, so I made an almost-proper git_update.sh <branch> <"commit message"> script for doing the above steps. Here is the Gist source for that script.

Instead of git commit -am I am selecting files from the "modified" list produced via git status and then pasting those in this script. This came about because I made dozens of edits but wanted varied branch names to help group the changes.

Mosh Feu
  • 24,625
  • 12
  • 74
  • 111
Chris K
  • 19,605
  • 2
  • 29
  • 41
  • 14
    Can you still safely delete a branch after do you a merge with the `--no-ff` option? – Andy Fleming Aug 12 '14 at 05:06
  • 21
    @DesignerGuy yes you can safely delete the old branch. Think of branch es as just pointers to a specific commit. – Zyphrax Aug 13 '14 at 09:36
  • 3
    I found this text from the linked page helpful: without --no-ff "it is impossible to see from the Git history which of the commit objects together have implemented a feature — you would have to manually read all the log messages." – Lorne Laliberte Sep 17 '14 at 16:15
  • one image always best than a thousand words ! – rupps Dec 27 '18 at 12:52
  • 3
    The graphic does not show the features branch in the second image, why not? It still exists doesn't it? – ADJenks Jan 30 '19 at 18:41
381

Merge Strategies

Explicit Merge: Creates a new merge commit. (This is what you will get if you used --no-ff.)

enter image description here

Fast Forward Merge: Forward rapidly, without creating a new commit:

enter image description here

Rebase: Establish a new base level:

enter image description here

Squash: Crush or squeeze (something) with force so that it becomes flat:

enter image description here

Mustafa Ehsan Alokozay
  • 4,373
  • 2
  • 22
  • 30
Premraj
  • 56,385
  • 22
  • 212
  • 157
  • 14
    Interesting graphic but it does not really show the --no-ff case and hence does not quite answer the question here – Vib Sep 17 '18 at 05:32
  • 33
    The first one, the "explicit merge", titled here as "merge", is a non-ff merge. – bkribbs Sep 20 '18 at 23:10
  • 7
    that graphics really helped me alot – exexzian Feb 19 '19 at 07:46
  • 2
    doesn't answer the question per say, but this graphic is amazing, UPVOTE! – SovietFrontier Apr 16 '19 at 20:28
  • 5
    So what's the difference between Rebase and fast-forward merge then? – ThanosFisherman Aug 27 '19 at 09:09
  • 2
    @ThanosFisherman The diffference is, that for a ff merge the HEAD of the current branch must be an ancestor of the commit you want to merge. Like the answer from Lily Ballard above https://stackoverflow.com/a/9069127/482702 – lrxw Sep 10 '19 at 09:24
  • @ThanosFisherman, Fast-Forward maintains the same base and tries to merge all file changes from the same changes after the shared base. A rebase "detaches" your branch, and then applies commit after commit on top of the base (as if you just checked out a fresh e.g. master branch), and THEN, tries to, apply the changes done on the new base. The rebase is usually used before you'd want to merge into master, so that it applies the topic-commits on top of master, without merge conflicts. – MahNas92 Nov 28 '19 at 10:16
  • What is "merge commit" from your first image? Where have the two red `feature` commits disappeared? Your `merge commit` looks like squashed commit, not a merge commit. – Green Apr 11 '20 at 18:19
  • Thank you! Great animation. – Player1 May 08 '20 at 16:46
  • picture is more better than thousand words :) – Tayab Hussain Jul 24 '20 at 10:25
235

The --no-ff option ensures that a fast forward merge will not happen, and that a new commit object will always be created. This can be desirable if you want git to maintain a history of feature branches.             git merge --no-ff vs git merge In the above image, the left side is an example of the git history after using git merge --no-ff and the right side is an example of using git merge where an ff merge was possible.

EDIT: A previous version of this image indicated only a single parent for the merge commit. Merge commits have multiple parent commits which git uses to maintain a history of the "feature branch" and of the original branch. The multiple parent links are highlighted in green.

Daniel Smith
  • 8,262
  • 3
  • 31
  • 56
  • 3
    What does the green arrow indicate vs the black arrow? – rtconner Jan 12 '15 at 23:30
  • 12
    the green arrow indicates a link between a commit and a parent where the commit has more than one parent. Normal commits (black) only have one parent. – Daniel Smith Jan 13 '15 at 01:11
  • 10
    @DanielSmith How do you draw this elegant graph? – chancyWu Jan 22 '16 at 06:24
  • 4
    @chancyWu with Adobe Illustrator – Daniel Smith Jun 22 '16 at 22:39
  • It seems in my company all pull requests are merged with the --no-ff option. Under that circumstances, does it still make sense to rebase before I create a pull request? – Nickpick Mar 14 '17 at 17:25
  • Just to confirm my understanding, so with `git merge --no-ff` it will be easier to revert to a previous commit using a command like `git reset --hard HEAD~1`, correct? Whereas with the other one, it might get involved say if there are other interleaved commits in the master branch. – Kalyan Mar 15 '18 at 15:36
  • 1
    The graphic does not show the feature branch in the second image, why not? It still exists doesn't it? – ADJenks Jan 30 '19 at 18:41
  • 1
    Yes it still exists in both cases and after the `git merge` it is identical to the master branch as all its commits have been fast forwarded onto master. In the git log you would see that both feature and master points to the same commit. In the `git merge --no-ff` case the feature branch would be missing the last merge commit. – cb2 May 24 '19 at 12:41
  • 1
    In that case they would not have an identical history although you would not see any difference when comparing what's in them. Their contents would be identical due to the fact that you actually could do a fast forward merge in this case. – cb2 May 24 '19 at 12:48
38

This is an old question, and this is somewhat subtly mentioned in the other posts, but the explanation that made this click for me is that non fast forward merges will require a separate commit.

Parris Varney
  • 10,663
  • 12
  • 44
  • 71
  • would you please review the workflow in my answer above: does this mean I need additional commits beyond what I've got now? Thanks. – Chris K Sep 11 '14 at 08:50
  • 3
    @ChrisK `git merge --no-ff ecc` you will simply have an additional merge commit in the `git log` for branch master. That is technically not needed in case master is pointing to a direct ancestor of the commit ecc is on but by specifying the --no-ff option you are forcing the creation of that merge commit. It will have the title: `Merge branch 'ecc'` – Ankur Agarwal Aug 04 '15 at 22:23
  • Great summary! :) – Eduard Apr 17 '20 at 12:43
7

The --no-ff flag causes the merge to always create a new commit object, even if the merge could be performed with a fast-forward. This avoids losing information about the historical existence of a feature branch and groups together all commits that together added the feature

jsina
  • 2,973
  • 24
  • 21
5

Other answers indicate perfectly well that --no-ff results in a merge commit. This retains historical information about the feature branch which is useful since feature branches are regularly cleaned up and deleted.

This answer may provide context for when to use or not to use --no-ff.

Merging from feature into the main branch: use --no-ff

Worked example:

$ git checkout -b NewFeature
[work...work...work]
$ git commit -am  "New feature complete!"
$ git checkout main
$ git merge --no-ff NewFeature
$ git push origin main
$ git branch -d NewFeature

Merging changes from main into feature branch: leave off --no-ff

Worked example:

$ git checkout -b NewFeature
[work...work...work]
[New changes made for HotFix in the main branch! Lets get them...]
$ git commit -am  "New feature in progress"
$ git pull origin main
[shortcut for "git fetch origin main", "git merge origin main"]
AlainD
  • 3,501
  • 2
  • 25
  • 63