Why does git perform fast-forward merges by default?
Asked Answered
B

2

670

Coming from mercurial, I use branches to organize features. Naturally, I want to see this work-flow in my history as well.

I started my new project using git and finished my first feature. When merging the feature, I realized git uses fast-forward, i.e. it applies my changes directly to the master branch if possible and forgets about my branch.

So to think into the future: I'm the only one working on this project. If I use git's default approach (fast-forward merging), my history would result in one giant master branch. Nobody knows I used a separate branch for every feature, because in the end I'll have only that giant master branch. Won't that look unprofessional?

By this reasoning, I don't want fast-forward merging and can't see why it is the default. What's so good about it?

Booher answered 17/5, 2010 at 15:26 Comment(15)
Note: see also sandofsky.com/blog/git-workflow.html, and avoid the 'no-ff' with its "checkpoint commits" that break bisect or blame.Akeyla
Absolutely not! In my work folder I have 7 one-man projects where I use git. Let me rephrase that: I started many projects since I asked this question and all of them are versioned via git. As far as I know, only git and mercurial support local versioning, which is essential to me since I got used to it. It's easy to set it up and you always have the whole history with you. In group projects its even better, since you can commit without interfering anyone with your work-in-progress code. In addition I use github to share some of my projects (e.g. micro-optparse) where git is an requirement.Booher
@Akeyla your newer tiny comment seem to break pretty much all of your answer's basis on using no-ff, even if only for specific branches. Would you care to elucidate? Seems to me the diagram and the whole nvie's ideas on using it should be avoided and I can't see now a good usage case for no-ff. Breaking blame like that is a big no-no indeed. For now I'm just glad I haven't even stumbled upon or started using gitflow before.Welker
@Cawas true, -no-ff is rarely a good idea, but can still help keep an feature-internal history while recording only one commit on the main branch. That makes sense for long feature history, when you merge from time to time its progression on the main branch.Akeyla
@Akeyla let me see if I get this straight: it will break blame only on the branch it's being applied to. Thus, in that nvie's model, it's ok to use it on master since you'll have everything on develop anyway. Is this right? I wonder why blame can't have a --follow optiong like log do.Welker
@Cawas true, and I don't know about the lack of --follow either (maybe a bit complex to implement?)Akeyla
@FlorianPilz: "only git and mercurial support local versioning": no, any DVCS does.Francisco
By the way, to your question of "Doesn't that [a linear branch history] look unprofessional?". There's nothing unprofessional about using a source code system with it's defaults. This isn't about professionalism. This is about determining which philosophy of branching you subscribe you. For example, @Akeyla linked to sandofsky's article where he champions using fast forward as a superior approach. Not right or wrong, just different philosophies for different contexts.Posset
@VonC, I read the article you referenced, and I don't agree that --no-ff is evil. The way I see it, you exchange creating merge commits with smashed commits, which has the same effect as the merge, and possibly history rewriting (which is time-consuming and error-prone).Interphone
@Interphone more than two years later, I do agree with you, and git bisect is more robust. Still, I would use the --no-ff carefully.Akeyla
@Akeyla I didn't even notice the date in the comment! LOL Thanks for the response, I'll look more into this matter.Interphone
Please remember that Git branches and Mercurial branches aren't quite the same concept. If you read the Mercurial documentation on "bookmarks" you will get a good idea of how Git branches work! (mercurial.selenic.com/wiki/Bookmarks)Republicanism
Fast-forward merging makes it super-difficult to "checkout before the merge". In Mercurial you just undo one merge commit. In git you have to scroll through zillions of commits to find the one you need.Ito
What really troubled me (freshly burned coming from Hg as well) is that my "bad merge" to master resulted in zero commits. Suddenly my branch and master are pretending to be one thing. It was a mistake b/c my pull request was not yet approved (I shouldn't have had privileges to merge, but I did). I did a reset to the prior commit, and pushed to remote. Wrong answer! All prior commits on the branch were "zipped into" master already, as if retroactively pushed "sideways". What is the pro of having no record corresponding to the action of moving a gazillion commits sideways after the fact?Whereon
@JoshSutterfield not that I know how to use it, but maybe you need to learn how to use the reflog?Anticipant
A
737

Fast-forward merging makes sense for short-lived branches, but in a more complex history, non-fast-forward merging may make the history easier to understand, and make it easier to revert a group of commits.

Warning: Non-fast-forwarding has potential side effects as well. Please review https://sandofsky.com/workflow/git-workflow/, avoid the 'no-ff' with its "checkpoint commits" that break bisect or blame, and carefully consider whether it should be your default approach for master.

alt text
(From nvie.com, Vincent Driessen, post "A successful Git branching model")

Incorporating a finished feature on develop

Finished features may be merged into the develop branch to add them to the upcoming release:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557).
$ git push origin develop

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.

Jakub Narębski also mentions the config merge.ff:

By default, Git does not create an extra merge commit when merging a commit that is a descendant of the current commit. Instead, the tip of the current branch is fast-forwarded.
When set to false, this variable tells Git to create an extra merge commit in such a case (equivalent to giving the --no-ff option from the command line).
When set to 'only', only such fast-forward merges are allowed (equivalent to giving the --ff-only option from the command line).


The fast-forward is the default because:

  • short-lived branches are very easy to create and use in Git
  • short-lived branches often isolate many commits that can be reorganized freely within that branch
  • those commits are actually part of the main branch: once reorganized, the main branch is fast-forwarded to include them.

But if you anticipate an iterative workflow on one topic/feature branch (i.e., I merge, then I go back to this feature branch and add some more commits), then it is useful to include only the merge in the main branch, rather than all the intermediate commits of the feature branch.

In this case, you can end up setting this kind of config file:

[branch "master"]
# This is the list of cmdline options that should be added to git-merge 
# when I merge commits into the master branch.
             
# The option --no-commit instructs git not to commit the merge
# by default. This allows me to do some final adjustment to the commit log
# message before it gets commited. I often use this to add extra info to
# the merge message or rewrite my local branch names in the commit message
# to branch names that are more understandable to the casual reader of the git log.

# Option --no-ff instructs git to always record a merge commit, even if
# the branch being merged into can be fast-forwarded. This is often the
# case when you create a short-lived topic branch which tracks master, do
# some changes on the topic branch and then merge the changes into the
# master which remained unchanged while you were doing your work on the
# topic branch. In this case the master branch can be fast-forwarded (that
# is the tip of the master branch can be updated to point to the tip of
# the topic branch) and this is what git does by default. With --no-ff
# option set, git creates a real merge commit which records the fact that
# another branch was merged. I find this easier to understand and read in
# the log.

mergeoptions = --no-commit --no-ff

The OP adds in the comments:

I see some sense in fast-forward for [short-lived] branches, but making it the default action means that git assumes you... often have [short-lived] branches. Reasonable?

Jefromi answers:

I think the lifetime of branches varies greatly from user to user. Among experienced users, though, there's probably a tendency to have far more short-lived branches.

To me, a short-lived branch is one that I create in order to make a certain operation easier (rebasing, likely, or quick patching and testing), and then immediately delete once I'm done.
That means it likely should be absorbed into the topic branch it forked from, and the topic branch will be merged as one branch. No one needs to know what I did internally in order to create the series of commits implementing that given feature.

More generally, I add:

it really depends on your development workflow:

  • if it is linear, one branch makes sense.
  • If you need to isolate features and work on them for a long period of time and repeatedly merge them, several branches make sense.

See "When should you branch?"

Actually, when you consider the Mercurial branch model, it is at its core one branch per repository (even though you can create anonymous heads, bookmarks and even named branches)
See "Git and Mercurial - Compare and Contrast".

Mercurial, by default, uses anonymous lightweight codelines, which in its terminology are called "heads".
Git uses lightweight named branches, with injective mapping to map names of branches in remote repository to names of remote-tracking branches.
Git "forces" you to name branches (well, with the exception of a single unnamed branch, which is a situation called a "detached HEAD"), but I think this works better with branch-heavy workflows such as topic branch workflow, meaning multiple branches in a single repository paradigm.

Akeyla answered 17/5, 2010 at 15:31 Comment(26)
Wow that was fast. ;) I know about the --no-ff option, but only find out about fast-forward after I screwed up my feature. I see some sense in fast-forward for short living branches, but making it the default action means, that git assumes you most often have such short living branches. Reasonable?Booher
@Florian: I believe this is a reasonable view of the process. I have added an example of a config which will setup the way you want to manage merge to master.Akeyla
Thanks, the config file should help to avoid such gotchas. :) Only applied this configuration locally with "git config branch.master.mergeoptions '--no-ff'"Booher
@Florian: Just to add a second voice, I think the lifetime of branches varies greatly from user to user. Among experienced users, though, there's probably a tendency to have far more short-lived branches (quick daily operations) than long-lived ones (only merged when finished), so I think it's a reasonable default.Glyceride
@Jefromi: Still - if I have only a single master branch after, say, 500 commits, doesn't that look somehow unprofessional? Nobody knows that there was a feature-branch structure during development. You only see the outcome. Or are branches in git more heavy weight like production and development branch which may stay on it's own for a month or more?Booher
@Florian: it really depends on your development workflow (stackoverflow.com/questions/216212#216228): if it is linear, one branch makes sense. If you need to isolate features and work on them for a long period of time and repeatedly merge them, several branches make sense. See "When should you branch": https://mcmap.net/q/13137/-when-should-you-branch/…Akeyla
@Florian: I think we have different notions of "short-lived". To me, a short-lived branch is one that I create in order to make a certain operation easier (rebasing, likely, or quick patching and testing), and then immediately delete once I'm done. That means it likely should be absorbed into the topic branch it forked from, and the topic branch will be merged as one branch. No one needs to know what I did internally in order to create the series of commits implementing that given feature.Glyceride
Big THANKS to both of you. I will dig deeper into the philosophy of git and will read through the links you (VonC) added to your post. :)Booher
@VonC: Does this mean that while I am on my feature branch, I should not rebase with the master? Otherwise, if I am not wrong, I would still get one linear history.Lovable
@BehrangSaeedzadeh: rebasing is an other topic (that hasn't been mentioned onced in this page): as long as you haven't push your feature branch to a public repo, you can rebase it on top of master as many times as you want. See stackoverflow.com/questions/5250817/…Akeyla
@VonC: But won't that make the history linear?Lovable
@BehrangSaeedzadeh: a rebase by itself won't make an history linear. It is how you integrate the changes of your feature branch back into master that makes said history linear or not. A simple fast-foward merge will make it linear. Which makes sense if you have cleaned the history of that feature branch before the fast-forward merge, leaving only significant commits, as mentioned in stackoverflow.com/questions/7425541/….Akeyla
that image is the best explanation i've seen about ff. everBungle
There is also merge.ff config optionProrate
@JakubNarębski good point. I have included that config to the answer.Akeyla
Reverting a merge is simple, but merging it again later is not. Here is Linus Torvalds on how to revert a faulty merge.Greenery
@Greenery true. Even more details in practice about reverting a merge: https://mcmap.net/q/13141/-avoid-the-effects-of-a-revert-commit-in-another-git-branch-while-mergingAkeyla
Can --no-ff merge is possible to enter a merge message in command? For eg: git merge --no -ff feature1 "your message for merge".Leralerch
@JustinJohn the option "--no-ff" shouldn't be incompatible with the "-m" option: git merge --no -ff -m "your message for merge" feature1. Meaning, '-m' is the option allowing you to enter a merge message in command.Akeyla
If there are multiple authors on the feature branch when the commits are squashed and merged is the author data still maintained ? or is it overwritten with the author who merged the code ? if so is there any way to maintain the author data ?Hg
@Hg not sure, actually. I will monitor your question at https://mcmap.net/q/22455/-merge-multiple-commits-by-different-authors-while-maintaining-author-details-in-git-blame/6309.Akeyla
@Akeyla your first comment to the OP's question regarding not defaulting to --no-ff is very helpful. It seems to me it would be good to add a disclaimer immediately after your first paragraph, warning not to default to --no-ff and referencing the same article by sandofsky. I would make that edit myself but you have obviously put a lot of time and thought into this topic, so I'd rather defer that to you in case you have a reason for not warning about it there.Renege
@Renege please, make the edit, and I will validate it.Akeyla
@Renege thank you for the edit. I remember that article now, having mentioned it numerous time already (https://mcmap.net/q/13142/-git-branches-amp-commits-history-after-merge, https://mcmap.net/q/13143/-with-dvcs-git-is-a-single-commit-preferred-over-multiple-small-thematic-commits, https://mcmap.net/q/12930/-git-commit-style-all-changed-files-at-once-or-one-at-a-time, https://mcmap.net/q/12929/-git-merge-testing-branch-final-commit-to-master-branch, https://mcmap.net/q/12928/-fast-forward-when-using-pull-and-no-ff-when-merging-branch)Akeyla
@Akeyla no problem; your comment to the OP's question here was the first time I saw Sandovski's article. It presents the potential pitfalls in an easy to consume format. However I am very familiar with the Successful Git Branching Model article. Initially when I saw this I thought "OK, I should default to that" just because I'm familiar with that branching model and it's so popular. So my worry was that someone else would do the same and miss the advice to use it carefully. Thanks for letting me update it.Renege
@Akeyla @Damon: The "Warning" is actually misleading: Ben Sandofsky didn't say that no-ff would break bisect and blame. You will have the same problems if you use fast-forward. The point was about the way many merge in general and if we should consider cleaning up private branches and rewriting them during merge.Actable
V
45

Let me expand a bit on a VonC's very comprehensive answer:


First, if I remember it correctly, the fact that Git by default doesn't create merge commits in the fast-forward case has come from considering single-branch "equal repositories", where mutual pull is used to sync those two repositories (a workflow you can find as first example in most user's documentation, including "The Git User's Manual" and "Version Control by Example"). In this case you don't use pull to merge fully realized branch, you use it to keep up with other work. You don't want to have ephemeral and unimportant fact when you happen to do a sync saved and stored in repository, saved for the future.

Note that usefulness of feature branches and of having multiple branches in single repository came only later, with more widespread usage of VCS with good merging support, and with trying various merge-based workflows. That is why for example Mercurial originally supported only one branch per repository (plus anonymous tips for tracking remote branches), as seen in older revisions of "Mercurial: The Definitive Guide".


Second, when following best practices of using feature branches, namely that feature branches should all start from stable version (usually from last release), to be able to cherry-pick and select which features to include by selecting which feature branches to merge, you are usually not in fast-forward situation... which makes this issue moot. You need to worry about creating a true merge and not fast-forward when merging a very first branch (assuming that you don't put single-commit changes directly on 'master'); all other later merges are of course in non fast-forward situation.

HTH

Veronikaveronike answered 31/5, 2013 at 13:30 Comment(3)
Regarding feature branches from stable releases: What if a feature I'm developing for the next release depends on the changes for another feature that I've already developed and merged into master? Surely that means I have to create this second feature branch from master?Saccharin
@dOxxx: Yes, there are exceptions, like e.g. where one branch builds on the other (either directly, or after merging previous into master).Prorate
@Saccharin nothing technically blocks the author of Feature B to pull from Feature A's branch as often as necessary, in order to "depend" on its changes. If A happen to be merged into the main branch before B (which is should, if B "depends" on it) it should (knock-knock) be fine from git's perspective.Shutt

© 2022 - 2024 — McMap. All rights reserved.