如何通过添加文件索引(blob)找到负责的提交
How to find commit responsible by adding a file index (blob)
当我们制作 git diff Version1..Version2 -- file 时,此命令将 return 类似于:
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 5d034bb9d8..617021e8d9 100644
这里的git比较一个文件的两个版本,让您知道它们之间的区别。
我需要从索引号 5d034bb9d8 和索引 **617021e8d9*.
中知道负责添加相关文件的提交
TL;DR
这个(未经测试的)脚本可能会做你想做的事。阅读其余部分以了解其工作原理、是否有效以及何时有效以及注意事项。
#! /bin/sh
case $# in
2);;
*) echo "usage: script left-specifier right-specifier" 1>&2; exit 1;;
esac
# turn arguments into hashes, then ensure they are commits
L=$(git rev-parse "") || exit
R=$(git rev-parse "") || exit
L=$(git rev-parse $L^{commit}) || exit
R=$(git rev-parse $R^{commit}) || exit
haveblob=$(git rev-parse $L:wp-includes/version.php) || exit
wantblob=$(git rev-parse $R:wp-includes/version.php) || exit
git rev-list --reverse --topo-order $R ^$L^@ | while read hash; do
thisblob=$(git rev-parse $hash:wp-includes/version.php)
test $thisblob = $haveblob && continue
if [ $thisblob = $wantblob ]; then
echo "target file appears in commit $hash"
exit 0 # we've found it - succeed and quit
fi
echo "note: commit $hash contains a different version than either end"
done
echo "error: got to the bottom of the loop"
exit 1
长
让我们再澄清一点:你 运行:
$ git diff <commit1> <commit2> -- wp-includes/version.php
其输出部分为:
index 5d034bb9d8..617021e8d9 100644
让我们调用 <commit1>
——你通过散列或标签或分支名称或其他任何方式指定的——L,其中 L代表 git diff
的 左侧。我们将右侧的第二个提交称为 R。
您想找到在 L 或之后以及 R 之前或之后的提交,其中文件 wp-includes/version.php
匹配 R 中的版本,即缩写哈希为 617021e8d9
的版本。但是你不想要 any 提交:你想要 first 这样的提交——最接近 L[=205 的那个=].
首先,值得注意的是,这两个提交之间可能根本没有任何合理的关系。也就是说,如果我们要绘制提交历史图,它可能很简单:
...--o--o--L--M--N--...--Q--R--o--o--o <-- branch
但这可能不是那么简单。目前,我们假设它很简单。
简单情况:L是L
,R是R
,有一条直线在
之间提交
在这种情况下,从L到R有一些直接的因果关系。您的问题的答案将很有意义。具体来说,它回答了这个问题:这个版本是从哪里来的? 有一个直接的提交行从 L
开始并在 R
结束,并且版本在R
也可能在较早的提交中。让我们看看如何在 L
-to-R
序列中找到最早的提交,它具有 相同的 版本 R
.
首先,请注意每次提交都代表该快照中所有文件的完整快照。也就是说,如果我们查看上面的提交 N
,它会以某种形式包含所有文件。 N
中 wp-includes/version.php
的副本可能与 L
中的副本匹配,也可能与 R
中的副本匹配。 (它显然不能同时匹配两者:如果匹配,L
中的那个将匹配 R
中的那个并且将没有 index
行并且没有差异输出。)
文件可能在 L
和 R
中,但不在两者之间的 any 中,但在这种情况下,答案是:文件首先出现在R
.
也有可能文件在L
和R
中,在some中,但不在all[=205=中], 中间提交:说 L
有它,然后它在 M
中被删除,然后它以它在 R
中的形式再次出现在 N
中,然后它是在 O
中再次删除,依此类推。所以它出现在 L
、N
、P
和 R
中;它在 M
、O
和 Q
中缺失。现在问题更难了:你想在 N
中看到它,即使它在 O
中又消失了吗?还是您只想在 R
中看到它,因为它在 Q
中不见了?
无论如何,我们需要做的是枚举L
到R
范围内的所有提交。所以我们将从:
git rev-list L..R
(这将省略 L
,这有点烦人)。 Git 将以 reverse-ish 顺序枚举这些;因为我们知道链是线性的,所以这实际上是直接逆序。 (稍后我们将看到如何为更复杂的情况强制执行合理的顺序。)要检查 L
本身,我们可以直接添加它:
(git rev-list L..R; git rev-parse L)
或者我们可以使用相当复杂的技巧:
lhash=$(git rev-parse L); git rev-list R ^${lhash}^@
(详情见the gitrevisions documentation)。越简单的:
git rev-list L^..R
通常也能正常工作:只有当 L
是根提交时才会失败。
无论如何,git rev-list
的输出是一堆提交哈希ID:提交R
的哈希ID,然后是提交Q
的哈希ID,然后是提交的哈希ID P
,依此类推,一直回到L
。因此,我们将通过命令传输此 git rev-list
的输出,以确定我们的特定 blob 来自何处。但是我们想以其他顺序访问提交:首先是 L
,然后是 M
,然后是 N
,一直到 R
。所以我们将 --reverse
添加到 git rev-list
参数。
其余部分假定我们在 sh
或 bash
或类似格式中编写此脚本。在我们 运行 git rev-list
之前,让我们先获取文件每个版本的完整 blob-hash。然后我们会让他们进入循环:
#! /bin/sh
case $# in
2);;
*) echo "usage: script left-specifier right-specifier" 1>&2; exit 1;;
esac
# turn arguments into hashes, then ensure they are commits
L=$(git rev-parse "") || exit
R=$(git rev-parse "") || exit
L=$(git rev-parse $L^{commit}) || exit
R=$(git rev-parse $R^{commit}) || exit
# get the blob hashes, exit if they don't exist
haveblob=$(git rev-parse $L:wp-includes/version.php) || exit
wantblob=$(git rev-parse $R:wp-includes/version.php) || exit
git rev-list --reverse $R ^$L^@ | while read hash; do
...
done
在循环内,让我们获取此提交的 blob 哈希:
thisblob=$(git rev-parse $hash:wp-includes/version.php)
如果此失败,则表示文件已删除。我们可以选择忽略它并通过添加 || continue
或停止 || break
来跳过此提交,或者我们可以简单地完全忽略这种可能性,假设该文件将存在于每次提交中。由于最后一个最简单,所以我就在这里做。
如果这个散列匹配$haveblob
,它这不是很有趣。如果匹配到$wantblob
,就很有意思了。如果它完全是另一回事,那么,让我们把它说出来。所以循环的剩余部分是:
test $thisblob = $haveblob && continue
if [ $thisblob = $wantblob ]; then
echo "target file appears in commit $hash"
exit 0 # we've found it - succeed and quit
fi
echo "note: commit $hash contains a different version than either end"
这是顶部的脚本(好吧,大部分)。
更复杂的案例需要更多注意事项
图表内部可能更 branch-y; R 甚至可以是 合并提交:
M-----N
/ \
...--L R <-- branch
\ /
O--P--Q
或后一个:
M--N
/ \
...--L Q--R <-- branch
\ /
O--P
或者,图表可能是 L 和 R 截然不同:
...--o--o--o--L--o--o <-- branch1
\
o--...--o--R--o <-- branch2
或者(如果有多个根提交)它们甚至可以完全不相关,graph-wise:
A--B--L <-- br1
C--D--R <-- br2
或者,它们可能是相关的,不管是不是简单的线性关系,但是向后:
...--o--R--E--F--G--L--o--...--o <-- branch
如果这两个提交是像这样向后,你应该简单地交换它们。 (脚本可以这样做:git merge-base --is-ancestor A B
测试提交 A
是否是提交 B
的祖先。)
如果它们不直接相关,L..R
语法将排除可从 L
到达的提交,同时列出可从 R
到达的提交。如果它们完全不相关,则从 R
可访问的提交将无法从 L
访问,因此这只是 "all commits in the history up to R
"。在任何一种情况下,您都可能找到答案,也可能找不到答案,也可能没有任何意义。
您可以使用上面的 git merge-base
来测试这些情况:如果两者都不是对方的祖先,则它们可能通过共同的第三个祖先相关——实际合并两个提交的基础——或者它们可能完全不相关。
如果有分支"between" L
和R
,使得在R
或之前有合并,遍历可能发生在difficult-to-predict ] 命令。为了强制 Git 以 topologically-sorted 顺序枚举提交,我在实际脚本中使用了 --topo-order
。这迫使 Git 一次遍历合并的每个 "leg"。这在这里不一定很重要,但它可以更轻松地推理脚本的输出。
当我们制作 git diff Version1..Version2 -- file 时,此命令将 return 类似于:
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 5d034bb9d8..617021e8d9 100644
这里的git比较一个文件的两个版本,让您知道它们之间的区别。 我需要从索引号 5d034bb9d8 和索引 **617021e8d9*.
中知道负责添加相关文件的提交TL;DR
这个(未经测试的)脚本可能会做你想做的事。阅读其余部分以了解其工作原理、是否有效以及何时有效以及注意事项。
#! /bin/sh
case $# in
2);;
*) echo "usage: script left-specifier right-specifier" 1>&2; exit 1;;
esac
# turn arguments into hashes, then ensure they are commits
L=$(git rev-parse "") || exit
R=$(git rev-parse "") || exit
L=$(git rev-parse $L^{commit}) || exit
R=$(git rev-parse $R^{commit}) || exit
haveblob=$(git rev-parse $L:wp-includes/version.php) || exit
wantblob=$(git rev-parse $R:wp-includes/version.php) || exit
git rev-list --reverse --topo-order $R ^$L^@ | while read hash; do
thisblob=$(git rev-parse $hash:wp-includes/version.php)
test $thisblob = $haveblob && continue
if [ $thisblob = $wantblob ]; then
echo "target file appears in commit $hash"
exit 0 # we've found it - succeed and quit
fi
echo "note: commit $hash contains a different version than either end"
done
echo "error: got to the bottom of the loop"
exit 1
长
让我们再澄清一点:你 运行:
$ git diff <commit1> <commit2> -- wp-includes/version.php
其输出部分为:
index 5d034bb9d8..617021e8d9 100644
让我们调用 <commit1>
——你通过散列或标签或分支名称或其他任何方式指定的——L,其中 L代表 git diff
的 左侧。我们将右侧的第二个提交称为 R。
您想找到在 L 或之后以及 R 之前或之后的提交,其中文件 wp-includes/version.php
匹配 R 中的版本,即缩写哈希为 617021e8d9
的版本。但是你不想要 any 提交:你想要 first 这样的提交——最接近 L[=205 的那个=].
首先,值得注意的是,这两个提交之间可能根本没有任何合理的关系。也就是说,如果我们要绘制提交历史图,它可能很简单:
...--o--o--L--M--N--...--Q--R--o--o--o <-- branch
但这可能不是那么简单。目前,我们假设它很简单。
简单情况:L是L
,R是R
,有一条直线在
之间提交
在这种情况下,从L到R有一些直接的因果关系。您的问题的答案将很有意义。具体来说,它回答了这个问题:这个版本是从哪里来的? 有一个直接的提交行从 L
开始并在 R
结束,并且版本在R
也可能在较早的提交中。让我们看看如何在 L
-to-R
序列中找到最早的提交,它具有 相同的 版本 R
.
首先,请注意每次提交都代表该快照中所有文件的完整快照。也就是说,如果我们查看上面的提交 N
,它会以某种形式包含所有文件。 N
中 wp-includes/version.php
的副本可能与 L
中的副本匹配,也可能与 R
中的副本匹配。 (它显然不能同时匹配两者:如果匹配,L
中的那个将匹配 R
中的那个并且将没有 index
行并且没有差异输出。)
文件可能在 L
和 R
中,但不在两者之间的 any 中,但在这种情况下,答案是:文件首先出现在R
.
也有可能文件在L
和R
中,在some中,但不在all[=205=中], 中间提交:说 L
有它,然后它在 M
中被删除,然后它以它在 R
中的形式再次出现在 N
中,然后它是在 O
中再次删除,依此类推。所以它出现在 L
、N
、P
和 R
中;它在 M
、O
和 Q
中缺失。现在问题更难了:你想在 N
中看到它,即使它在 O
中又消失了吗?还是您只想在 R
中看到它,因为它在 Q
中不见了?
无论如何,我们需要做的是枚举L
到R
范围内的所有提交。所以我们将从:
git rev-list L..R
(这将省略 L
,这有点烦人)。 Git 将以 reverse-ish 顺序枚举这些;因为我们知道链是线性的,所以这实际上是直接逆序。 (稍后我们将看到如何为更复杂的情况强制执行合理的顺序。)要检查 L
本身,我们可以直接添加它:
(git rev-list L..R; git rev-parse L)
或者我们可以使用相当复杂的技巧:
lhash=$(git rev-parse L); git rev-list R ^${lhash}^@
(详情见the gitrevisions documentation)。越简单的:
git rev-list L^..R
通常也能正常工作:只有当 L
是根提交时才会失败。
无论如何,git rev-list
的输出是一堆提交哈希ID:提交R
的哈希ID,然后是提交Q
的哈希ID,然后是提交的哈希ID P
,依此类推,一直回到L
。因此,我们将通过命令传输此 git rev-list
的输出,以确定我们的特定 blob 来自何处。但是我们想以其他顺序访问提交:首先是 L
,然后是 M
,然后是 N
,一直到 R
。所以我们将 --reverse
添加到 git rev-list
参数。
其余部分假定我们在 sh
或 bash
或类似格式中编写此脚本。在我们 运行 git rev-list
之前,让我们先获取文件每个版本的完整 blob-hash。然后我们会让他们进入循环:
#! /bin/sh
case $# in
2);;
*) echo "usage: script left-specifier right-specifier" 1>&2; exit 1;;
esac
# turn arguments into hashes, then ensure they are commits
L=$(git rev-parse "") || exit
R=$(git rev-parse "") || exit
L=$(git rev-parse $L^{commit}) || exit
R=$(git rev-parse $R^{commit}) || exit
# get the blob hashes, exit if they don't exist
haveblob=$(git rev-parse $L:wp-includes/version.php) || exit
wantblob=$(git rev-parse $R:wp-includes/version.php) || exit
git rev-list --reverse $R ^$L^@ | while read hash; do
...
done
在循环内,让我们获取此提交的 blob 哈希:
thisblob=$(git rev-parse $hash:wp-includes/version.php)
如果此失败,则表示文件已删除。我们可以选择忽略它并通过添加 || continue
或停止 || break
来跳过此提交,或者我们可以简单地完全忽略这种可能性,假设该文件将存在于每次提交中。由于最后一个最简单,所以我就在这里做。
如果这个散列匹配$haveblob
,它这不是很有趣。如果匹配到$wantblob
,就很有意思了。如果它完全是另一回事,那么,让我们把它说出来。所以循环的剩余部分是:
test $thisblob = $haveblob && continue
if [ $thisblob = $wantblob ]; then
echo "target file appears in commit $hash"
exit 0 # we've found it - succeed and quit
fi
echo "note: commit $hash contains a different version than either end"
这是顶部的脚本(好吧,大部分)。
更复杂的案例需要更多注意事项
图表内部可能更 branch-y; R 甚至可以是 合并提交:
M-----N
/ \
...--L R <-- branch
\ /
O--P--Q
或后一个:
M--N
/ \
...--L Q--R <-- branch
\ /
O--P
或者,图表可能是 L 和 R 截然不同:
...--o--o--o--L--o--o <-- branch1
\
o--...--o--R--o <-- branch2
或者(如果有多个根提交)它们甚至可以完全不相关,graph-wise:
A--B--L <-- br1
C--D--R <-- br2
或者,它们可能是相关的,不管是不是简单的线性关系,但是向后:
...--o--R--E--F--G--L--o--...--o <-- branch
如果这两个提交是像这样向后,你应该简单地交换它们。 (脚本可以这样做:git merge-base --is-ancestor A B
测试提交 A
是否是提交 B
的祖先。)
如果它们不直接相关,L..R
语法将排除可从 L
到达的提交,同时列出可从 R
到达的提交。如果它们完全不相关,则从 R
可访问的提交将无法从 L
访问,因此这只是 "all commits in the history up to R
"。在任何一种情况下,您都可能找到答案,也可能找不到答案,也可能没有任何意义。
您可以使用上面的 git merge-base
来测试这些情况:如果两者都不是对方的祖先,则它们可能通过共同的第三个祖先相关——实际合并两个提交的基础——或者它们可能完全不相关。
如果有分支"between" L
和R
,使得在R
或之前有合并,遍历可能发生在difficult-to-predict ] 命令。为了强制 Git 以 topologically-sorted 顺序枚举提交,我在实际脚本中使用了 --topo-order
。这迫使 Git 一次遍历合并的每个 "leg"。这在这里不一定很重要,但它可以更轻松地推理脚本的输出。