【问题标题】:Understanding git rev-list了解 git rev-list
【发布时间】:2021-01-31 10:55:26
【问题描述】:

在寻找 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>. 

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

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

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

【问题讨论】:

标签: git git-rev-list


【解决方案1】:

git rev-list 命令在 Git 中是一个非常复杂、非常重要的命令,因为它的作用是遍历图表。这里的词 graph 指的是提交图本身,在某些情况下,也指下一层(Git 对象 reachable from 提交)。

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

不完全是,但很接近:

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

所以 默认 是按时间倒序列出一些提交。准确指定图表的 哪些部分 我们将拥有git rev-list walk: some in some commits。

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

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

更多关于--not --all

--all 选项的作用与您从文档中引用的一样:

假装refs/ 中的所有引用都列在命令行中...

所以这相当于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 语法(参见 the gitrevisions documentation)中,我们可以编写类似 develop 的内容,这意味着 我告诉你从 develop 开始并向后工作并包含 这些提交 ,还有^develop 之类的东西,意思是我告诉你从develop 开始并向后工作排除这些提交。所以如果我写:

git rev-list feature1 feature2 ^main

我要求 Git 遍历由名称 feature1feature2 标识的提交可访问,但排除可从由标识的提交中访问的提交main。更多关于可达性和graph-walking的一般概念,请参阅Think Like (a) Git

--not 运算符有效地翻转每个 ref 上的 ^

git rev-list --not feature1 feature2 ^main

可以说是以下的简写:

git rev-list ^feature1 ^feature2 main

这会遍历可从main 访问的提交列表,但不包括可从feature1feature2 访问的提交。

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

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

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

所以“分离 HEAD”模式曾经是 git rev-list --not --all 在命令行中有用的地方。但是对于您正在检查的情况——一个 pre-receive 钩子——我们并没有真正在命令行上。

预接收挂钩

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

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

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

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

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

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

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

我们现在想检查这些提交,而不是任何现有分支可访问的任何现有提交。为简单起见,而不是我的另一个答案中的$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从哪里得到它?从 name develop Git 得到这个哈希 ID。这是一个预接收挂钩名称 develop 尚未更新。所以名称develop 旧哈希 ID $old 的名称。这意味着:

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

做这项工作。

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

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

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

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

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


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

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

【讨论】:

  • 非常感谢您的详细解释。现在对我来说确实有意义。希望文档尽可能详细 - 再次感谢您抽出宝贵时间分享这些精彩的深入信息。
【解决方案2】:

意思是:

  • 列出通过从给定提交(此处为$new_list)的父链接可访问的提交,新的、修改的或删除的提交
  • 但排除可从前面带有^ 的提交(此处为“all”,即所有 HEADS 提交或标记提交)中可访问的提交。

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

【讨论】:

  • 感谢@VonC 的回答。它现在可以帮助我了解幕后发生的事情。
  • @iDev 没问题。我看到你有 torek 的答案的长版本。
猜你喜欢
  • 2020-07-17
  • 1970-01-01
  • 1970-01-01
  • 2021-10-23
  • 2017-01-18
  • 1970-01-01
  • 2023-01-10
  • 2014-10-22
  • 2020-11-26
相关资源
最近更新 更多