如何找到指向Git树对象的提交(commit)?

20

尝试将存储库镜像到远程服务器时,服务器拒绝了树对象4e8f805dd45088219b5662bd3d434eb4c5428ec0。这不是顶级树,而是一个子目录。

我该如何找出间接引用该树对象的提交,以便避免推送链接到这些提交的引用,从而使我的存储库的所有其他内容都能正确推送?


我考虑删除树对象,然后运行git fsck,希望它将在恢复的过程中删除对它的所有引用。但我也不知道如何从packfile中删除一个对象。 - Andrew Arnott
如何优化这个问题:使用“git bisect”查找引入错误树引用的提交,然后您可以使用“git ls-tree”命令查找该提交中的错误树。 - Raymond Chen
@RaymondChen 这可能行不通。除了花费很长时间(二分查找是很棒的,但对于这么大的树来说并不是很好用),它可能会失败,因为树本身可能无法在相关提交上进行检出。此外,我需要一个“好”的和一个“坏”的样本提交,以便开始二分查找,而我不知道哪个提交是不好的。 - Andrew Arnott
2个回答

24

正如你所指出的,你只需要找到具有所需 tree 的提交。如果它可能是顶层树,你需要一个额外的测试,但由于它不是,所以你不需要。

你想要:

  • 对于一些提交集(例如从给定分支名称可达的所有提交),
  • 如果该提交具有目标树哈希作为子树,则打印提交 ID

这可以通过两个 Git "plumbing" 命令加上 grep 轻松实现。

这是我原始脚本的稍微更新版本(更新为接受参数,并默认为 --all,就像 badp 的编辑一样):

#! /bin/sh
#
case $# in
0) echo "usage: git-searchfor <object-id> [<starting commit>...]" 1>&2; exit 1;;
esac

searchfor=$(git rev-parse --verify "$1") || exit 1
searchfor=$(git rev-parse --verify "$searchfor"^{tree}) || exit 1
shift
  
git log ${@-"--all"} --pretty='format:%H' |
    while read commithash; do
        if git ls-tree -d -r --full-tree $commithash | grep $searchfor; then
            echo " -- found at $commithash"
        fi
    done

要检查顶层树,您需要执行git cat-file -p $commithash命令,并查看其中是否包含该哈希值。
请注意,此代码也会找到blob(假设您从git ls-tree中删除了-d选项)。但是,树不能具有blob的ID,反之亦然。 grep将打印匹配的行,因此您将看到例如:
040000 tree a3a6276bba360af74985afa8d79cfb4dfc33e337    perl/Git/SVN/Memoize
 -- found at 3ab228137f980ff72dbdf5064a877d07bec76df9

为了使其适用于一般用途,您可能想在搜索 blob 或树时使用git cat-file -t以获取它的类型。
正如jthill在评论中指出,现在git diff-tree有一个--find-object选项。 这是在Git 2.17中引入的(在此问题最初发布之后的2018年)。 git log命令也有此选项,但我们通常更关心哪个特定提交添加了文件或树。 通过删除试图强制searchfor哈希ID成为树的额外行,我们可以得到一个更快的脚本,找到任何树或blob对象的每个出现(虽然您必须小心指定正确的哈希ID或自己使用^{tree}后缀如果您要提供提交哈希ID)。 然后我们只需要运行:
git log --all --find-object=$searchfor

或者,如下面的评论所述:
git rev-list --all | git diff-tree --stdin --find-object=$searchfor

寻找我们要找的内容。(如果需要,添加${2-"--all"}。)


谢谢!这应该可以工作,尽管我不知道哪个分支/标签有问题,所以我必须在我的几千个分支中循环运行整个过程(这是一个具有许多用户的大型存储库)。因此,我将不得不想办法先获取跨分支存储库中每个提交的列表并消除重复项。但这是一个很好的开始。 - Andrew Arnott
1
git rev-list 接受与 git log 相同的参数。实际上,它们基本上是相同的命令!它们都是从一个源文件构建而来,只是在作为 git loggit rev-list 运行时更改默认设置。Rev-list 旨在供脚本使用,而 log 则旨在供人类使用。无论如何,A..B 的意思是 B ^A,因此 origin/master..mastermaster ^origin/master 在这里完全相同。在这种情况下,您可以使用 git rev-list --branches ^origin/master(或者也许是 --branches --tags)。 - torek
1
所以,它成功了!我发现脚本微妙地只会找到当前子目录下的树(而不是从根目录开始)。我已经修复了这个问题,但是然后它花费了很长时间才完成,而且我对树所代表的目录有一个很好的想法,所以我利用了这一点作为优化。我现在有几个提交可以使用。 :) - Andrew Arnott
更快的方法是:使用 git rev-list --all | git diff-tree --stdin --find-object=4e8f805dd45088219b5662bd3d434eb4c5428ec0 命令查找引入该树的所有提交,然后使用 git branch --contains 命令避免推送任何包含该树的提交。 - jthill
@jthill 对的,--find-object(Git 2.17中新增)现在是最好的选择。我会稍微调整一下答案。 - torek
显示剩余3条评论

2

如果您想通过GNU Parallel加速操作,以下是由torek提供的优秀答案变体

#!/bin/bash    
searchfor="$1"
startpoints="${2-HEAD}"

git rev-list "$startpoints" |
    parallel "if git ls-tree -d -r --full-tree '{}' | grep '$searchfor'; then echo ' -- found at {}'; fi"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接