当我们制作一个git diff Version1..Version2 - file时,这个命令将返回如下内容:
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 5d034bb9d8..617021e8d9 100644
这里的git比较了两个版本的文件,以便您了解它们之间的区别。我需要知道负责从索引号5d034bb9d8和索引** 617021e8d9 *添加有问题的文件的提交。
这个(未经测试的)脚本可以做你想要的。阅读其余内容,了解它的工作原理,是否有效,以及注意事项。
#! /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 "$1") || exit
R=$(git rev-parse "$2") || 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之前或之后,或之前或之后找到一些提交,其中文件wp-includes/version.php
与R中的版本匹配,即缩写哈希值为617021e8d9
的提交。但是你不想要任何提交:你想要第一个这样的提交 - 最接近L的提交。
值得注意的是,首先,两个提交之间可能没有任何明智的关系。也就是说,如果我们要绘制提交历史的图表,那可能很简单:
...--o--o--L--M--N--...--Q--R--o--o--o <-- branch
但它可能不那么简单。目前,让我们假设它很简单。
L
and R is R
and there's a straight line of commits in between在这种情况下,从L到R有一些直接的因果关系。你的问题的答案会很有意义。具体来说,它回答了这个问题:这个版本来自哪里?有一个直接的提交行从L
开始,到R
结束,R
中的版本也可能在之前的提交中。让我们看看如何在L
-to-R
序列中找到最早的提交,它与R
中的版本相同。
首先,请注意每个提交代表该快照中所有文件的完整快照。也就是说,如果我们查看上面提交的N
,它会以某种形式存在所有文件。 wp-includes/version.php
中的N
副本可能与L
中的R
相匹配,或者可能与L
中的R
相匹配。 (它显然无法与两者相匹配:如果确实如此,index
中的那个将匹配L
中的那个并且将没有R
线并且没有差异输出。)
该文件可能在R
和L
中,但不在其中的任何提交中,但在这种情况下,答案是:文件首先出现在R
中。
也有可能该文件位于L
和M
以及某些(但不是全部)中间提交中:比如说N
有它,然后它在R
中被移除,然后它再次出现在O
中,形式为L
,然后它在N
再次被删除,依此类推。所以它存在于P
,R
,M
和O
;它在Q
,N
和O
中缺失。现在问题更加困难:你想在R
看到它,即使它在Q
再次消失了吗?或者你想只在L
看到它,因为它在R
中丢失了?
无论如何,我们需要做的是枚举git rev-list L..R
到L
范围内的所有提交。所以我们先从:
L
(这将省略(git rev-list L..R; git rev-parse L)
,这有点烦人)。 Git将以反向顺序列举这些;因为我们知道链是线性的,所以这实际上是直接相反的顺序。 (我们将在后面看到如何对更复杂的案例强制执行合理的命令。)为了检查lhash=$(git rev-parse L); git rev-list R ^${lhash}^@
本身,我们可以明确地添加它:
the gitrevisions documentation
或者我们可以使用相当复杂的技巧:
git rev-list L^..R
(有关详细信息,请参阅L
)。更简单:
git rev-list
通常也可以工作:只有当R
是root提交时它才会失败。
在任何情况下,Q
的输出都是一堆提交哈希ID:提交P
的哈希ID,然后是提交L
的哈希ID,然后是提交git rev-list
的哈希ID,依此类推,一直回到L
。因此,我们将通过命令管理此M
的输出,以确定我们特定blob的来源。但是我们希望以另一种顺序访问提交:首先是N
,然后是R
,然后是--reverse
,一直到git rev-list
。所以我们将sh
添加到bash
参数中。
其余部分假设我们在git rev-list
或#! /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 "$1") || exit
R=$(git rev-parse "$2") || 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
或类似的地方编写此脚本。在我们运行 thisblob=$(git rev-parse $hash:wp-includes/version.php)
之前,让我们得到每个版本文件的完整blob-hash。然后我们将它们放在循环中:
|| continue
在循环中,让我们获取此提交的blob哈希:
|| 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"
,那不是很有趣。如果它与 M-----N
/ \
...--L R <-- branch
\ /
O--P--Q
匹配,那就非常有趣了。如果它完全不同,那就让我们说出来。所以循环的其余部分是:
M--N
/ \
...--L Q--R <-- branch
\ /
O--P
这是顶部的脚本(主要是)。
图表可能在内部相当分支; R甚至可以是合并提交:
...--o--o--o--L--o--o <-- branch1
\
o--...--o--R--o <-- branch2
或者来一个:
A--B--L <-- br1
C--D--R <-- br2
或者,图表可能是L和R完全不同:
...--o--R--E--F--G--L--o--...--o <-- branch
或者(如果有多个root提交)它们甚至可以完全不相关,以图形方式:
git merge-base --is-ancestor A B
或者,它们可能是相关的,无论它是否是一个简单的线性关系,而是向后:
A
如果这两个提交像这样倒退,你应该简单地交换它们。 (脚本可以这样做:B
测试提交L..R
是否是提交L
的祖先。)
如果它们没有直接相关,那么R
语法将排除从R
可以访问的提交,同时列出可以从L
访问的提交。如果它们完全不相关,那么从R
到达的提交无法从git merge-base
到达,所以这只是“历史上所有提交到L
”。在任何一种情况下,您可能会或可能不会找到答案,它可能会或可能没有任何意义。
您可以使用上面的R
测试这些情况:如果它们都不是另一个的祖先,它们可能通过共同的第三个祖先 - 两个提交的实际合并基础 - 或它们可能完全不相关来关联。
如果在R
和--topo-order
之间存在分支,以便在qazxswpoi或之前存在合并,则遍历可能以某种难以预测的顺序发生。为了强制Git以拓扑排序的顺序枚举提交,我在实际脚本中使用qazxswpoi。这迫使Git一次遍历合并的每个“腿”。这不一定是关键,但它使得脚本输出的推理变得更容易。