是否有任何命令可以在两个分支中找到文件的共同祖先?
假设有一个文件在两个分支中独立修改。我想找到两个分支共有的该文件的最后一个版本。我相信这归结为在两个分支中找到该文件的单个父提交。
但是,merge-base只允许为提交而不是文件查找父提交。我试图指定两个最后提交修改它们各自分支中的文件,但我得到的父提交不在任何一个分支中该文件的更改历史记录中,这可能是由于提交通常包含更改超过一个档案。
答案 0 :(得分:3)
是否有任何命令可以在两个分支中找到文件的共同祖先?
不,或是,或者可能:这取决于你的意思。
假设有一个文件在两个分支中独立修改。我想找到两个分支共有的该文件的最后一个版本。我相信这归结为在两个分支中找到该文件的单个父提交。
文件没有父提交。只有提交有父提交。
更糟糕的是,每个提交都存储每个文件(在提交时,每个文件都是暂存区域的一部分,即)。因此,从某种意义上说,这是每次提交,或者是普通的普通合并基础。显然,这不是你的意思,所以让我们看看我们在这里还能说些什么。
让我们尝试思考实验。假设您有两个分支提示br1
和br2
最终具有共同的祖先提交:
o--o--o--Y <-- br1
/
...--X
\
o--o--o--Z <-- br2
还要考虑一个更复杂的图表,它仍然有一个共同的祖先和两个分支提示:
o
/ \
o o--o--Y <-- br1
/ \ /
...--X o
\
o--o--o--Z <-- br2
鉴于图表的方式和git merge
的工作方式,&#34;常规&#34;合并(或使用git merge-base
)将找到合并基础X
,此时我认为大多数人会同意某个文件位于X
并且已传播(可能已重命名) Y
以及Z
在X
中有一个共同的祖先。此共同祖先可能会在Y
或Z
(或甚至Y
和 Z
中)的不同路径名下显示,但它&#39 ; s仍然是共同的祖先,因此它被用作合并基础版本。
这里有一个问题:git不记录重命名。相反,它发现&#34;他们每次做出差异。为了发现generic/b.c
中的文件X
现在specific/b.c
Y
,{g}必须将X
下的整个树对Y
下的整个树进行区分{1}}。这意味着它必须找到提交X
。
这对于常规合并来说并不太难,因为它使用提交图:它从提交Y
和Z
开始,并向后遍历历史记录以查找最近的公共提交(属于当然X
这里)。一旦我们知道(或git知道)使用X
,就会产生两个差异,X
- vs - Y
和X
- vs - Z
,以及然后它可以将更改合并到公共文件的内容,而不管它在Y
和Z
中的路径。< / p>
(交叉合并存在次要问题,可能存在多个最近共同提交,但我们现在可以忽略它。)
如果我们(至少是暂时的)放弃了寻找重命名的想法,我们可以根据一些路径 p
,使用不同的方法,我认为< / em>就是你要问的:
cy
和X
之间的每次提交 Y
(包括X
并从Y
向后工作),每个提交在cz
和X
之间提交 Z
(同样从Z
向后工作),比较cy/p
和cz/p
。 请注意,这会将X
的路径 p
的版本与X
的版本进行比较(当然相同的),也反对任何一个提交链上的每个版本,同时也将每个版本与其他版本进行比较。
制作了这个完整的矩阵(我们可以在以后优化),我们现在可以找到许多有趣的&#34;承诺:
cy
- 至 - X
链中的最后一次提交 Y
,其中 p
具有相同的内容它在X
中的内容(这是该链中最新的提交 p
不变)cz
- 至 - X
链中的最后一次提交 Z
,其中 p
具有相同的内容它在X
中的内容(另一条链中最新的未更改)cy
,其中 p
与提交Y
中的内容相同(这是最后一次路径 p
已在X
- 至 - Y
链中修改cz
,其中 p
与提交Z
p
具有相同内容的任何提交与其他链中的任何提交相同。我想你可能正在考虑在这里找到第1项和第2项。但是,为什么并不清楚。如果您只关心路径 p
下存储的内容,我们已经确定(上文)这两个提交会在 p
下存储相同的内容正如您在X
中找到的那样。所以X:p
是&#34;同样好的&#34;在识别这些内容时,您也可以使用提交X
。
如果您正在谈论找到第3项和第4项,那么为什么并不是很清楚,因为我们已经确定这些内容与< 相同em> p
作为提交最多提交,因此Y:p
和Z:p
对于识别这些内容同样有用。
但也许您正在使用第5项:在两个链上提交,其中路径 p
下的内容相同(与另一个链上的其他提交相同) ,但不一定与tip-most提交中的内容相同。
可能有很多这样的对。例如,假设在X
(git merge-base
找到的绝对共同的祖先)中,路径 p
有五行。然后,在向Y
前进时,该路径中的第一个提交将删除最后一行。同时在X
- 到 - Z
序列中,几个提交保留所有5行,然后一个删除最后一行。现在,此版本的 p
在两个开发行中都是相同的,直到修改 p
的下一个提交。让我们在X
到 - Z
序列中说出另一行被删除的情况。然后在X
- 到 - Y
序列中删除相同的行;然后,两个提交删除更多行,直到最后一个或两个分支提示文件完全为空。
定义&#34;最近的&#34;还有另一个问题。让我们再看一下更为复杂的X
- 到 - Y
图片段,但会加入一些更有区别的字母:
R
/ \
P T--o--Y <-- br1
/ \ /
...--X S
假设路径 p
在提交R
和S
中具有相同的内容,但在P
和{{1}中都有所不同}。两者都与T
或X
的图表距离相同。只要您仅关心路径 Y
,这可能无关紧要,但它确实表明不一定是唯一的提交。< / p>
在我开始使用你想要使用的一些命令之前,这是很多措辞,以便解决你想要解决的任何问题。
使您更接近解决方案的命令(可能甚至一直在那里,取决于您想要什么,尽管您似乎可能需要使用其他命令,有些甚至不是git命令)是git rev-list
。这可以找到修改特定路径的提交(与那些提交&#39; parent相比;请注意,合并必须特别处理,因为它们具有多个父提交)。如果您执行使用一个或多个路径来限制p
列出的修订,请注意它将执行&#34;历史记录简化&#34;以便从输出中省略一些提交。根据您希望处理DAG级分支(如更复杂的git rev-list
- 至 - X
链中的分支)的方式,这可能是您想要的。
基本上,Y
会发现可以从git rev-list X..Y -- path
到达的提交,不包括Y
可以修改的提交,修改X
,其中&#34;修改&#34;表示&#34;对父母的差异显示对该路径的改变&#34;。 (有关此处理如何合并,请参阅文档。)列出提交的顺序取决于您选择的排序(有或没有拓扑约束;请参阅&#34;提交订购&#34;部分)。
如果您使用path
重复此操作,则可以找到哪些提交修改了那里的路径。
这两个X..Z
基本上是从git rev-list
到两个分支提示的整个修订链,但是因为它们允许你将输出限制为&#34;提交修改某些路径( s)&#34;,他们可以优化我在思想实验中概述的过程。
您可能希望在此处包含提交X
。默认情况下,X
赢了:您可以提前开始一次提交(在rev-list
的父级),但如果X
本身是合并,则可能会失败;或者您可以使用X
来指示--boundary
包含提交rev-list
的SHA-1(以X
为前缀)。
要确定存储在特定路径下的内容在两个不同的提交中是否相同 - 显然,如果您在此处使用相同的提交ID两次,内容是相同的,但它仍然有效 - 您可以比较存储的blob& #39; s SHA-1 ID:
-
这些都不会检测到重命名;为此,你必须使用一个完整的path=dir/file
...
rev_a=... # something from git rev-list, for instance
rev_b=...
if [ $(git rev-parse ${rev_a}:${path}) = $(git rev-parse ${rev_b}:${path} ]; then
... the contents match ...
else
... the contents differ (at least slightly) ...
fi
(打开重命名检测)。