如何通过添加文件索引(blob)来查找提交者

时间:2018-03-04 17:08:01

标签: git indexing blob diff

当我们制作 git diff Version1..Version2 - file 时,此命令将返回如下内容:

diff --git a/wp-includes/version.php b/wp-includes/version.php index 5d034bb9d8..617021e8d9 100644

这里的git比较两个版本的文件,以便您了解它们之间的区别。 我需要知道负责从索引 5d034bb9d8 和索引** 617021e8d9 *中添加有问题的文件的提交。

1 个答案:

答案 0 :(得分:1)

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 "$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 之前或之后, R 之前或之后找到一些提交,其中文件wp-includes/version.php中的版本匹配R ,即缩写哈希为617021e8d9的那个。但是你不想要任何提交:你希望首先这样的提交 - 最接近 L 的提交。

值得注意的是,首先,两次提交之间可能没有明显的关系。也就是说,如果我们要绘制提交历史的图表,那么可能很简单:

...--o--o--L--M--N--...--Q--R--o--o--o   <-- branch

但它可能不那么简单。目前,让我们假设它很简单。

简单的情况: L L,而 R R,其间有直线提交

在这种情况下,从 L R 有一些直接的因果关系。你的问题的答案将非常有意义。具体来说,它回答了以下问题:这个版本来自哪里?L开始到R结束的直接提交行和#&#的版本39; R中的s也可能在之前的提交中。让我们看看如何在L - 到 - R序列中找到最早的提交,其中包含<{>相同的版本{{1>} 1}}。

首先,请注意每个提交代表该快照中所有文件的完整快照。也就是说,如果我们查看上面的提交R,它会以某种形式存在所有文件。 Nwp-includes/version.php的副本可能与N中的L匹配,也可能与R中的L匹配。 (它显然无法与两者匹配:如果确实如此,R中的那个将与index中的那个匹配,并且没有L行且没有差异输出。)

该文件可能位于RR但不在任何之间的提交中,但在这种情况下,答案是是:该文件首先出现在L

该文件也可能在RL以及某些中,但不是所有,中间提交:说M有它,然后在N中删除它,然后它再次出现在R中的O形式,然后它& #39;在L中再次删除,依此类推。它出现在NPRM中;它在OQN中丢失了。现在问题更加困难:你想在O中看到它,即使它在R中再次消失了吗?或者您是否只想在Q中看到它,因为它在L中丢失了?

无论如何,我们需要做的是枚举Rgit rev-list L..R 范围内的所有提交。所以我们从:

开始
L

(将省略L,这有点烦人)。 Git将以反向顺序列举这些;因为我们知道链是线性的,所以这实际上是直接相反的顺序。 (我们将在稍后看到如何针对更复杂的案例强制执行合理的订单。)要检查(git rev-list L..R; git rev-parse L) 本身,我们也可以明确地添加它:

lhash=$(git rev-parse L); git rev-list R ^${lhash}^@

或者我们可以使用相当复杂的技巧:

git rev-list L^..R

(详见the gitrevisions documentation)。更简单:

L

通常也可以正常工作:只有当git rev-list是根提交时才会失败。

在任何情况下,R的输出都是一堆提交哈希ID:提交Q的哈希ID,然后是提交P的哈希ID,然后是提交L的哈希ID。 1}}等等,一直回到git rev-list。因此,我们通过命令管道此L的输出,以确定我们的特定blob来自何处。但我们希望以其他顺序访问提交:M首先是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到达的提交,因此这只是&#34;历史记录中所有提交到L&#34; 。在任何一种情况下,您可能会或可能不会找到答案,而且可能有或没有任何意义。

您可以使用上面的R来测试这些情况:如果它们都不是另一个的祖先,它们可能通过共同的第三祖先 - 两者的实际合并基础相关联承诺 - 或者他们可能完全不相关。

如果有分支&#34;介于&#34; R--topo-order以便在{{1}}之前或之前进行合并,遍历可能会以某种难以预测的顺序发生。为了强制Git以拓扑排序的顺序枚举提交,我在实际脚本中使用{{1}}。这迫使Git遍历每条腿&#34;一次合并一个。这不一定是关键,但它使得脚本输出的推理更容易。