该git rev-list
命令是 Git 中一个非常复杂、非常核心的命令,因为它的作用是遍历图形。这里的图一词既指提交图本身,在某些情况下,也指下一层(可从提交中访问的 Git 对象)。
我认为 rev-list 按时间倒序显示提交。
不完全是,但很接近:
- 顺序是可变的。默认为逆时间顺序。
- 默认是遍历一些提交,但您可以
rev-list
更深入地包括树和 blob 对象,甚至标记对象。这适用于git fetch
和git push
(调用git pack-objects
)和git pack-objects
. 我打算在这里完全忽略这种可能性,但我觉得我至少应该提一下。
所以默认是按时间倒序列出一些提交。准确地指定我们将要遍历图形的哪些部分既重要又有点棘手: some in some commits。git rev-list
但是,有人可以分享更多关于什么--not
和--all
选项的见解吗?
正如VonC 所指出的,这里的效果是列出接收存储库的新提交。这取决于此git rev-list
命令在预接收挂钩中运行的事实。它通常不会在这个特定的钩子之外做任何有用的事情。因此,如您所见,Git 中的钩子运行时环境通常至少有点特殊。(这不仅适用于预接收钩子:必须考虑每个钩子的激活上下文。)
更多关于--not --all
该--all
选项正是您从文档中引用的内容:
假装所有的 refsrefs/
都列在命令行上......
所以这相当于 a git for-each-ref refs
: 它遍历每个引用。这包括分支名称(master
或main
,,,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) Git。feature1
feature2
main
--not
操作员有效地翻转每个^
ref:
git rev-list --not feature1 feature2 ^main
可以说是简写:
git rev-list ^feature1 ^feature2 main
这将遍历可从 到达的提交列表main
,但不包括可从feature1
or到达的提交列表feature2
。
通常所有提交都可以通过--all
如果您以正常的日常方式使用 Git,并且目前没有“分离的 HEAD”——分离的 HEAD 模式并不完全不正常,但它不是通常的工作方式——告诉它包含所有提交的--all
选项,因为所有提交都可以从所有引用中访问。1 因此有效地排除了所有提交。因此,添加到任何会列出一些提交的内容都会产生禁止列表的效果。输出为空:我们为什么要打扰?git rev-list
--not --all
--not --all
git 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 更新请求列表。这些更新请求本质上只是说让这个名字持有这个哈希 ID。2
在实际执行任何请求的更新之前,您的 Git:
- 将整个列表馈送到 pre-receive 挂钩。那个钩子可以说“不”;如果是这样,则整个推送都被拒绝。
- 如果说“ok”,则将列表一次一个请求提供给更新挂钩。当那个钩子说“好的”时,更新。如果钩子说“不”,你的 Git 会拒绝一个更新,但会继续检查其他更新。
- 在步骤 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 --tags
but--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为全零)。