Git 像单个文件一样合并基础
Git merge-base like for a single file
有没有命令可以在两个分支中找到一个文件的共同祖先?
假设有一个文件在两个分支中被独立修改。我想找到两个分支通用的该文件的最新版本。我相信这归结为在两个分支中找到文件的单父提交。
但是,merge-base 只允许为提交查找父提交,而不是文件。我试图指定最后两次提交修改各自分支中的文件,但我得到的父提交不在任何一个分支中该文件的更改历史记录中,这可能是由于提交通常包含对多个文件的更改一个文件。
Is there any command to find a common ancestor of a file in two branches?
不,或者是,或者也许:这取决于你的意思。
Say there is a file that was modified independently in two branches. I want to find the last version of that file common to both branches. I believe this boils down to finding the single parent commit for the file in both branches.
文件没有父提交。只有提交有父提交。
更糟糕的是,每次提交都会存储每个文件(即提交时属于暂存区的每个文件)。因此,从某种意义上说,这要么是每次提交,要么是常规的普通合并基础。显然这不是你的意思,所以让我们看看在这里还能说些什么。
让我们来做一个思想实验。假设你有两个分支提示 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
的工作方式,"regular" 合并(或使用 git merge-base
)将找到合并基础 X
,其中我认为大多数人会同意 X
中的某些文件传播(可能重命名)到 Y
,也传播到 Z
,在 [=18] 中有一个共同的祖先=].这个共同的祖先可能出现在 Y
或 Z
中的不同路径名下(甚至在 Y
和 Z
中)但是它仍然是共同的祖先,因此它被用作合并基础版本。
这里有一个问题:git 不记录重命名。相反,它 "discovers" 它们每次都会产生差异。为了发现 X
中的文件 generic/b.c
现在是 Y
中的 specific/b.c
,git 必须将 X
下的整个树与Y
下的整棵树。这意味着它必须找到提交 X
.
这对于常规合并来说并不难,因为它使用提交图:它从提交 Y
和 Z
开始,并向后遍历历史以找到最近的公共提交(这里当然是X
)。一旦我们知道(或 git 知道)使用 X
,它就会产生两个差异,X
-vs-Y
和 X
-vs-Z
,然后它可以将更改合并到公共文件的 contents,而不管它在 [=20] 中的 path 是什么=] 和 Z
.
(交叉合并还有一个次要问题,其中可能有多个最近的共同提交,但我们现在可以忽略它。)
如果我们(至少暂时)放弃寻找重命名的想法,我们可以,给定一些路径 p
,使用不同的方法,我think 你问的是:
- 对于
X
和 Y
之间的每个提交 cy
(包括 X
并从 Y
),以及 X
和 Z
之间的每个提交 cz
(同样从 Z
向后工作),比较 <em>cy</em>/<em>p</em>
和 <em>cz</em>/<em>p</em>
.
- 当这两个路径的内容相等时,声明提交相等。
请注意,这会将 X
的路径版本 p
与 X
的版本(当然是相同),并且还针对任一提交链中的每个版本,同时还将每个版本与其他版本进行比较。
制作完这个完整的矩阵(我们稍后可以优化),我们现在可以找到许多 "interesting" 提交:
- 最后一次提交
cy
在 X
-to-Y
链中 p
与 X
中的内容相同(这是该链中最新的提交,p
未更改)
- 最后一次提交
cz
在 X
-to-Z
链中 p
与 X
中的内容相同(另一个链中最新的未更改)
- 最早的
cy
其中 p
与提交中的内容相同 Y
(这是最后一次在 X
-to-Y
链中修改路径 p
)
- 最早的
cz
其中 p
与提交中的内容相同 Z
- 任一链中的任何提交与
p
的内容与另一链中的任何提交相同。
我想您可能正在考虑在这里找到第 1 项和第 2 项。不过,还不清楚 为什么 。如果您只关心存储在路径 p
下的内容,我们已经确定(上文)这两个提交在 [= 下存储相同的内容44=] 正如您在 X
中找到的那样。所以 X:<em>p</em>
是 "just as good" 识别这些内容,你不妨使用 commit X
.
如果你说的是寻找项目 3 和 4,那么又不是很清楚 为什么 ,因为我们已经确定它们与 [=181= 的内容相同]p
作为他们的最高提交,所以 Y:<em>p</em>
和 Z: <em>p</em>
对于识别这些内容同样有用。
但也许您正在处理第 5 项:在路径 p
下的内容相同的两条链上提交(因为另一个提交在其他链),但不一定与最尖端提交中的内容相同。
可以有很多这样的对。例如,假设在 X
(git merge-base
找到的绝对共同祖先)中,路径 p
有五行。然后,在朝着 Y
前进的过程中,该路径中的第一个提交删除了最后一行。同时在 X
-to-Z
序列中,几次提交保留所有 5 行,然后删除最后一行。现在这个版本的 p
在两条开发线中都是一样的,直到下一次提交修改 p
。假设在 X
-to-Z
序列中删除了另一行。然后在 X
-to-Y
序列中,同一行被删除;然后,两次提交都删除了更多行,直到最终文件在一个或两个分支提示处完全为空。
定义"nearest"还有另一个问题。我们再看一下比较复杂的X
-to-Y
的图片段,但是放几个比较有区别的字母:
R
/ \
P T--o--Y <-- br1
/ \ /
...--X S
假设路径 p
在提交 R
和 S
中具有相同的内容,但在 P
中不同和 T
。两者与 X
或 Y
的图形距离相同。只要你只关心路径p
,这可能是无关紧要的,但它确实表明不一定存在唯一的提交.
在我深入了解您想要使用的几个命令之前,为了解决您要解决的问题,我说了很多废话。
使您更接近解决方案的命令(甚至可能一直到那里,具体取决于您想要什么,尽管您似乎可能需要使用其他命令,有些甚至不需要 git 命令)是 git rev-list
。这可以找到修改了特定路径的提交(与那些提交的父提交相比;请注意,通常必须特别处理合并,因为它们有多个父提交)。如果您 do 使用一个或多个路径来限制 git rev-list
列出的修订,请注意它将执行 "history simplification" 以便从其输出中省略一些提交。根据您希望如何处理 DAG 级分支(例如更复杂的 X
-to-Y
链中的分支),这可能正是您想要的。
基本上,git rev-list X..Y -- path
将找到可从 Y
访问的提交,不包括可从 X
访问的修改 path
的提交,其中 "modify" 表示 "a diff against the parent shows a change to that path"。 (关于它如何处理合并,请参阅文档。)列出提交的顺序取决于您选择的排序(有或没有拓扑约束;请参阅 "Commit Ordering" 部分)。
如果用 X..Z
重复此操作,您可以找到哪些提交修改了那里的路径。
这两个 git rev-list
基本上是从 X
到两个分支提示的整个修订链,但是因为它们允许您将它们的输出限制为 "commits that modify some path(s)",所以它们可以优化我在思想实验中概述的过程。
您可能希望在此处包括提交 X
。默认情况下,rev-list
不会:您可以更早地开始一个提交(在 X
的父级),但是如果 X
本身是一个合并,这可能会失败;或者您可以使用 --boundary
,它指示 rev-list
包含提交 X
的 SHA-1(以 -
为前缀)。
查看某个路径下存储的内容在两次不同的commit中是否相同——显然这里两次使用相同的commit ID内容是相同的,但仍然有效——你可以比较存储的 blob 的 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
其中 None 将检测重命名;为此,您必须使用成熟的 git diff
(打开重命名检测)。
有没有命令可以在两个分支中找到一个文件的共同祖先?
假设有一个文件在两个分支中被独立修改。我想找到两个分支通用的该文件的最新版本。我相信这归结为在两个分支中找到文件的单父提交。
但是,merge-base 只允许为提交查找父提交,而不是文件。我试图指定最后两次提交修改各自分支中的文件,但我得到的父提交不在任何一个分支中该文件的更改历史记录中,这可能是由于提交通常包含对多个文件的更改一个文件。
Is there any command to find a common ancestor of a file in two branches?
不,或者是,或者也许:这取决于你的意思。
Say there is a file that was modified independently in two branches. I want to find the last version of that file common to both branches. I believe this boils down to finding the single parent commit for the file in both branches.
文件没有父提交。只有提交有父提交。
更糟糕的是,每次提交都会存储每个文件(即提交时属于暂存区的每个文件)。因此,从某种意义上说,这要么是每次提交,要么是常规的普通合并基础。显然这不是你的意思,所以让我们看看在这里还能说些什么。
让我们来做一个思想实验。假设你有两个分支提示 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
的工作方式,"regular" 合并(或使用 git merge-base
)将找到合并基础 X
,其中我认为大多数人会同意 X
中的某些文件传播(可能重命名)到 Y
,也传播到 Z
,在 [=18] 中有一个共同的祖先=].这个共同的祖先可能出现在 Y
或 Z
中的不同路径名下(甚至在 Y
和 Z
中)但是它仍然是共同的祖先,因此它被用作合并基础版本。
这里有一个问题:git 不记录重命名。相反,它 "discovers" 它们每次都会产生差异。为了发现 X
中的文件 generic/b.c
现在是 Y
中的 specific/b.c
,git 必须将 X
下的整个树与Y
下的整棵树。这意味着它必须找到提交 X
.
这对于常规合并来说并不难,因为它使用提交图:它从提交 Y
和 Z
开始,并向后遍历历史以找到最近的公共提交(这里当然是X
)。一旦我们知道(或 git 知道)使用 X
,它就会产生两个差异,X
-vs-Y
和 X
-vs-Z
,然后它可以将更改合并到公共文件的 contents,而不管它在 [=20] 中的 path 是什么=] 和 Z
.
(交叉合并还有一个次要问题,其中可能有多个最近的共同提交,但我们现在可以忽略它。)
如果我们(至少暂时)放弃寻找重命名的想法,我们可以,给定一些路径 p
,使用不同的方法,我think 你问的是:
- 对于
X
和Y
之间的每个提交cy
(包括X
并从Y
),以及X
和Z
之间的每个提交cz
(同样从Z
向后工作),比较<em>cy</em>/<em>p</em>
和<em>cz</em>/<em>p</em>
. - 当这两个路径的内容相等时,声明提交相等。
请注意,这会将 X
的路径版本 p
与 X
的版本(当然是相同),并且还针对任一提交链中的每个版本,同时还将每个版本与其他版本进行比较。
制作完这个完整的矩阵(我们稍后可以优化),我们现在可以找到许多 "interesting" 提交:
- 最后一次提交
cy
在X
-to-Y
链中p
与X
中的内容相同(这是该链中最新的提交,p
未更改) - 最后一次提交
cz
在X
-to-Z
链中p
与X
中的内容相同(另一个链中最新的未更改) - 最早的
cy
其中p
与提交中的内容相同Y
(这是最后一次在X
-to-Y
链中修改路径p
) - 最早的
cz
其中p
与提交中的内容相同Z
- 任一链中的任何提交与
p
的内容与另一链中的任何提交相同。
我想您可能正在考虑在这里找到第 1 项和第 2 项。不过,还不清楚 为什么 。如果您只关心存储在路径 p
下的内容,我们已经确定(上文)这两个提交在 [= 下存储相同的内容44=] 正如您在 X
中找到的那样。所以 X:<em>p</em>
是 "just as good" 识别这些内容,你不妨使用 commit X
.
如果你说的是寻找项目 3 和 4,那么又不是很清楚 为什么 ,因为我们已经确定它们与 [=181= 的内容相同]p
作为他们的最高提交,所以 Y:<em>p</em>
和 Z: <em>p</em>
对于识别这些内容同样有用。
但也许您正在处理第 5 项:在路径 p
下的内容相同的两条链上提交(因为另一个提交在其他链),但不一定与最尖端提交中的内容相同。
可以有很多这样的对。例如,假设在 X
(git merge-base
找到的绝对共同祖先)中,路径 p
有五行。然后,在朝着 Y
前进的过程中,该路径中的第一个提交删除了最后一行。同时在 X
-to-Z
序列中,几次提交保留所有 5 行,然后删除最后一行。现在这个版本的 p
在两条开发线中都是一样的,直到下一次提交修改 p
。假设在 X
-to-Z
序列中删除了另一行。然后在 X
-to-Y
序列中,同一行被删除;然后,两次提交都删除了更多行,直到最终文件在一个或两个分支提示处完全为空。
定义"nearest"还有另一个问题。我们再看一下比较复杂的X
-to-Y
的图片段,但是放几个比较有区别的字母:
R
/ \
P T--o--Y <-- br1
/ \ /
...--X S
假设路径 p
在提交 R
和 S
中具有相同的内容,但在 P
中不同和 T
。两者与 X
或 Y
的图形距离相同。只要你只关心路径p
,这可能是无关紧要的,但它确实表明不一定存在唯一的提交.
在我深入了解您想要使用的几个命令之前,为了解决您要解决的问题,我说了很多废话。
使您更接近解决方案的命令(甚至可能一直到那里,具体取决于您想要什么,尽管您似乎可能需要使用其他命令,有些甚至不需要 git 命令)是 git rev-list
。这可以找到修改了特定路径的提交(与那些提交的父提交相比;请注意,通常必须特别处理合并,因为它们有多个父提交)。如果您 do 使用一个或多个路径来限制 git rev-list
列出的修订,请注意它将执行 "history simplification" 以便从其输出中省略一些提交。根据您希望如何处理 DAG 级分支(例如更复杂的 X
-to-Y
链中的分支),这可能正是您想要的。
基本上,git rev-list X..Y -- path
将找到可从 Y
访问的提交,不包括可从 X
访问的修改 path
的提交,其中 "modify" 表示 "a diff against the parent shows a change to that path"。 (关于它如何处理合并,请参阅文档。)列出提交的顺序取决于您选择的排序(有或没有拓扑约束;请参阅 "Commit Ordering" 部分)。
如果用 X..Z
重复此操作,您可以找到哪些提交修改了那里的路径。
这两个 git rev-list
基本上是从 X
到两个分支提示的整个修订链,但是因为它们允许您将它们的输出限制为 "commits that modify some path(s)",所以它们可以优化我在思想实验中概述的过程。
您可能希望在此处包括提交 X
。默认情况下,rev-list
不会:您可以更早地开始一个提交(在 X
的父级),但是如果 X
本身是一个合并,这可能会失败;或者您可以使用 --boundary
,它指示 rev-list
包含提交 X
的 SHA-1(以 -
为前缀)。
查看某个路径下存储的内容在两次不同的commit中是否相同——显然这里两次使用相同的commit ID内容是相同的,但仍然有效——你可以比较存储的 blob 的 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
其中 None 将检测重命名;为此,您必须使用成熟的 git diff
(打开重命名检测)。