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

问题描述 投票:0回答:1

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

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

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

git indexing blob diff
1个回答
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

Long

让我们再澄清一点:你跑了:

$ 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

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

The simple case: L is 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线并且没有差异输出。)

该文件可能在RL中,但不在其中的任何提交中,但在这种情况下,答案是:文件首先出现在R中。

也有可能该文件位于LM以及某些(但不是全部)中间提交中:比如说N有它,然后它在R中被移除,然后它再次出现在O中,形式为L,然后它在N再次被删除,依此类推。所以它存在于PRMO;它在QNO中缺失。现在问题更加困难:你想在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

这是顶部的脚本(主要是)。

More complex cases introduce more caveats

图表可能在内部相当分支; 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一次遍历合并的每个“腿”。这不一定是关键,但它使得脚本输出的推理变得更容易。

© www.soinside.com 2019 - 2024. All rights reserved.