首页 > 解决方案 > 了解 git rev-list

问题描述

在寻找 git hook 示例时,我遇到了以下帖子:https ://github.com/Movidone/git-hooks/blob/master/pre-receive ,我想了解以下命令:

git rev-list $new_list --not --all 

其中 new_list 来自:

NULL_SHA1="0000000000000000000000000000000000000000" # 40 0's
new_list=
any_deleted=false
while read oldsha newsha refname; do
    case $oldsha,$newsha in
        *,$NULL_SHA1) # it's a delete
            any_deleted=true;;
        $NULL_SHA1,*) # it's a create
            new_list="$new_list $newsha";;
        *,*) # it's an update
            new_list="$new_list $newsha";;
    esac
done

我认为 rev-list 按时间倒序显示提交。

但是,有人可以分享更多关于什么-not-all选项的见解吗?

根据文档:

--not
Reverses the meaning of the ^ prefix (or lack thereof) for all following revision specifiers, up to the next --not.
--all
Pretend as if all the refs in refs/ are listed on the command line as <commit>. 

我无法完全理解这些选项。

[更新] 在做了一些测试提交后,发现如果我不使用--notand--all选项,那么git rev-list列出分支上的所有提交,而不是我打算推送的那些。

--all但是,想了解为什么在传递选项时它不在终端上打印 sha 值?

标签: gitgit-rev-list

解决方案


git rev-list命令是 Git 中一个非常复杂、非常核心的命令,因为它的作用是遍历图形这里的图一词既指提交图本身,在某些情况下,也指下一层(可从提交中访问的 Git 对象)。

我认为 rev-list 按时间倒序显示提交。

不完全是,但很接近:

  • 顺序是可变的。默认为逆时间顺序。
  • 默认是遍历一些提交,但您可以rev-list更深入地包括树和 blob 对象,甚至标记对象。这适用于git fetchgit push(调用git pack-objects)和git pack-objects. 我打算在这里完全忽略这种可能性,但我觉得我至少应该提一下。

所以默认是按时间倒序列出一些提交。准确地指定我们将要遍历图形的哪些部分既重要又有点棘手: some in some commitsgit rev-list

但是,有人可以分享更多关于什么--not--all选项的见解吗?

正如VonC 所指出的,这里的效果是列出接收存储库的新提交。这取决于此git rev-list命令在预接收挂钩中运行的事实。它通常不会在这个特定的钩子之外做任何有用的事情。因此,如您所见,Git 中的钩子运行时环境通常至少有点特殊。(这不仅适用于预接收钩子:必须考虑每个钩子的激活上下文。)

更多关于--not --all

--all选项正是您从文档中引用的内容:

假装所有的 refsrefs/都列在命令行上......

所以这相当于 a git for-each-ref refs: 它遍历每个引用。这包括分支名称(mastermain,,,develop等等feature/tall,所有这些都在真正的refs/heads/),标签名称(v1.2真正的refs/tags/v1.2),远程跟踪名称(origin/develop真正的refs/remotes/origin/develop),替换参考(in refs/replace/),存储(refs/stash), bisection refs,Gerrit refs,如果你使用 Gerrit,等等。请注意,它不会遍历 reflog 条目。

--not前缀是一个简单的布尔运算。在 gitrevisions 语法中——请参阅gitrevisions 文档——我们可以编写类似的东西develop,这意味着我告诉你从头开始develop并向后工作并包含这些提交,但也有类似的东西^develop,这意味着我告诉你从头开始develop并向后工作并排除这些提交. 所以如果我写:

git rev-list feature1 feature2 ^main

我要求 Git 遍历名称和标识的提交中可访问的提交,但排除.标识的提交中可访问的提交。有关可达性和图遍历的一般概念的(更多)信息,请参阅Think Like (a) Gitfeature1feature2main

--not操作员有效地翻转每个^ref:

git rev-list --not feature1 feature2 ^main

可以说是简写:

git rev-list ^feature1 ^feature2 main

这将遍历可从 到达的提交列表main,但不包括可从feature1or到达的提交列表feature2

通常所有提交都可以通过--all

如果您以正常的日常方式使用 Git,并且目前没有“分离的 HEAD”——分离的 HEAD 模式并不完全不正常,但它不是通常的工作方式——告诉它包含所有提交的--all选项,因为所有提交可以从所有引用中访问。1 因此有效地排除了所有提交。因此,添加到任何会列出一些提交的内容都会产生禁止列表的效果。输出为空:我们为什么要打扰?git rev-list--not --all--not --allgit rev-list

如果您处于分离的 HEAD 模式并进行了几次新提交——例如,当你处于交互式或冲突的 rebase 中间时可能会发生这种情况——那么git rev-list HEAD --not --all将列出那些从任何分支名称访问HEAD不能从任何分支名称访问的提交。例如,在那个 rebase 中,这将只是您迄今为止复制的那些提交。

因此,“分离 HEAD”模式将曾经git rev-list --not --all是命令行有用的地方。但是对于您正在检查的情况(预接收挂钩),我们并没有真正在命令行上。

预收挂钩

当有人使用git push您自己的 Git发送提交时,您的 Git:

  • 设置隔离区来保存任何新对象(新提交和 blob 等);1
  • 与发送者协商决定发送者应该发送什么;
  • 接收这些对象;和
  • 获取ref 更新请求列表。这些更新请求本质上只是说让这个名字持有这个哈希 ID2

在实际执行任何请求的更新之前,您的 Git:

  1. 将整个列表馈送到 pre-receive 挂钩。那个钩子可以说“不”;如果是这样,则整个推送都被拒绝。
  2. 如果说“ok”,则将列表一次一个请求提供给更新挂钩。当那个钩子说“好的”时,更新。如果钩子说“不”,你的 Git 会拒绝一个更新,但会继续检查其他更新。
  3. 在步骤 2 中接受或拒绝所有更新后,将接受的列表提供给 post-receive 挂钩。

在第 2 步中添加到某个 ref 的所需对象将从隔离区移至 Git 的对象数据库。那些被拒绝的不是。

现在,考虑一个典型的git push. 我们得到一些新的提交和一个请求:创建一个新的分支名称feature/short,或者我们得到一些新的提交和一个请求:更新现有的分支名称develop以包含这些新的提交以及旧的提交

在上面的步骤 1 中,我们有一个新的哈希 ID。我们运行了一个循环来读取所有 ref 名称,以及它们当前和提议的新哈希 ID,并且该循环只运行了一次,因为只有一个名称git push-ed。该哈希 ID 指的是的提交或提交,它将被添加到这个现有的分支中,或者是新分支独有的提示和其他提交。

我们现在要检查这些提交,而不是任何现有分支可访问的任何现有提交。为简单起见,而不是$new_list在我的其他答案中,假设我们只有一个新的哈希 ID$new和分支名称的旧哈希 ID,$old如果分支是全新的,则全零,或者如果它是某个有效的现有提交现有的分支名称。

如果新的提交在一个全新的分支上,那么:

git rev-list $new ^master ^develop ^feature/short ^feature/tall

例如,如果我们知道唯一现有的分支是这四个(并且没有标签等需要担心),就会覆盖它们。但是,如果它们被添加到,比如说,develop呢?然后我们想排除当前正在进行的提交develop。我们可以使用$old哈希 ID 来做到这一点:

git rev-list $new ^master ^$old ^feature/short ^feature/tall

这将再次仅列出正在运行的git push origin develop人想要添加到我们的develop.

不过想想$old。这是一个哈希 ID。Git从哪里得到它?Git从name获得了这个哈希 ID 。这是一个预接收挂钩名称尚未更新。所以名称旧哈希 ID 的名称。这意味着: develop develop develop $old

git rev-list $new ^master ^develop ^feature/short ^feature/tall

将完成这项工作。

如果git rev-list $new后面跟着“并不是所有现有的”都可以完成这项工作,那么:

git rev-list $new --not --branches

将完成这项工作。这几乎就是我们这里所拥有的。

仅使用的错误--branches是它没有得到任何标签或其他参考。我们可以使用--not --branches --tagsbut--not --all更短,并且还可以获取所有其他参考。

所以这就是--not --all从哪里来的:它取决于预接收钩子的特殊情况。我们列出了新的哈希 ID,由运行 的人提出git push,我们的 Git 作为行列表传递给我们。我们已经git rev-list遍历了建议更新的提交图,查看了隔离区中的新提交,但排除了我们存储库中已经存在的所有提交。rev-list 命令生成这些哈希 ID,每行一个,然后我们在 shell 循环中读取这些 ID,并执行我们喜欢检查每个提交的任何操作。


1隔离区是 Git 2.11 中的新增功能。在此之前,即使推送被拒绝,新对象也可能会在存储库中保留一段时间。隔离区对大多数人来说并不是什么大不了的事,但对于像 GitHub 这样的大型服务器来说,它可以为他们节省大量的磁盘空间。

2请求可以是强制的,也可以是非强制的,如果是强制的,可以是强制的,也可以不是。这个信息在 pre-receive 钩子(也不是在更新钩子)中是不可用的,也就是说,嗯,我们只是说不太好,但是添加它存在兼容性问题。不过,大部分都是宜居的。钩子可以判断它是创建新的引用还是删除现有的引用请求,因为如果是这样,两个哈希 ID(旧的或新的)之一将是全零的“空哈希”(保留;不允许哈希 ID为全零)。


推荐阅读