【问题标题】:Git history for branch after merge合并后分支的 Git 历史记录
【发布时间】:2018-02-15 18:35:30
【问题描述】:

我对git在合并后如何存储历史有点困惑。

我已成功将分支A 合并到分支B。现在,当我转到分支B 中的一个文件时,这是合并的一部分,我看到了该文件的所有历史记录分支A,但我没有看到分支B 的任何历史记录。我的分支B 的该文件的历史记录到哪里去了?

我合并的方式是通过git merge <branch>,所以在这种情况下,我在分支B 并使用git merge A


例如,在分支A 我有以下提交: a,aa,aaa对应不同的文件。

在分支B 中,我有以下提交: b,bb,bbb对应不同的文件。

现在,当我将分支 A 合并到分支 B 时,我在分支 B git log 中看到的只是 aaaaaa 历史记录。我没有看到我的 b 历史记录。

本质上,我希望我的合并是线性的,当我将 A 合并到 B 时,我希望历史记录拥有所有分支 B 历史记录,并且在历史记录之上,它将是合并刚刚发生类似于 SVN 的做法。

我当前的 git 日志历史非常混乱。

【问题讨论】:

  • 您能否分享一些之前在 B 中的提交以及在 A 中的提交以及 B 现在显示的提交。

标签: git version-control


【解决方案1】:

TL;DR

历史并没有消失,Git 只是没有显示它。

在 Git 中,历史一组提交。没有文件历史记录!

当您在dir/sub 中运行类似git log dir/sub/file.ext 或就此而言,git log dir/subgit log . 之类的命令时,Git 将合成一个(临时)文件历史记录,通过提取来自真实历史的一些子历史——提交集。这个合成过程故意放弃一些提交。例如,它会丢弃不影响您询问的任何文件的 all 提交。但默认情况下,它通过git log 调用History Simplification 的东西减少了很多。

更长

每个提交都有一个唯一的哈希 ID。例如,您可以在 git log 输出中看到这些。哈希 ID 实际上只是提交内容的加密校验和。

每个提交都存储(哈希 ID)文件的快照——Git 将其称为 。合并提交也是如此:合并提交与任何其他提交一样,都有一棵树。

每个提交还存储您的姓名(作者和提交者)以及电子邮件地址和时间戳,以便 Git 可以将这些显示给您。它存储一条日志消息——无论你给它什么——以便 Git 也可以显示它。

Git 在提交中存储的最后一件事——第二件事,实际上,就在 tree 之后——是 提交的列表,按其唯一的哈希 ID。

线性历史很容易

在处理普通的非合并提交时,查看历史记录非常简单。我们只需从 latest 提交开始,如master 之类的分支名称所标识,然后向后工作。分支名称包含最后一次提交的哈希 ID——分支的 tip——我们说分支名称​​指向那个提交:

... <--1234567...   <--master

如果提交1234567master 的提示,git log 可以向您显示提交1234567 ...并且提交1234567 在其中包含正确提交的哈希ID之前 1234567.

如果我们将真实的哈希 ID 换成单个字母,为了让事情变得更简单,我们会得到这样的结果:

A <-B <-C <-D <-E <-F <-G   <--master

提交G 指向提交F,后者又指向E,依此类推,直到我们到达第一个提交,提交A。这个提交没有指向任何地方——它不能,它是第一次提交;它不能有父对象——所以这是历史结束(开始?)的地方,在时间的开始。 Git 调用 A 一个 root 提交:一个没有父级的提交。

显示线性历史很容易,从时间的尽头开始,在开始时结束。 Git 只是一次挑出一个提交并显示出来。就是这样:

git log master

does:它从master 标识的一个提交开始,并显示它,然后显示一个提交的一个父级,然后显示之前的一个,依此类推。

当您让 Git 向您显示提交时,您可以(事实上,几乎总是)让 Git 将其显示为 patch,而不是快照。例如,git log --patch 就是这样做的。要将提交显示为补丁,Git 只是先查看提交的 的树,然后查看提交的树,然后比较两者。由于两者都是快照,因此无论从父快照到子快照的更改,都必须是使子提交的人实际所做的。

非线性历史更难

现在我们知道 Git 是反向工作的,让我们来看看更复杂的历史记录,包括包含实际合并提交的历史记录。 (我们不要因为git merge 并不总是合并这一事实而走神!)

合并提交只是一个至少有两个父级的提交。在大多数情况下,您不会看到包含三个或更多父级的提交——Git 称这些为 octopus 合并,它们不会做任何普通合并无法做到的事情,因此 octopus 合并主要是为了炫耀你的 Git-fu。 :-)

我们通常通过git checkout somebranch; git merge otherbranch 来获得合并,我们可以像这样绘制生成的提交链:

...--E--F--G------M   <-- master
         \       /
          H--I--J   <-- feature

现在,假设您运行git log master(注意:没有--patch 选项)。 Git 当然应该首先显示你提交M。但是接下来 Git 会显示哪个提交呢? J,还是G?如果显示其中之一,那之后应该显示哪一个?

Git 对这个问题有一个通用的答案:当它向您显示合并提交时,它可以将提交的 both 父级添加到“尚未显示的提交”队列中。当它向您显示一个普通的非合并提交时,它会将(单个)父级添加到同一个队列中。然后它可以遍历队列,向您展示一次提交一个,将它们的父级添加到队列中。

当历史是线性的时,队列中一次有一个提交:一个提交被删除并显示,队列现在有一个父级,您可以看到父级。

当历史有一个合并时,队列从一个提交开始,Git 将提交从队列中弹出并显示,并将两个父级都放入队列中。然后 Git 选择两个父母之一,并显示GJ,并将FI 放入队列中。队列中仍然有两个提交。 Git 弹出一个并显示该提交并打开另一个。

F 已经在队列中时,Git 最终会尝试将 F 放入队列中。 Git 避免添加两次,因此最终队列深度再次减少到一次提交,在这种情况下显示FED 等等。 (这里的细节有点复杂:队列具体是一个priority queue,其优先级由附加的git log排序参数决定,所以有不同的方法可以实现。)

您可以查看与git log --graph 的连接

如果您将--graph 添加到您的git log 命令,Git 将绘制一个略显粗糙的 ASCII 艺术图形,其中包含连接子提交与其父提交的线。这非常有助于告诉您您正在查看的提交历史毕竟不是线性的,即使 git log 一次向您显示一个提交(因为它必须)。

显示合并提交

我在上面提到过-p--patchgit log 将通过比较父快照/树与子快照/树来显示提交中的更改。但是对于合并提交,有 两个(甚至更多)父母:没有办法向您展示 父母与孩子的比较,因为至少有两个父母。

默认情况下,git log 所做的是完全放弃。它根本不显示补丁。其他命令执行更复杂的操作,您也可以说服 git log 执行此操作,但请注意,此处默认为 git log 放弃。

History Simplification(这是指向 git log 文档的可点击链接)

当您运行git log file.ext 时,Git 将故意跳过任何非合并提交,其中差异(通过比较父与子获得)不触及file.ext。这很自然:如果你有这样的链:

A--B--C--D--E   <-- master

并且您在提交AE 时更改(或首次创建)file.ext,您只想看到这两个提交。 Git 可以通过找出D-vs-E 的补丁并看到file.ext 更改(所以它应该显示 E)然后转到D 来做到这一点. C-vs-D 比较显示 file.ext 没有变化,所以 Git 不会显示 D,但它会将 C 放入优先级队列并继续访问C。这也没有对文件进行更改,因此 Git 最终移动到 B,它没有任何更改,Git 移动到 A。出于比较目的,A 中的所有文件始终是新文件——这是任何根提交的规则;所有文件都已添加,因此 Git 也会向您显示 A

不过,我们刚刚看到,默认情况下 git log 不喜欢为合并计算补丁。太难了!所以git log 通常不会在这里显示合并。但是,它确实尝试简化提交图的任何部分。正如文档所说,默认模式:

如果最终结果相同,则修剪一些侧枝...

如果提交是合并,并且 [文件与] 一个父级相同,则仅遵循该父级。 ...否则,请跟随所有父母。

因此,在我们图中的 M 之类的合并提交中,Git 将进行快速检查:M 中的 file.extG 中的相同吗?如果是这样,请将G 添加到队列中。如果不是,MJ 一样吗?如果是这样,请将J 添加到队列中。否则——即,file.extM 中与在both G J 中的不同——将GJ 添加到队列中.

历史简化还有其他模式,您可以使用各种标志进行选择。这个答案已经太长了,所以我会把它们留给文档(见上面的链接)。

结论

由于 Git 执行的历史简化,您不能从 git log -- <em>path</em> 向您展示的内容中得出太多推论。如果您想查看所有内容,请考虑改为运行 git log --full-history -m -p -- <em>path</em>-m 选项将每个合并拆分为 git diff 目的(这与 -p 选项一起使用),--full-history 强制 Git 始终跟随所有父母。

【讨论】:

  • 谢谢!对于线性历史,使用 git rebase 可以代替使用 git merge 吗?我真的不喜欢 git merge 显示历史的方式。令人困惑。
  • 为了保持您自己的历史线性,而其他人正在添加提交,您确实可以使用面向 rebase 的工作流程。有很多页面介绍了如何执行此操作、何时执行此操作、何时执行此操作、为什么它很好、为什么它很糟糕并且没有人应该这样做,等等:换句话说,这是一个见仁见智的问题。阅读 rebase 如何在内部工作并自己决定! :-)
【解决方案2】:

它应该还在那里。尝试使用git log --graph 查看它。

【讨论】:

    猜你喜欢
    • 2011-11-08
    • 1970-01-01
    • 2010-11-02
    • 2011-03-02
    • 2016-01-11
    • 2021-03-11
    • 2016-03-13
    • 2023-03-27
    • 2016-10-10
    相关资源
    最近更新 更多