【问题标题】:Git Merge --no-ff makes copy of commitsGit Merge --no-ff 复制提交
【发布时间】:2016-06-09 10:08:23
【问题描述】:

我今天有一个奇怪的git行为,我想问一下这是否正常。现在,情况是这样(新的提交被添加到底部):

br1  br2
  A
    \ 
     B
     C
     D

现在,当我制作时

git checkout br1
git merge --no-ff br2

变成了这样(请自上而下阅读;最新的提交是'E')

br1  br2
  A
  | \ 
  |  B
  |  C
  |  D
  | /
  E

好的.. 现在,奇怪的是;现在我称之为“git status”;它说我在远程分支之前提交了 4 次。这是怎么回事?我不应该只有一个提交吗?

奇怪的是,当我从 Stash(基本上是 Git Web UI)检查时,它确认了这个“4 次提交”状态,我在“br1”和“br2”下看到相同的提交(BC 和 D)两个……

我假设当我使用“--no-ff”参数时,“br2”(B C D)的提交不会被复制到“br1”,而只会创建合并提交。我是不是搞错了?

【问题讨论】:

  • 使用 ABCD 而不是 0 会提供更多信息

标签: git merge fast-forward


【解决方案1】:

从技术上讲,这 4 个提交实际上是 br1 的一部分。如果您删除br2,提交仍将是br1 的一部分。当您重置合并提交时,差异就出现了。如果您进行硬重置,您将返回到合并提交之前的提交,即E。这在您想要回滚时特别有用。所以是的,你提前四次提交,但只要你不重置br1,你应该没问题。

-no-ff 也不是为了阻止 git 复制提交。这是为了将提交与br1 的树分开。这样您就可以维护分支的历史记录和提交的上下文,而不是大量与不同功能相关的提交

【讨论】:

  • 在我们的例子中,最后一次提交是 E,而不是 A。你正在阅读字母反转 :) 无论如何,如果你所说的 --no-ff 是正确的,那么我看到了历史Stash 上的 br2 完全没问题,对吧? TBH,我希望在那里只看到合并提交 A,而不是其他。
【解决方案2】:

我认为您误解了分支在 Git 中的工作方式。 Branches are lightweight,这意味着当您创建新分支或将一个分支合并到另一个分支时,不会复制任何内容。

分支是指向提交的引用。由于每个提交都与之前的提交相关联,因此当您指向一个提交时,您实际上是在指向该提交的历史

在您的示例中,E 是一个合并提交,它两个分支组合在一起,创建了一个历史记录。这意味着在将br2 合并到br1 之后,br1现在是对E 的引用)将知道br2 的历史(仍然是 引用D),因为DCBE 历史的一部分。

您的本地br1 是对E 的引用,而您的远程br1 仍然是对A 的引用。因此,br1 的历史中有四个提交(BCDE

【讨论】:

  • 好吧,让我换个说法:为什么br1 引用B C 和D?它不仅应该指 E(合并提交)吗?当我检查状态时,它显示“提前 4 次提交”,而不是“提前 1 次提交”。这让我感到困惑,就好像 Git 再次将相同的提交推送到服务器(尽管提交具有相同的哈希值)。
  • 我刚刚检查过,Stash 一直在这样做;并且由于它以线性方式显示所有内容,因此是造成混乱的原因。我想我必须让每个人都使用 git log --graph 而不是 Stash history :)
  • br1 是对E 的引用。这意味着它也知道BCD。这是因为E 有两个父母:ADD 有一个父母:CC 有一个父母:BB 有一个父级:A。因此,EAD 链接在一起。
【解决方案3】:

更新:在我写完这个答案后,发帖者改变了问题。此更新回答了其余答案未涵盖的问题之一。

好的.. 现在,奇怪的是;现在我称之为“git status”;它说我在远程分支之前提交了 4 次。这是怎么回事?我不应该只有一个提交吗?

在您合并 (--no-ff) 提交 ADE 之后,本地分支比远程分支提前 4 次提交(我想远程分支仍然指向 A)。这是正确的。

让我们计算本地分支上的提交(现在指向E)和不在远程分支上(指向A)的提交。 E 显然是其中之一。 E,作为一个合并提交,有两个父级:A(存在于远程分支上)和D(不存在于远程分支上)。当gitE 推送到远程服务器时,它需要同时推送其父服务器(否则E 在远程服务器上无效)。

D 需要C(它的父级),需要B,需要AA 已经存在于远程服务器上,链到此结束。远程服务器上不存在BCDE。必须全部推送,以保持远程服务器上数据的一致性。


回复的其余部分处理了未明确定义的原始问题。海报提到了git log --graph 和(可能)Atlassian Stash。这两个工具(以及我知道的所有其他git 接口)都首先在提交历史中显示最近的提交。这个答案使用相同的约定。

git merge --no-ff在你描述的情况下没有任何作用。

br1 作为当前分支,git merge br2 将提交引入的更改引入br1,这些更改可以从br2 访问,但不能从br1 访问。根据br1 相对于br2 的相对位置,git merge 可以创建一个新的提交(包含从br1 无法访问的提交引入的所有更改),或者它可以只是将br1 分支头移动到一个新的位置(并且不会创建任何新的提交)。

在您的情况下,br2 落后于 br1,有 1 个提交,因为 A 是一个合并提交,所以从 br2 访问的所有提交也可以从 br1 访问。


不管怎样,git merge --no-ff 用在不同的情况下。

假设我们有这个图表:

 B <-- br2
 |
 C
 |
 D
 |
 E <-- br1
 |
...

br2 分支是从br1 分支创建的(当时两者都在提交E)然后br2 被签出并三个新提交(DCB ) 已添加。

在这种情况下,命令:

git checkout br1
git merge br2

不要创建新分支。分支br1 被移动到提交Bbr2 所在的位置),仅此而已。现在可以从br2 访问的所有提交也可以从br1 访问。 br2 已成功合并到br1

这就是图表的样子:

br1 --> B <-- br2
        |
        C
        |
        D
        |
        E
        |
       ...

这种合并称为fast-forward,因为分支br1从其先前位置“快进”移动到新位置(可能与从E移动到B的分支b2相反一次提交一个)。只有当br2br1 的唯一一个子路径时才有可能(从E 开始,每个提交都有一个子提交并且路径到达B)。

--no-ff 开始发挥作用了。如果您希望此合并创建一个合并提交,其中包含提交 DCB 引入的所有更改,那么您运行:

git checkout br1
git merge --no-ff br2

--no-ff 选项的存在禁止分支b1 的快速转发,并强制创建新的合并提交。图表将如下所示:

 A <-- br1
 |\
 | B <-- br2
 | |
 | C
 | |
 | D
 |/
 E
 |
...

【讨论】:

  • 不想这么说,但您阅读了历史逆向...在我的图表中,新提交添加到底部,而不是顶部...这正是我的问题:您的答案已经是什么我期待但不是这样。当我写 --no-ff;系统仍然说“提前 4 次提交”而不是“提前 1 次提交”...
  • 我阅读了我所知道的所有git 接口所呈现的历史记录,包括git log --graph Stash,即最近的提交排在第一位。你的图表是颠倒的,原因只有你知道。我更新了答案,解释了为什么您的本地分支比远程分支提前 4 次提交。
  • 那你能解释一下,在这个问题的背景下,我怎么能在历史的后面添加提交?我的语言是从左到右、从上到下阅读的;所以我确实使用了那个约定;甚至使用字母来显示优先级...无论如何,仍然感谢您的回答,但您回答了错误的问题;不是我的,当你第一次发布这个时。
猜你喜欢
  • 2012-02-22
  • 2016-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-28
  • 1970-01-01
  • 2018-06-14
相关资源
最近更新 更多