了解 git 修订列表

Understanding git rev-list

在寻找 git 钩子示例时,我遇到了以下 post: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 值?

意思是:

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

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

git rev-list命令是非常复杂的,非常重要命令在Git中,正如它做的是走图。这里的 graph 这个词指的是提交图本身,在某些情况下,指的是下一层(Git objects reachable from提交)。

I figured that rev-list shows commits in reverse chronological order.

不完全是,但接近:

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

所以默认是按照倒序列出一些提交。准确指定 图表的哪些部分 我们将 git rev-list 遍历既重要又有点棘手: some 一些提交中 .

But, can someone share more insight on what --not and --all options are meant for?

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

更多关于 --not --all

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

Pretend as if all the refs in refs/ are listed on the command line ...

所以这相当于 git for-each-ref refs:它遍历每个引用。这包括 b运行ch 名称(mastermaindevelopfeature/tall 等等,所有这些实际上都在 refs/heads/ ),标签名称(v1.2,实际上是 refs/tags/v1.2),remote-tracking 名称(origin/develop,实际上是 refs/remotes/origin/develop),替换引用(在 refs/replace/)、存储 (refs/stash)、二分引用、Gerrit 引用(如果您使用的是 Gerrit)等等。请注意,它不会循环引用日志条目。

--not前缀是一个简单的布尔运算。在 gitrevisions 语法中——参见 the gitrevisions documentation——我们可以写类似 develop 的东西,意思是 I tell you to start from develop and work backwards and include 这些提交,还有像 ^develop 这样的事情,意思是 我告诉你从 develop 开始,然后倒退 排除这些提交。所以如果我写:

git rev-list feature1 feature2 ^main

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

--not 运算符有效地翻转每个 ref:

上的 ^
git rev-list --not feature1 feature2 ^main

是shorthand,可以说是:

git rev-list ^feature1 ^feature2 main

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

访问的提交列表

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

找到

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

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

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

Pre-receive 挂钩

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

  • 设置一个 qua运行tine 区域来保存任何新对象(新提交和 blob 等等);1
  • 与发件人协商决定发件人应该发送什么;
  • 收到这些对象;和
  • 获取 ref 更新请求 的列表。这些更新请求基本上只是说 让这个名字持有这个哈希 ID.2[=260=

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

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

在第 2 步中添加到某些 ref 中的所需对象已从 qua运行tine 移至 Git 的对象数据库。那些被拒绝的不是。

现在,想想一个典型的 git push。我们得到一些新的提交和一个请求:create a new b运行ch name feature/short,或者我们得到一些新的提交和一个请求:更新现有的 b运行ch 名称 develop 以包括这些新提交以及旧提交

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

我们现在想要检查这些提交,而不是从任何现有 b运行ch 可以访问的任何现有提交。为简单起见,而不是 $new_list 在我的另一个答案中,假设我们只是一个新的哈希 ID,$new,和 b运行ch 名称的旧哈希 ID,$old: all-zeros 如果 b运行ch 是 all-new,或者一些有效的现有提交,如果它是现有的 b运行ch 名称。

如果新提交在一个全新的 b运行ch 上,那么:

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

将覆盖它们,例如,如果我们知道唯一存在的 b运行 是这四个(并且没有标签等需要担心)。但是,如果将它们添加到 develop 中怎么办?然后我们想排除 develop 当前 的提交。我们可以使用 $old 哈希 ID 来做到这一点:

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

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

的新提交

但是想想$old。这是一个哈希 ID。 Git 从哪里弄来的? Git name develop 得到了 这个哈希 ID。这是一个pre-receive挂钩名称 develop 尚未更新。所以名称 develop 旧哈希 ID $old 的名称。这意味着:

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

也会完成这项工作。

如果 git rev-list $new 后跟“and not all existing”将完成工作,则:

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

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

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

所以这就是 --not --all 的来源:它取决于 pre-receive 钩子的特殊情况。我们列出了新的哈希 ID,正如 运行 和 git push 所提议的那样,我们的 Git 已作为行列表传递给我们。我们 git rev-list 遍历 proposed-to-be-updated 提交图,查看 qua运行tine 区域中的新提交,但不包括我们存储库中已有的所有提交。 rev-list 命令生成这些哈希 ID,每行一个,然后我们在 shell 循环中读取这些 ID,并在每次提交时做任何我们想做的事情 检查


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

2请求可以是强制的,也可以是not-forced,如果是强制的,可以是force-with-lease,也可以不是。此信息在 pre-receive 挂钩(也不在更新挂钩)中不可用,也就是说,嗯,我们只是说 不太好 ,但是添加存在兼容性问题它。不过,这主要是宜居的。钩子可以判断它是 create new ref 还是 delete existing ref 请求,因为如果是这样,两个哈希 ID 之一——旧的或新的——将是 all-zeros“空哈希”(保留;不允许哈希 ID 为 all-zeros)。