关于git branch:为什么git默认执行快进合并?

Why does git perform fast-forward merges by default?

来自mercurial,我使用分支来组织功能。
当然,我也想在我的历史中看到这种工作流程。

我使用git开始了我的新项目并完成了我的第一个功能。 合并该功能时,我意识到git使用快进,即如果可能,它会将我的更改直接应用到主分支并忘记我的分支。

因此,思考未来:我是唯一一个从事这个项目的人。 如果我使用git的默认方法(快进合并),我的历史将导致一个巨大的主分支。
没有人知道我为每个功能使用了一个单独的分支,因为最后我只有那个巨大的主分支。 这看起来不专业吗?

通过这种推理,我不想要快进合并,也不知道为什么它是默认的。 这有什么好处的?


快速合并对于短期分支是有意义的,但是在更复杂的历史中,非快进合并可以使历史更容易理解,并且更容易恢复一组提交。

警告:非快进也有潜在的副作用。请查看https://sandofsky.com/blog/git-workflow.html,避免'no-ff'的"检查点提交"打破平分或责备,并仔细考虑是否应该是

alt text
(来自nvie.com,Vincent Driessen,发表"成功的Git分支模型")

Incorporating a finished feature on develop

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

1
2
3
4
5
6
7
8
$ 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还提到了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).

快进是默认值,因为:

  • 短命分支很容易在Git中创建和使用
  • 短命分支通常会隔离许多可以在该分支内自由重组的提交
  • 这些提交实际上是主要分支的一部分:一旦重组,主分支就会快速转发以包含它们。

但是如果你期望一个主题/功能分支上的迭代工作流(即,我合并,然后我回到这个功能分支并添加一些更多的提交),那么仅包括主分支中的合并是有用的,而不是功能分支的所有中间提交。

在这种情况下,您最终可以设置这种配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[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

OP在评论中添加:

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回答:

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.

更一般地说,我补充说:

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?"

实际上,当您考虑Mercurial分支模型时,每个存储库的核心是一个分支(即使您可以创建匿名头,书签甚至命名分支)
请参阅"Git和Mercurial - 比较和对比"。

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.


让我对VonC非常全面的答案进行一些扩展:

首先,如果我没记错的话,Git默认情况下不会在快进的情况下创建合并提交的事实来自于考虑单分支"相等的存储库",其中相互拉动用于同步这两个存储库(a您可以在大多数用户的文档中找到作为第一个示例的工作流程,包括"The Git用户手册"和"按示例进行版本控制")。在这种情况下,您不使用pull来合并完全实现的分支,您可以使用它来跟上其他工作。当您碰巧执行保存并存储在存储库中的同步时,您不希望出现短暂且不重要的事实,以备将来保存。

请注意,功能分支的有用性以及在单个存储库中具有多个分支的功能仅在稍后出现,具有良好的合并支持的VCS的更广泛使用以及尝试各种基于合并的工作流。这就是为什么例如Mercurial最初只支持每个存储库一个分支(加上用于跟踪远程分支的匿名提示),如旧版"Mercurial:The Definitive Guide"中所示。

其次,当遵循使用特征分支的最佳实践时,即特征分支应该都从稳定版本开始(通常从最后一个版本开始),以便能够通过选择要合并的特征分支来挑选和选择要包括的特征,通常不会处于快速前进状态......这使得这个问题没有实际意义。合并第一个分支时,您需要担心创建一个真正的合并而不是快进(假设您没有直接在'master'上放置单个提交更改);所有其他后来的合并当然都是非快进的情况。

HTH