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

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

展望未来:我是唯一一个在做这个项目的人。如果我使用git的默认方法(快进合并),我的历史记录将导致一个巨大的主分支。 没有人知道我为每个特性使用了一个单独的分支,因为最后我只有那个巨大的主分支。那样会不会显得不专业?

根据这个推理,我不想快进合并,也看不出为什么它是默认的。它有什么好呢?


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

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

(来自nvie.com, Vincent Driessen, post“一个成功的Git分支模型”)

在开发中加入一个完成的功能 完成的功能可以合并到开发分支中,以添加到即将发布的版本中:

$ 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

——no-ff标志导致合并总是创建一个新的提交对象,即使合并可以用快进执行。这避免了丢失关于特性分支的历史存在信息,并将所有添加了该特性的提交组合在一起。

Jakub narabbski也提到了config merge.ff:

默认情况下,Git在合并当前提交的后代提交时不会创建额外的合并提交。相反,当前分支的顶端是快进的。 当设置为false时,这个变量告诉Git在这种情况下创建一个额外的合并提交(相当于在命令行中给出——no-ff选项)。 当设置为'only'时,只允许这样的快进合并(相当于在命令行中给出——ff-only选项)。


快进是默认的,因为:

短寿命的分支在Git中很容易创建和使用 短命的分支经常隔离许多可以在该分支内自由重组的提交 这些提交实际上是主分支的一部分:一旦重新组织,主分支将快进以包括它们。

但是如果你预期在一个主题/特性分支上有一个迭代的工作流程(例如,我合并,然后我回到这个特性分支并添加更多的提交),那么在主分支中只包含合并是有用的,而不是在特性分支中所有的中间提交。

在这种情况下,你可以设置这样的配置文件:

[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在评论中补充道:

对于[短寿命]分支,我认为快进是有意义的,但将其作为默认操作意味着git假设您……通常有[短命的]分支。合理吗?

杰弗罗米回答:

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.

更笼统地说,我补充说:

这取决于你的开发流程: 如果它是线性的,一个分支是有意义的。 如果您需要隔离特性,并在它们上花费很长一段时间并反复地合并它们,那么几个分支是有意义的。 参见“何时应该进行分支?”

实际上,当您考虑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非常全面的回答:


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.

请注意,特性分支和在一个存储库中拥有多个分支的有用性是后来才出现的,随着VCS的广泛使用和良好的合并支持,以及尝试各种基于合并的工作流。这就是为什么Mercurial最初只支持每个存储库一个分支(加上匿名跟踪远程分支的提示),正如在“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