简短的回答是你不能可靠地做你想做的事:Git 本身不记录git push 动作。但是有些事情你可以做。具体来说,在 Git 执行 git push 或 Git 接收 git push,在推送本身时,您可以获取此信息。如何保存、处理和以后使用,由您自己决定。
(我也认为这不是一个好主意:不要尝试通过 push 对事物进行分组,而是以其他方式对它们进行分组。例如,在 CI 系统中,将它们分组通过 request,请求动态更新。如果构建请求 #30 将 A、B 和 C 提交为“自请求创建以来的新”,则由于先前的推送,五秒钟后,但现在有 A 、B 和 D 而是对 ABD 进行 CI 检查,而不是对 ABC 进行 CI 检查,然后对 remove-C-add-D 进行检查。通读此答案的其余部分以了解此处发生的情况。)
预推钩
发送提交的 Git 将运行 pre-push 钩子(如果存在)。发送 Git 上的 pre-push 钩子每个获取四个信息项,嗯,我们暂时称它为“per thingy”:
- 本地参考
- 本地 OID/SHA-1/哈希
- 远程参考
- 远程 OID/SHA-1/哈希
假设你做到了:
git push origin refs/heads/master:refs/tags/v1.1
这里的本地引用是refs/heads/master。哈希 ID — 今天是 SHA-1 哈希,但 Git 内部现在称为“OID”(意思是对象 ID),以便在 Git 切换到 SHA-256 时进行验证,但您可以将其称为“哈希”以避免TLA 综合症1 — 是您的 refs/heads/master 标识的任何提交哈希 ID。远程引用将是refs/tags/v1.1,远程哈希可能是全零,因为这可能是您想要创建的一个新的轻量级标签。
如果你跑了:
git push origin master develop
你的钩子会得到两个东西。一个会提到refs/heads/master 两次,另一个会提到refs/heads/develop 两次:本地和远程master 分支,以及本地和远程develop 分支,你正在推动一个大的git push 操作。哈希 ID 将是您本地的 master 和他们的 master,以及您本地的 develop 和他们的 develop。
使用这些哈希 ID,您可以查看哪些提交对他们来说是新的。如果他们的哈希 ID 在您的 Git 存储库中,您还可以查看您是否要求他们删除任何提交,或者更准确地说,让他们无法访问。有关可达性的更多信息,请参阅Think Like (a) Git。
其中一些哈希 ID 可能全为零。这样的哈希 ID 意味着“没有这样的名称”。对于git push,如果您要求他们的Git 删除 引用,remote 哈希将全为零。如果您没有引用,则 local 哈希将全为零(这仅在您也要求它们删除时才有意义)。
1TLA 代表三个字母的首字母缩写词。与 ETLA 相比,ETLA 是一个超过三个字母的扩展 TLA。
pre-receive、update 和 post-receive 钩子
接收提交并被要求更新其引用的 Git 将运行 pre-receive 钩子和 post-receive 钩子(如果存在)。这些将获得与更新请求一样多的“东西”。它还将运行更新钩子(如果存在),每件事一次。
pre-receive 钩子为每个事物获取三个信息项:
- 当前(旧)OID/哈希
- 提议的新 OID/哈希
- 参考文献
current 哈希告诉您名称当前代表什么。例如,在我们的标签创建示例中,当前哈希将全为零。建议的新哈希是推送 Git 要求您(接收 Git)用作更新引用的新哈希 ID 的对象 ID。引用当然是要更新的引用。
在我们的两个要更新的分支示例中,refs/heads/master 的两个哈希值将是 当前 master 提交和提议的新 master 提交。这些都可能是有效的散列,而不是全零,但最多有一个可以是全零。如果您(接收 Git)还没有引用,则旧哈希全为零(即分支 master 对您来说是全新的);如果您(接收 Git)被要求删除引用,则 new-hash 为全零。
预推送挂钩的工作是通读所有建议的更新并验证这是否正常。如果是这样,pre-push hook 应该退出 0(shell-exit-status-speak 中的“true”)。如果没有,pre-push 挂钩可以打印输出,旨在通知运行 git push 的用户 为什么 推送被拒绝 - 用户将看到此输出,其中包含单词 remote: 卡在前面它——然后退出非零,以拒绝整个推送。
在 pre-receive 钩子运行时,接收 Git 可以访问所有建议的对象。也就是说,如果执行推送的人运行git push origin master develop,这意味着发送三个新的master 提交和一个新的develop 提交,则服务器上的预接收挂钩运行在服务器之后已收集所有四个新提交,以及这些提交所需的任何其他对象。新物品被“隔离”在某处的收容区。如果推送被拒绝,隔离区将被丢弃,而不会将提交合并到主存储库中。2整个推送在此阶段中止。
如果 pre-receive 钩子允许推送(或不存在),推送会进入下一个阶段,接收 Git 实际上确实更新每个引用,一次一个。此时接收 Git 为每个引用运行 update 钩子,给它(作为参数,而不是作为标准输入)引用、旧哈希和新哈希(注意不同的顺序)。更新挂钩可以像以前一样检查项目,然后接受或拒绝此特定更新。无论更新是否被拒绝,接收都会继续下一个引用。所以更新钩子只有一个本地视图——一次一个引用——但是更细粒度的接受/拒绝控制。
最后,在所有更新完成或被拒绝后,如果 any 引用被更新,接收 Git 运行 post-receive 钩子(如果存在)。这将获得与预接收挂钩相同的标准输入行。钩子应该退出零,因为推送已经完成。各种参考更新上的锁定已被释放,因此钩子不应在 Git 存储库中查找参考名称——它们可能已因另一次推送而更改!
2这个“隔离区”是 Git 2.13 中的新功能;在此之前,即使最终未被使用,新对象也会进入,但后来不得不被丢弃。在非常大的服务器(例如 GitHub)上,这会造成很大的痛苦。
枚举提交
给定一个旧的哈希 ID 和一个新的哈希 ID,命令:
git rev-list $old..$new
枚举所有可从$new 访问但不能从$old 访问的提交。例如,对于 git push,这些是刚刚添加的新提交。
它的对应物:
git rev-list $new..$old
枚举从$old 可访问的提交,这些提交不再可从$new 访问。例如,这些是被推送删除的提交。
请注意,两者可以同时进行!更新可能会删除一个提交并将其替换为新的和改进的变体。
您可以使用以下方法一次性获得两组提交:
git rev-list $old...$new
要使此输出有用,您必须添加--left-right 以插入标记,哪些提交只能从$old 访问,哪些提交只能从$new 访问。
您可以使用git rev-list --count 获取 counts 次可访问的提交。将--left-right 添加到三点变体会给您两个计数:例如,这就是git status 计算前后计数的方式。 (好吧,git status 已经编译了代码,所以它比在脚本中更容易——但这让你可以在脚本中执行 git status 所做的事情。)
结论
推送枚举是可能的,但只能通过使用 Git 仅在推送事件期间保留的信息。一旦推送完成或被拒绝,您就只有结果图。除了记录推送本身的关于的东西——例如,发送邮件通知某人推送事件添加了 3 个提交并删除了 1 个——这通常不是很有用,这就是 Git 不保留它的原因自己。
如果某个特定的提交分组有什么重要的地方,您可以将其记录在图表本身中。例如,假设您有一个需要三个步骤才能实现的功能:
- 升级现有的无法使用的例程,让它们更有能力
- 添加新的例程来做新的事情
- 添加以新方式使用新旧例程的顶级集成
在这种情况下,而不是从:
...--o--* <-- master
到:
...--o--*--A--B--C <-- master
其中A 到C 是执行这三个步骤的新提交,考虑将新图推送为:
...--o--*---------M <-- master
\ /
A--B--C
这里M 是一个新的合并提交。将其合并消息设置为(更好的变体)集成新功能。设置 A、B 和 C 的提交消息以增加现有例程、添加新例程以及整合新旧例程以支持新功能。这个合并气泡——A-B-C 链——隔离了这个特性,所以如果真的很糟糕,你可以通过恢复 M 来恢复整个合并,如果有什么问题,你可以测试提交 A 到 @ 987654374@单独弄清楚是什么。您可以执行其中一项或两项操作——是否恢复整个合并;测试是否单独提交,因为所有信息都永远保存在图表中。