git rev-list
命令是Git中非常复杂、非常核心的命令,因为它所做的是遍历图形。这里的“图形”一词既指提交图形本身,也在某些情况下指从提交可达的Git对象的下一级。
我认为rev-list以相反的时间顺序显示提交。
不完全正确,但很接近:
顺序是可以更改的。默认值是相反的时间顺序。
默认值是遍历一些提交,但您可以让
rev-list
深入到包括树和blob对象甚至标签对象。这是针对像
git fetch
和
git 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
选项正是您从文档中引用的内容:
假装所有的
refs/
都列在命令行上…
所以这相当于执行
git for-each-ref refs
:它循环遍历每个引用。这包括分支名称(如
master
或
main
、
develop
、
feature/tall
等,所有这些都在
refs/heads/
中)、标签名称(如
v1.2
实际上是
refs/tags/v1.2
)、远程跟踪名称(如
origin/develop
实际上是
refs/remotes/origin/develop
)、替换引用(在
refs/replace/
中)、stash(
refs/stash
)、二分法引用、如果您正在使用Gerrit,则为Gerrit引用等。请注意,它
不会循环遍历reflog条目。
--not
前缀是一个简单的布尔运算。在gitrevisions语法中(请参阅the gitrevisions documentation),我们可以编写诸如develop
之类的东西,意思是我告诉你从develop
开始向后工作,并且包括这些提交,但也可以编写诸如^develop
之类的东西,意思是我告诉你从develop
开始向后工作,并且排除这些提交。因此,如果我写道:
git rev-list feature1 feature2 ^main
我要求Git遍历与名称为
feature1
和
feature2
的提交相关的提交,但要排除与名称为
main
的提交相关的提交。关于
可达性和图形遍历的一般想法,请参见
像Git一样思考。
--not
运算符有效地翻转了每个引用上的
^
。
git rev-list --not feature1 feature2 ^main
作为简写,可以这样说:
git rev-list ^feature1 ^feature2 main
这会遍历从
main
可达的提交列表,但排除那些可达于
feature1
或
feature2
的提交。
通常情况下,使用--all
可以找到所有提交
如果你通常以正常方式使用Git,并且此时没有"分离头指针",则git rev-list
选项中的--all
告诉它包含所有提交,因为所有提交都可以从所有引用中到达。1因此,对任何本来会列出一些提交的git rev-list
添加--not --all
实际上是排除所有提交。输出为空:我们为什么要费心呢?
如果你处于分离头状态并且已经进行了多个新提交 - 例如,在交互式或有冲突的rebase过程中,就可能会发生这种情况 - 那么git rev-list HEAD --not --all
将列出那些可从HEAD
到达但不可从任何分支名称到达的提交。例如,在那个rebase中,这只是你已经复制的那些提交。
因此,“分离头”模式将是命令行中使用git rev-list --not --all
的一个地方。但对于你正在检查的情况 - 一个pre-receive hook - 我们并不真正在命令行上。
Pre-receive hooks
当有人使用git push
将提交发送到你自己的Git时,你的Git:
- 设置隔离区来保存任何新对象(新提交和blob等);1
- 与发送者协商决定发送者应该发送什么;
- 接收这些对象;并且
- 获取引用更新请求列表。这些更新请求基本上只是说使此名称保持此哈希ID。2
在实际执行任何请求的更新之前,你的Git:
将整个列表提供给pre-receive hook。如果hook说“不行”,则整个推送将被拒绝。
如果hook返回“ok”,则逐个请求将列表提供给update hook。当hook返回“ok”时,执行更新。如果hook返回“no”,则您的Git会拒绝一个更新,但会继续检查其他更新。
在步骤2中接受或拒绝所有更新后,将接受的列表提供给post-receive hook。
需要的对象是添加到步骤2中某个ref中的,将从隔离区移动到Git的对象数据库中。被拒绝的对象则不会移动。
现在,考虑一下典型的git push。我们得到了一些新提交和一个请求:创建一个新的分支名称feature/short,或者我们得到了一些新提交和一个请求:更新现有的分支名称develop以包括这些新提交和旧提交。
在上述第1步中,我们有一个新的哈希ID。我们运行了一个循环来读取所有ref名称、它们的当前和拟议的新哈希ID,循环只运行了一次,因为只有一个名称正在进行git push。该哈希ID指的是新提交或提交,将被添加到此现有分支,或者是作为新分支的尖端和其他提交,这些提交是独占的。
现在,我们想要检查这些提交,而不是可从任何现有分支到达的任何现有提交。为简单起见,假设我们只有一个新的哈希ID $new和分支名称的旧哈希ID $old:如果分支是全新的,则为全零,否则为某个有效的现有提交。
如果新提交在完全新的分支上,则:
git rev-list $new ^master ^develop ^feature/short ^feature/tall
比如说,如果我们知道现有的分支只有这四个(并且没有需要担心的标签等),那么{{这些命令}}就可以涵盖它们。但是如果它们被添加到例如develop
中,那么我们希望排除当前在develop
上的提交。我们可以使用$old
哈希值来实现:
git rev-list $new ^master ^$old ^feature/short ^feature/tall
这将再次列出只有那些运行git push origin develop
的人想要添加到我们的develop
中的新提交。
但是请考虑$old
。这是一个哈希值。Git从哪里获取它?Git从名称develop
获取了此哈希ID。这是一个pre-receive hook;名称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
的来源:它依赖于pre-receive钩子的特殊情况。我们将由运行git push
的人提出的新哈希ID列表列出为一系列行。我们使用git rev-list
在隔离区中遍历建议更新的提交图,但不包括已在我们的存储库中的所有提交。rev-list命令生成这些哈希ID,每行一个,然后我们在shell循环中读取它们,并对每个提交进行检查。
1隔离区是Git 2.11中新增的功能。在此之前,即使推送被拒绝,新对象也可能会在存储库中保留一段时间。对于大型服务器(如GitHub)来说,隔离区并不是什么大问题,但它可以为他们节省很多磁盘空间。
2请求可以强制执行或不强制执行,如果强制执行,则可以是带租约的强制执行,也可以不带。这些信息在pre-receive钩子(也不在update钩子)中不可用,这个情况,呃,让我们只说不太好,但添加它存在兼容性问题。尽管如此,这一切都是可以应对的。钩子可以告诉我们这是否是一个创建新引用或删除现有引用的请求,因为如果是这样,旧哈希ID或新哈希ID中的一个将是所有零的“null哈希”(该哈希ID不允许为全零)。