【问题标题】:What effect does the `--no-ff` flag have for `git merge`?`git merge` 和 `git merge --no-ff` 有什么区别?
【发布时间】:2012-02-22 13:16:59
【问题描述】:

使用gitk log,我无法发现两者之间的区别。如何观察差异(使用 git 命令或某些工具)?

【问题讨论】:

标签: git merge fast-forward


【解决方案1】:

如果--no-ff 标志检测到您当前的HEAD 是您尝试合并的提交的祖先,则它会阻止git merge 执行“快进”。快进是当 git 不构造合并提交时,只是将您的分支指针移动到指向传入的提交。在没有任何本地更改的情况下执行git pull 时,通常会发生这种情况。

但是,有时您希望防止这种行为发生,通常是因为您想要维护特定的分支拓扑(例如,您正在合并一个主题分支,并且您希望确保它在阅读历史记录时看起来像这样)。为此,您可以传递--no-ff 标志,git merge始终构造一个合并而不是快进。

同样,如果您想执行git pull 或使用git merge 以显式快进,并且如果它不能快进,您想退出,那么您可以使用--ff-only旗帜。这样您就可以不假思索地定期执行git pull --ff-only 之类的操作,然后如果出错,您可以返回并决定是要合并还是变基。

【讨论】:

  • 为了更直接地回答 OP 的问题:它们并不总是不同的,但如果它们不同,那么从 gitkgit log --graph 可以清楚地看出,快进合并没有创建合并提交,而非快进的提交。
  • 最好扩展一下避免使用 ff 的原因:作者提到“特定分支拓扑”意味着在 --no-ff 情况下,额外的合并提交用作合并的标记。优点是带有作者姓名和合并名称的显式合并标记。缺点是非线性历史,看起来像一组会聚的铁轨。合并的一个可能的心理副作用是贡献者由于审查过程较长而失去兴趣:blog.spreedly.com/2014/06/24/…
  • 可以说--no-ff 从一个功能到开发或开发到掌握类似于合并一个拉取请求吗?
  • @merlinpatt 当然。如果你在 GitHub 中合并一个拉取请求,它相当于 --no-ff
  • 这是一个很好的解释。伙计,没有足够好的 git 解释让人们理解为什么干净的 git 历史真的非常重要(包括我!)
【解决方案2】:

此问题的图形答案

Here is a site 带有使用git merge --no-ff 的清晰解释和图形说明:

直到我看到这个,我完全迷失了 git。使用--no-ff 允许查看历史记录的人清楚地see the branch you checked out 继续工作。 (该链接指向 github 的“网络”可视化工具)这里是 another great reference 带有插图。这个参考很好地补充了第一个,更多地关注那些不太熟悉 git 的人。


像我这样的新手的基本信息

如果您像我一样,而不是 Git 专家,my answer here 描述了在不从本地文件系统中删除文件的情况下处理从 git 跟踪中删除文件,这似乎记录不充分,但经常发生。另一个新手情况是getting current code,它仍然设法躲避我。


示例工作流程

我在我的网站上更新了一个包,不得不返回我的笔记来查看我的工作流程;我认为在这个答案中添加一个例子很有用。

我的 git 命令工作流程:

git checkout -b contact-form
(do your work on "contact-form")
git status
git commit -am  "updated form in contact module"
git checkout master
git merge --no-ff contact-form
git branch -d contact-form
git push origin master

如下:实际用法,包括解释。
注意:下面的输出被截断; git 相当冗长。

$ git status
# On branch master
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   ecc/Desktop.php
#       modified:   ecc/Mobile.php
#       deleted:    ecc/ecc-config.php
#       modified:   ecc/readme.txt
#       modified:   ecc/test.php
#       deleted:    passthru-adapter.igs
#       deleted:    shop/mickey/index.php
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       ecc/upgrade.php
#       ecc/webgility-config.php
#       ecc/webgility-config.php.bak
#       ecc/webgility-magento.php

请注意上面的 3 件事:
1) 在输出中,您可以看到 ECC 包升级的更改,包括添加新文件。
2) 还要注意有两个文件(不在/ecc 文件夹中)我删除了与此更改无关的文件。我不会将这些文件删除与 ecc 混淆,而是稍后创建一个不同的 cleanup 分支以反映这些文件的删除。
3)我没有按照我的工作流程!当我试图让 ecc 再次工作时,我忘记了 git。

下面:我通常不会做包罗万象的git commit -am "updated ecc package",我只想将文件添加到/ecc 文件夹中。那些被删除的文件并不是我git add 的特定部分,但是因为它们已经在 git 中被跟踪,所以我需要从这个分支的提交中删除它们:

$ git checkout -b ecc
$ git add ecc/*
$ git reset HEAD passthru-adapter.igs
$ git reset HEAD shop/mickey/index.php
Unstaged changes after reset:
M       passthru-adapter.igs
M       shop/mickey/index.php

$ git commit -m "Webgility ecc desktop connector files; integrates with Quickbooks"

$ git checkout master
D       passthru-adapter.igs
D       shop/mickey/index.php
Switched to branch 'master'
$ git merge --no-ff ecc
$ git branch -d ecc
Deleted branch ecc (was 98269a2).
$ git push origin master
Counting objects: 22, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (14/14), 59.00 KiB, done.
Total 14 (delta 10), reused 0 (delta 0)
To git@github.com:me/mywebsite.git
   8a0d9ec..333eff5  master -> master



上面的自动化脚本

在一天内使用此过程 10 多次后,我开始编写批处理脚本来执行命令,因此我制作了一个几乎正确的 git_update.sh &lt;branch&gt; &lt;"commit message"&gt; 脚本来执行上述步骤。 Here is the Gist source 用于该脚本。

我从git status 生成的“修改”列表中选择文件,而不是git commit -am,然后将它们粘贴到此脚本中。这是因为我进行了数十次编辑,但希望使用不同的分支名称来帮助对更改进行分组。

【讨论】:

  • 在与--no-ff 选项合并后,您还能安全地删除分支吗?
  • @DesignerGuy 是的,您可以安全地删除旧分支。将分支 es 视为指向特定提交的指针。
  • 我发现链接页面中的这段文字很有帮助:没有 --no-ff “从 Git 历史记录中不可能看到哪些提交对象一起实现了一个功能——你必须手动阅读所有日志消息。”
  • 一张图片永远胜过一千个字!
  • 第二张图没有显示特征分支,为什么呢?它仍然存在不是吗?
【解决方案3】:

Merge Strategies

显式合并(也称为非快进):创建新的合并提交。 (如果你使用--no-ff,这就是你会得到的。)

Fast Forward Merge:快速前进,不创建新的提交:

Rebase:建立新的基础级别:

挤压:用力挤压(某物)使其变平:

【讨论】:

  • 那么Rebase和快进合并有什么区别呢?
  • @ThanosFisherman 不同之处在于,对于 ff 合并,当前分支的 HEAD 必须是您要合并的提交的祖先。就像上面的莉莉巴拉德的答案stackoverflow.com/a/9069127/482702
  • @ThanosFisherman,Fast-Forward 维护相同的基础并尝试在共享基础之后合并来自相同更改的所有文件更改。一个变基“分离”你的分支,然后在基础之上提交后应用提交(就像你刚刚签出一个新的,例如主分支),然后,尝试应用在新基础上完成的更改。 rebase 通常在您想要合并到 master 之前使用,以便它将主题提交应用到 master 之上,而不会发生合并冲突。
  • 第一个图像中的“合并提交”是什么?两个红色的feature 提交在哪里消失了?您的 merge commit 看起来像是压缩提交,而不是合并提交。
【解决方案4】:

--no-ff 选项确保不会发生快进合并,并且始终会创建新的提交对象。如果您希望 git 维护功能分支的历史记录,这可能是可取的。 在上图中,左侧是使用git merge --no-ff 后的git 历史示例,右侧是使用git merge 的示例,其中可以进行ff 合并。

编辑:此图像的先前版本仅指示合并提交的单个父级。 合并提交有多个父提交,git 使用这些提交来维护“功能分支”和原始分支的历史记录。多个父链接以绿色突出显示。

【讨论】:

  • 绿色箭头和黑色箭头分别代表什么?
  • 绿色箭头表示提交与父级之间的链接,其中提交有多个父级。正常提交(黑色)只有一个父级。
  • @DanielSmith 你是如何画出这个优雅的图表的?
  • @chancyWu 与 Adob​​e Illustrator
  • 第二张图没有显示特征分支,为什么呢?它仍然存在不是吗?
【解决方案5】:

这是一个老问题,在其他帖子中有些微妙地提到,但让我点击的解释是非快进合并需要单独提交。 p>

【讨论】:

  • 请您查看我上面回答中的工作流程:这是否意味着我需要超出我现在所拥有的额外提交?谢谢。
  • @ChrisK git merge --no-ff ecc 您只需在 git log 中为分支主服务器添加一个额外的合并提交。如果 master 指向提交 ecc 的直接祖先,则技术上不需要这样做,但通过指定 --no-ff 选项,您将强制创建该合并提交。标题为:Merge branch 'ecc'
  • 很棒的总结! :)
【解决方案6】:

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

【讨论】:

    【解决方案7】:

    其他答案很好地表明--no-ff 导致合并提交。这保留了有关功能分支的历史信息,这很有用,因为功能分支会定期清理和删除。

    此答案可能会提供有关何时使用或不使用 --no-ff 的上下文。

    从功能合并到主分支:使用--no-ff

    工作示例:

    $ git checkout -b NewFeature
    [work...work...work]
    $ git commit -am  "New feature complete!"
    $ git checkout main
    $ git merge --no-ff NewFeature
    $ git push origin main
    $ git branch -d NewFeature
    

    合并从主分支到特性分支的更改:离开--no-ff

    工作示例:

    $ git checkout -b NewFeature
    [work...work...work]
    [New changes made for HotFix in the main branch! Lets get them...]
    $ git commit -am  "New feature in progress"
    $ git pull origin main
    [shortcut for "git fetch origin main", "git merge origin main"]
    

    【讨论】:

      【解决方案8】:

      什么是快进?

      快进是 Git 在您合并或变基时仅在您签出的分支之前执行的操作。

      鉴于以下分支设置:

      你的两个分支都引用了同一个提交。他们都有完全相同的历史。现在提交一些功能。

      master 分支仍在引用 7ddac6c,而该功能已提前两次提交。现在可以在 master 之前考虑 feature 分支。

      现在相对容易看出 Git 执行快进时会发生什么。它只是更新主分支以引用该功能所做的确切提交。不会对存储库本身进行任何更改,因为该功能的提交已经包含所有必要的更改。

      您的存储库历史现在看起来像这样:

      什么时候不会发生快进?

      在原始分支和新分支中进行更改时不会发生快进。

      如果您要将功能合并或 rebase 到 master 上,Git 将无法进行快进,因为树已经分叉了。考虑到 Git 提交是不可变的,Git 无法在不更改父引用的情况下将提交从 feature 获取到 master。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-03-27
        • 2013-05-15
        • 2021-03-19
        • 2012-07-29
        • 2016-05-19
        • 2016-11-10
        • 2016-06-09
        相关资源
        最近更新 更多