理解 git rev-list

iDe*_*Dev 5 git git-rev-list

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

git rev-list $new_list --not --all 
Run Code Online (Sandbox Code Playgroud)

从哪里获得 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
Run Code Online (Sandbox Code Playgroud)

我认为 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>. 
Run Code Online (Sandbox Code Playgroud)

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

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

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

Von*_*onC 5

它的意思是:

  • 列出可通过遵循给定提交的父链接访问的提交,此处$new_list为新的、修改的或删除的提交
  • 但排除从^前面带有 a 的提交中可以访问的提交,这里是“全部”,即所有 HEADS 提交或标记提交。

这将 rev-list 限制为仅接收到的新提交,而不是所有提交(已接收并已存在于接收存储库中)


tor*_*rek 5

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

我认为 rev-list 以相反的时间顺序显示提交。

不完全是,但接近:

  • 顺序是可变的。在默认情况下是反向时间。
  • 默认是走一些提交,但你可以rev-list更深入地包括树和 blob 对象,甚至标记对象。这适用于git fetchgit push(调用git pack-objects)和 之类的程序git pack-objects。我打算在这里完全忽略这种可能性,但我觉得我至少应该提到它。

所以默认是按时间倒序列出一些提交。它既是重要的,有点棘手,指定究竟哪些零件图的,我们将有git rev-list散步:对一些某些提交

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

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

更多关于 --not --all

--all选项仅执行您从文档中引用的内容:

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

所以这相当于 a git for-each-ref refs: 它遍历每个引用。这包括分支名称(mastermaindevelopfeature/tall,等等,所有这一切真的是refs/heads/),标签名称(v1.2这是真的refs/tags/v1.2),远程跟踪名称(origin/develop这是真的refs/remotes/origin/develop),更换裁判(中refs/replace/),藏匿(refs/stash)如果您使用的是 Gerrit,则为二分引用、Gerrit 引用,等等。请注意,它不会遍历 reflog 条目。

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

git rev-list feature1 feature2 ^main
Run Code Online (Sandbox Code Playgroud)

我要求 Git名称feature1和标识的提交中遍历可到达的提交feature2,但从由 标识的提交中排除可到达的提交main。有关可达性和图形行走的一般概念的(更多)更多信息,请参阅Think Like (a) Git

--not运营商有效地翻转^每个参考:

git rev-list --not feature1 feature2 ^main
Run Code Online (Sandbox Code Playgroud)

可以说是简写,因为:

git rev-list ^feature1 ^feature2 main
Run Code Online (Sandbox Code Playgroud)

这将遍历可从 访问的提交列表main,但不包括可从feature1或访问的提交列表feature2

通常所有提交都可以找到--all

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

如果你是在分离的头模式,并取得了一些新的提交,这个时候你是在交互式或冲突底垫的中部,例如,则可能发生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 hook。

在第 2 步中添加到某个引用的所需对象从隔离区移动到 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
Run Code Online (Sandbox Code Playgroud)

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

git rev-list $new ^master ^$old ^feature/short ^feature/tall
Run Code Online (Sandbox Code Playgroud)

这将再次仅列出正在运行的人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
Run Code Online (Sandbox Code Playgroud)

做的工作。

如果git rev-list $new后跟“而不是所有现有的”将完成这项工作,则:

git rev-list $new --not --branches
Run Code Online (Sandbox Code Playgroud)

会做的工作。这几乎就是我们这里的情况。

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

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


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

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