--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.
What is the difference between git merge and git merge –no-ff ?
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. 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.
Answer #3: git merge vs git merge –no-ff
Graphic answer to this question
Here’s a clear graphical illustration of using
git merge --no-ff:
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.
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 email@example.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.
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.
What’s a fast-forward?
A fast-forward is what Git does when you merge or rebase against a branch simply ahead of the one you have checked-out.
Given the following branch setup:
You’ve got both branches referencing the same commit. They’ve both got precisely the same history. Now commit something to feature.
The master branch is still referencing 7ddac6c, while the feature has advanced by two commits. The feature branch can now be considered ahead of the master.
It’s now relatively easy to see what’ll happen when Git does a fast-forward. It simply updates the master branch to reference the exact commit that feature does. No changes are made to the repository itself, as the commits from the feature already contain all the necessary changes.
Your repository history would now look like this:
When doesn’t a fast-forward happen?
Fast-forwards don’t happen when changes have been made in the original branch and the new branch.
If you were to merge or rebase the feature onto master, Git would be unable to do a fast-forward because the trees have both diverged. Considering Git commits are immutable, there’s no way for Git to get the commits from feature into master without changing their parent references.
Explicit Merge (aka non fast-forward): Creates a new merge commit. (This is what you will get if you used
Fast Forward Merge: Forward rapidly, without creating a new commit:
Rebase: Establish a new base level:
Squash: Crush or squeeze (something) with force so that it becomes flat:
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
Merging from feature into the main branch: use
$ 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
$ 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"]
Hope you learned something from this post.
Follow Programming Articles for more!