考虑此测试脚本。
#!/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
我们首先创建三个文件x
,y
和z
,然后创建一个名为branch
的分支。在master
中,对x
和z
进行更改,在分支中,对y
和z
进行更改。
然后,在分支中,我们从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
确实显示了所有内容,但在现实生活中会显示所有文件的所有更改,包括对y
和z
的不相关更改,这些更改太麻烦了。 )
答案 0 :(得分:4)
使用--full-history
-但您可能还需要更多选项,因此请继续阅读。
首先,非常感谢您的复制脚本!这在这里非常有用。
下一步:
({
git log --all
确实显示了所有内容,但在现实生活中会显示所有文件的所有更改,包括对y
和z
的不相关更改,这些更改太麻烦了。 )
是的。但是它表明,任何 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显示该提交,然后继续执行该提交的父级。这里有两个父对象dcaa916
和ad686b0
,因此Git将两个地方都放置到priority queue中。然后,它从队列的开头提取提交之一。当我尝试此操作时,它拉出的是dcaa916
。 (在更实际的图形中,默认情况下它将使用带有较晚提交者时间戳的图,但是在使用脚本构建了该存储库后,两个提交都具有相同的时间戳。)Git显示了提交并将dcaa916
的父放置a222cef
进入队列。为了保持拓扑结构的合理性,给定此特定图表,队列前面的提交现在始终为ad686b0
,因此Git会显示该提交,然后....
好吧,ad686b0
的父级是a222cef
,但是a222cef
已经在队列中!这就是“保持拓扑结构合理性”的地方。不早显示a222cef
可以确保我们不会意外两次显示a222cef
(以及其他问题)。队列中现在有a222cef
,并且没有其他内容,因此git log
将a222cef
从队列中移出,显示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--...
通过提取H
和G
并进行比较,我们将查看提交H
中发生了什么变化。通过提取G
和F
并进行比较,我们将了解G
中发生了什么变化。这就是这里的TREESAME段落所要解决的问题:我们拿F
和G
,然后除去所有您要查询的文件。然后,我们比较其余文件。在简化的F
和G
中,它们是否相同?如果是这样,F
和G
是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 show
或git log
要显示差异时,并且提交是合并提交时,Git将显示每个父对象一个差异。也就是说,对于像M
这样的合并提交,git show -m
将首先比较K
与M
并显示差异。然后它将比较L
与M
并显示另一个差异。在特定情况下,这就是您想要的选项。
请注意,-m
与--first-parent
很好地结合在一起,以仅显示每个合并的第一个父对象的完整差异。通常,这正是您想要的。