git-log缺少未完成更改的合并提交

时间:2019-06-12 00:55:10

标签: git

考虑此测试脚本。

#!/bin/sh -x

#initialize repository
rm -rf missing-merge-log
mkdir missing-merge-log
cd missing-merge-log
git init

# create files, x, y, and z
echo x > x
echo y > y
echo z > z
git add -A .
git commit -m "initial commit"

# create a branch
git branch branch

# change x and z on master
echo x2 > x
echo z2 > z
git commit -am "changed x to x2, z to z2"
git log master -- x

# change y and z on the branch
git checkout branch
echo y2 > y
echo z3 > z
git commit -am "changed y to y2, z to z3"

# merge master into branch
git merge master
# resolve z conflict
echo z23 > z
git add z
# undo changes to x during merge conflict resolution
# (imagine this was developer error)
git checkout branch -- x
git commit --no-edit

# merge branch into master
git checkout master
git merge branch

# now the x2 commit is entirely missing from the log
git log master -- x

我们首先创建三个文件xyz,然后创建一个名为branch的分支。在master中,对xz进行更改,在分支中,对yz进行更改。

然后,在分支中,我们从master进行合并,但是在解决合并冲突期间,我们将更改还原为x。 (为此示例,请想象这是开发人员错误;开发人员无意拒绝对x所做的更改。)

最后,回到master,我们合并分支中的更改。

我希望此时git log x会显示三个更改:初始提交,在master上更改为x,以及将更改恢复为x的分支提交。

但是,相反,在脚本末尾,git log仅显示了对x的初始提交,没有任何迹象表明x曾经被修改过!这是使用git版本2.22.0。

git log为什么要这样做? git log -- x是否有参数可以显示此处发生的情况? git log --all -- x没有帮助。

({git log --all确实显示了所有内容,但在现实生活中会显示所有文件的所有更改,包括对yz的不相关更改,这些更改太麻烦了。 )

1 个答案:

答案 0 :(得分:4)

TL; DR

使用--full-history-但您可能还需要更多选项,因此请继续阅读。

首先,非常感谢您的复制脚本!这在这里非常有用。

下一步:

  

({git log --all确实显示了所有内容,但在现实生活中会显示所有文件的所有更改,包括对yz的不相关更改,这些更改太麻烦了。 )

是的。但是它表明,任何 commits都没有问题; 问题完全是git log在这里造成的。它与可怕的History Simplification模式有关,该模式是:

  

git log master -- x

调用。

git log,不简化历史记录

让我添加以下内容的输出:

git log --all --decorate --oneline --graph

("git log with help from A DOG"),由于我使用脚本进行了复制,因此其哈希ID与您(或其他进行其他复制的人)将具有不同的哈希ID,但是具有相同的结构,因此让我们来讨论一下提交:

*   cc7285d (HEAD -> master, branch) Merge branch 'master' into branch
|\  
| * ad686b0 changed x to x2, z to z2
* | dcaa916 changed y to y2, z to z3
|/  
* a222cef initial commit

现在,普通的git log没有-- x来检查文件x没有启用历史记录简化功能。 Git从您指定的提交开始,例如:

git log dcaa916

dcaa916开始-如果未指定,则从HEAD开始。

然后,在这种情况下,git log从提交cc7285d开始。 Git显示该提交,然后继续执行该提交的父级。这里有两个父对象dcaa916ad686b0,因此Git将两个地方都放置到priority queue中。然后,它从队列的开头提取提交之一。当我尝试此操作时,它拉出的是dcaa916。 (在更实际的图形中,默认情况下它将使用带有较晚提交者时间戳的图,但是在使用脚本构建了该存储库后,两个提交都具有相同的时间戳。)Git显示了提交并将dcaa916的父放置a222cef进入队列。为了保持拓扑结构的合理性,给定此特定图表,队列前面的提交现在始终为ad686b0,因此Git会显示该提交,然后....

好吧,ad686b0的父级是a222cef,但是a222cef已经在队列中!这就是“保持拓扑结构合理性”的地方。不早显示a222cef可以确保我们不会意外两次显示a222cef(以及其他问题)。队列中现在有a222cef,并且没有其他内容,因此git loga222cef从队列中移出,显示a222cef,并将a222cef的父母放入队列。在此复制者示例中,没有父母,因此队列保持为空,git log可以结束,这就是我们在常规git log中看到的内容。在DOG的帮助下,我们也可以得到图形和单行输出变体。

git log(具有简化的历史记录)

Git没有文件历史记录。存储库中的历史记录由 commits 组成。但是git log将尽力显示文件历史记录。为了做到这一点,它必须综合一个,而要做到 ,Git的作者选择只是省略一些提交子集。该文档试图用一个句子的段落来解释这一点:

  

有时候,您只对历史的一部分感兴趣,例如,修改特定的提交。但是 History Simplification (历史简化)有两个部分,一个部分是选择提交,另一部分是如何进行提交,因为存在多种简化历史的策略。

我认为这一段落的解释是行不通的,但是我也没有想出我认为是 right 的解释。 :-)他们试图在这里表达的是这样:

  • Git不会向您显示所有提交。这将显示一些选定的提交子集。

    这部分很合理。我们已经看到,即使没有“历史简化”功能:Git也从 last 提交开始,我们使用分支名称或HEAD或其他名称指定提交,然后向后工作,一次提交到时间,必要时可一次将多个提交提交到其优先级队列中。

    使用简化历史记录,我们仍然使用优先级队列浏览提交图,但是对于许多提交,我们只是不显示提交。到目前为止还可以,但是现在Git陷入了扭曲,导致他们写了那段怪异的段落。

  • 如果Git不会向您显示所有提交,那么它可能会作弊,甚至不会费心地跟随一些分叉。

    这是很难表达的部分。当我们从分支尖端向后移到提交图根时,每一个 merge 提交(两个提交流汇合在一起)成为一个分叉,其中两个提交流发散。特别是,提交cc7285d是合并,当我们进行历史简化时,Git总是将父母双方都放在队列中。但是,当我们做到进行历史简化时,Git有时不会将这些提交放入队列。

这里真正棘手的部分是确定哪些提交进入队列,这就是文档的“更详细的解释”和 TREESAME 观念出现的地方。我鼓励人们仔细阅读它,因为它具有很多有用的信息,但是它包装得非常密集,并且一开始不是很擅长定义 TREESAME。该文档是这样写的:

  

假设您将foo指定为。我们将调用修改foo的提交!TREESAME,其余的称为TREESAME。 (在针对foo进行比较的差异过滤中,它们分别看起来不同且相等。)

此定义取决于提交是非合并提交!

所有提交都是快照(或更正确地说,是包含快照)。因此,没有提交会单独修改 any 文件。它只是文件,或者没有文件。如果包含文件,则文件具有 的某些特定内容。要将提交视为变更(作为一组修改),我们需要选择一些 other 提交,提取两个 提交,然后将两者进行比较。对于非合并提交,有一个显而易见的提交要使用:父提交。给定一些提交链:

...--F--G--H--...

通过提取HG并进行比较,我们将查看提交H中发生了什么变化。通过提取GF并进行比较,我们将了解G中发生了什么变化。这就是这里的TREESAME段落所要解决的问题:我们拿FG,然后除去所有您要查询的文件。然后,我们比较其余文件。在简化的FG中,它们是否相同?如果是这样,FG是TREESAME。如果不是,则不是。

但是,根据定义,合并提交至少具有两个父级:

...--K
      \
       M
      /
...--L

如果我们正在进行合并提交M,我们会选择哪个父母来确定什么是TREESAME,什么不是?

Git的答案是一次比较所有父母对 all 的提交。某些比较可能会导致“是TREESAME”,而其他比较可能会导致“ is不是TREESAME”。例如,foo中的文件M可能与foo中的文件K和/或foo中的文件L相匹配。

Git使用哪种提交取决于您为git log提供的其他选项:

  

默认模式

     

如果不是对任何父级的TREESAME,则包括提交(尽管可以更改,请参见下面的--sparse)。如果提交是合并,并且对一个父对象是TREESAME,则仅遵循该父对象。 (即使有几个TREESAME父母,也只能跟随其中一个。)否则,请跟随所有父母。

因此,我们考虑合并cc7285d,并将其与其(两个)父母中的每一个进行比较:

$ git diff --name-status cc7285d^1 cc7285d
M       z
$ git diff --name-status cc7285d^2 cc7285d
M       x
M       y
M       z

这意味着git log仅会走过第一个父级,并提交cc7285d^1(即dcaa916),这是做的't 更改x

  

...如果提交是合并,并且对一个父对象是TREESAME,则仅跟随该父对象。 ...

因此 this git log进行cc7285d提交,然后提交dcaa916,然后提交a222cef,然后停止。根本不会查看提交cc7285d^2(即ad686b0)。

git log文档本节的其余部分描述了选项--full-history--dense--sparse--simplify-merges(甚至我也没有了解最后一个选项的真正目的:-))。在所有这些中,--full-history是最明显的,并且可以完成您想要的操作。 (--ancestry-path--simplify-by-decoration也是本节,但它们不会影响合并时的路径。)

注意事项

尽管--full-history将确保Git遍历每个合并的所有“分支”,但是git log -p本身默认情况下对合并提交显示 no 差异。您必须添加三个选项之一(-c--cc-m),以使git log -p完全显示任何合并的差异。

如果您的目标是专门找到一个 bad 两亲合并,而该合并会丢弃应该保留的某些特定更改,则您可能希望显示该合并中的差异到至少一个,也许还有两个父母。 git show命令将执行此操作,但是其默认值为--cc样式。 git log命令根本不会做。如果将--cc添加到git log,将得到与默认显示的git show相同的差异-也不起作用。

--cc-c选项告诉Git,在查看合并提交时,Git应将提交与所有父项进行比较,然后生成 summary diff,而不是详细的。摘要的内容不包括与一个或所有父母匹配的部分。您正在寻找一个意外删除了重要更改的合并-与它的父级中的至少一个相同的合并,而该合并应该与该父级不同 。这个组合的差异会隐藏不是但应该更改的地方。因此,您想要-c--cc

剩下-m选项。当git showgit log要显示差异时,并且提交是合并提交时,Git将显示每个父对象一个差异。也就是说,对于像M这样的合并提交,git show -m将首先比较KM并显示差异。然后它将比较LM并显示另一个差异。在特定情况下,这就是您想要的选项。

请注意,-m--first-parent很好地结合在一起,以仅显示每个合并的第一个父对象的完整差异。通常,这正是您想要的。

相关问题