ChatGPT解决这个技术问题 Extra ChatGPT

When to use the '--no-ff' merge option in Git

A Successful Git Branching Model recommends to use --no-ff when merging branches:

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. […] Yes, it will create a few more (empty) commit objects, but the gain is much bigger that that cost. Unfortunately, I have not found a way to make --no-ff the default behavior of git merge yet, but it really should be.

Understanding the Git Workflow, however, recommends not to use --no-ff:

So you add a new rule: “When you merge in your feature branch, use –-no-ff to force a new commit.” This gets the job done, and you move on. […] The --no-ff band-aid, broken bisect, and blame mysteries are all symptoms that you’re using a screwdriver as a hammer. […]

Both approaches seem reasonable for difference scenarios, but what is considered "good practice?"

When do you use --no-ff, when do you not, why?

At my work, we use '--no-ff', but the team next door doesn't (they rebase). Each to their own.
Here, we use git rebase, but the final merge is done with --no-ff.
I came across this question since there is no help info on --no-ff on git merge -h (git version 2.7.4)

P
Paolo

It's really dependent on what you're trying to do. My rule of thumb is that if the merge "means something" I use the --no-ff option. When the merge doesn't really mean anything and you might have used a rebase there's no reason to use --no-ff.

The thing with git is that it's a very powerful tool, and you can use it in many ways - most of which are not wrong. Asking what's the "right way" to do it is like asking what's the right way to paint a picture.

At least for me it's an evolving set of ways I like to do it, with frequent discussions in the team of how we want to collaborate on the code - from this we try to derive a kind of "how it's done here" standard, but it's just our way of doing it.


Accepting this answer since it made me understand the subjective use of this option, based on our desired workflow. Thanks, @Tomas.
佚名

For the case of a finished branch with a single commit, don't use --no-ff, just fast-forward it, because the history will be much simpler and less cluttered. It's hard to argue that --no-ff gets you any advantages in this case, because it's uninteresting to see a parallel branch of development with just a single commit, vs a single commit in a sequential line:

# No fast-forward
$ git merge --no-ff awesome-feature
*   3aa649c Merge branch 'awesome-feature'
|\
| * 35ec88f Add awesome feature
|/
* 89408de Modify feature-001.txt and fix-001.txt
* c1abcde Add feature-003

# versus fast-forward
$ git merge awesome-feature
* 35ec88f Add awesome feature
* 89408de Modify feature-001.txt and fix-001.txt
* c1abcde Add feature-003

For the case of a finished branch with more than a single commit, it's up to you, whether or not you want to keep the fact that the branched development happened in parallel vs sequential. I would probably use --no-ff for more than one commit, just so that I can visually see this branched work, and so that I can manipulate it easily with a single git revert -m 1 <sha-of-merge-commit> if I had to.

# No fast-forward
$ git merge --no-ff epic-feature
*   d676897 Merge branch 'epic-feature'
|\
| * ba40d93 Add octocat.txt
| * b09d343 Add bye.txt
| * 75e18c8 Add hello.txt
|/
* 89408de Modify feature-001.txt and fix-001.txt
* c1abcde Add feature-003

# versus fast-forward
$ git merge epic-feature
* ba40d93 Add octocat.txt
* b09d343 Add bye.txt
* 75e18c8 Add hello.txt
* 89408de Modify feature-001.txt and fix-001.txt
* c1abcde Add feature-003

See how in this case of fast-forward merge, without additional information in the commit messages themselves, it's hard to tell that the last 3 commits actually belong together as one feature?


The argument about undoing a merge with git revert -m 1 <sha-of-merge-commit> tends to favor the use of --no-ff even in the case of a branch with a single commit.
J
Jonatan Goebel

That really depends on your workflow, and how you are using branches.

Let´s say you have a "master" and two feature branches, "foo" and "bar", in development.

In this case, the branches only exist to allow different developer work without conflict, when the features are done, they need to be merged into master, and it is not important to know if those features were implemented in different branches.

But you could have a different workflow, were the branches, "foo" and "bar", refers to independent modules in your system.

In this case, some commits may change files outside the module scope. When you merge those two branches to the master, the log of the files, outside the specific module, may become confusing and hard to know where those changes come from.

You need to decide if the historic of branch´s merge is important to your workflow.

And following the comments, I prefer the rebase, and pull --rebase workflow.


Thanks, Jonathan. Do you have some links to articles or other material, explaining typical "rebase and pull --rebase workflows?" I would be interested in learning more about those approaches as well.
@Leif have you read the chapter on rewriting history from the Pro Git book? It talks about rebasing. Rebasing is one of the key, fundamental tools of Git. People use it frequently instead of merge to sync branches up with other lines of development, because it leaves behind no merge commits, and thus helps keep the history simple. It's applicable to all possible workflows you can think of. Code School has a good video on rebasing in their [Git Real course])codeschool.com/courses/git-real).
@Leif clarification, rebase is used frequently to sync feature and bugfix branches with changes made to upstream/main development branches. It's not used as commonly to integrate those branches back into the main development branches, which in that case, you should decide if you want a parallel branched history with merge --no-ff, or if you just want to do a fast-forward merge of the main branch to the tip of the feature/bugfix branch for linear history.
@Leif rebase keep your history cleaner. This works well for temporary branches. This also grants that any conflict will be solved inside the commit, and not with an extra one. About documentations, the ones that Cupcake mentioned are really good, and this githowto.com/rebasing it´s more direct about when to use rebase or merge. As I mentioned, I prefer to use rebase, but is not always possible.
@Leif one more resource for you to consider, GitHub Flow instead of git flow.