为什么 git 默认执行快进合并?

发布于 2024-09-02 01:07:27 字数 309 浏览 10 评论 0原文

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

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

所以展望未来:我是唯一一个从事这个项目的人。如果我使用 git 的默认方法(快进合并),我的历史记录将产生一个巨大的主分支。 没有人知道我为每个功能使用了单独的分支,因为最终我将只有那个巨大的主分支。这样不会显得不专业吗?

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

怎樣才叫好 2024-09-09 01:07:27

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

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

替代文本
(来自 nvie.com文森特·德里森,帖子“< strong>成功的 Git 分支模型")

在开发中纳入已完成的功能

已完成的功能可能会合并到开发分支中,以将其添加到即将发布的版本中:

$ git checkout 开发
切换到分支“开发”
$ git merge --no-ff myfeature
正在更新 ea1b82a..05e9557
(变更摘要)
$ git 分支 -d myfeature
删除分支 myfeature (为 05e9557)。
$ git push origin 开发

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

Jakub Narębski提及 配置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添加在评论中:

我认为 [short-lived] 分支快进有一定意义,但将其设为默认操作意味着 git 假设您...经常有 [short-lived] 分支。合理吗?

杰弗罗米回答:

我认为分支的生命周期因用户而异。不过,在经验丰富的用户中,可能存在拥有更多短期分支的趋势。

对我来说,短期分支是我为了使某项操作更容易而创建的分支(变基,可能或快速修补和测试),然后一旦我“就立即删除”我完成了。
这意味着它可能应该被吸收到它派生的主题分支中,并且主题分支将合并为一个分支。没有人需要知道我在内部做了什么才能创建实现该给定功能的一系列提交。

更一般地说,我补充说:

这实际上取决于您的开发工作流程

  • 如果它是线性的,则一个分支有意义。
  • 如果您需要隔离功能并长期处理它们并反复合并它们,那么多个分支是有意义的。

请参阅“何时应该分支?

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

默认情况下,Mercurial 使用匿名轻量级代码线,其术语称为“头”。
Git 使用轻量级命名分支,通过单射映射将远程存储库中的分支名称映射到远程跟踪分支的名称。
Git“强制”您命名分支(嗯,除了单个未命名分支,这种情况称为“分离 HEAD"),但我认为这对于分支繁重的工作流程(例如主题分支工作流程)效果更好,这意味着单个存储库范例中有多个分支。

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.

白色秋天 2024-09-09 01:07:27

让我详细介绍一下 VonC非常全面的答案


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

请注意,功能分支和在单个存储库中拥有多个分支的有用性只是后来才出现的,随着具有良好合并支持的 VCS 的更广泛使用,以及尝试各种基于合并的工作流程。这就是为什么 Mercurial 最初只支持每个存储库一个分支(加上用于跟踪远程分支的匿名提示),如“Mercurial:权威指南”的旧版本中所示。


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

华泰

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文