如果同时有多个用户运行 "git push",那些命令运行在服务器上是顺序的还是并发的?

If many users run "git push" at the same time, are those commands ran sequentially or concurrently on the server?

我有一个场景,我需要为每个添加或更改的 .hbs 文件创建、添加文件并将其提交到存储库。我在 post-receive 挂钩中完成所有这些,因为要求规定必须在服务器端完成。

为了做到这一点,我在服务器 (Gitlab) 上保留了一份 repo 的工作副本,这样我就可以将文件添加到 repo 中。在我的钩子中,我生成文件然后执行 git addgit commitgit push.

这种方法让我担心的是,如果很多人同时推送会发生什么情况?如果push操作运行并发,我觉得可能会有问题。但如果它们是 运行 顺序,那么我看不出问题。

所以我的问题是:我可以假设 git push(特别是 post-receive 挂钩)运行 在服务器上按顺序排列吗?

简短的回答是否定的:你不能做出这个假设。不过你可以做一些其他的。

任何 Git 存储库将 锁定 任何给定的引用,同时更新它。即使在纯本地 Git 操作中也是如此,没有任何 client/server 事情发生:如果你 运行 一个 git commit 在一个 window 中,另一个 git commit git commit 在另一个 window 中,同时在同一个分支上,一次只能更新一个分支名称。

也就是说,实际的更新发生在之前post-接收挂钩运行。在 pre-receiveupdate 挂钩 运行 之后,post-update 挂钩 运行 之前,所有锁(在分支名称和索引本身上)​​都被释放。所以我们可以像这样构建一些比赛场景:

  • Alice 运行s git push(来自 Alice 的计算机)向服务器发送提交 a123456 并要求服务器设置服务器的 master 分支以识别提交 a123456.
  • Bob 运行s git push(从 Bob 的计算机)发送提交 b789abc 到服务器并要求服务器设置服务器的 feature 以识别提交b789abc.
  • Carol 运行s git push 将提交 fedcba9 发送到服务器并要求服务器设置服务器的 master 以识别提交 fedcba9 .

这三个中的一个将 "win the race" 开始更新。让我们假设,在不(我希望)不失一般性的情况下,是爱丽丝,接下来是鲍勃,第三位是卡罗尔。 Alice 的提交进入隔离区,1 并且服务器的预接收和更新挂钩有机会决定是否使服务器的 master 标识 a123456 是一个好主意。如果她的 git push 没有被强制,服务器 Git 将首先确保 mastercurrent 值是这个提议的新提交的祖先,也是——也就是说,git push 是一个快进操作。

如果一切顺利,Alice 的提交将从隔离区移动到主存储库数据库,master 现在在服务器上识别 a123456。服务器释放所有锁并开始 运行 连接 post-receive 挂钩。由于 Bob 排队,服务器现在(几乎是立即——这是一个一直在等待的单独进程)接受 Bob 的提交 b789abc 并将 that 放入隔离区并开始审查过程对于服务器的 refs/heads/feature。如果一切顺利,提交从隔离区移动到主数据库,refs/heads/feature 现在标识 b789abc,并且 Git 脱离 post-接收挂钩。 (如果推送被拒绝,我 think Git 仍然会脱离 post-receive 挂钩,但现在它的标准输入流中没有更新。让我们假设推送被接受。)

此时Carol的推送可以运行。她的提交 fedcba9 几乎可以肯定 没有 a123456 作为祖先——它 可以 的唯一方式是如果毕竟,Carol 早些时候从 Alice 那里得到了这个——所以在她的提交进入隔离区之后,她设置 master 的尝试将被拒绝,除非她使用 --force。服务器 Git 然后 运行 发送 post-receive 挂钩,但其标准输入流中没有更新。 (可能 post-receive 钩子根本没有 运行。)

如果第一个post-receive hook还是运行ning,此时有三个 post-receive挂钩 运行ning。一个在其标准输入中,将 refs/heads/master 从它之前持有的任何哈希 ID 更新为 a123456。其中一个在其标准输入中更新了 refs/heads/feature,从它之前持有的任何哈希 ID 更新为 b789abc。第三个有一个空的标准输入流:没有要执行的更新。

从现在开始发生的事情当然取决于您,post-receive hook 的作者。


1隔离区在某些 post-2.0 Git 版本中是新的。这些细节大多无关紧要;它们只有在预接收或更新挂钩拒绝一些名称更改时才真正重要。

Quarantine areas were new in some post-2.0 Git version. These details mostly don't matter; they're only really important if a pre-receive or update hook rejects some name-change.

问题是:在Git 2.36之前,失败的推送可能不会清除隔离区,而在其他并发推送之后可能会。

在 Git 2.36(2022 年第 2 季度)中,“receive-pack”检查在从临时目录中取出接收到的对象之前是否会进行任何引用更新(各种情况可能会拒绝推送)用于隔离目的,因此 known-to-fail 的推送将 不会留下 crufts 未来的“gc” 需要清理。

commit 5407764 (29 Jan 2022) by Chen Bojun (cbj-bojun)
(由 Junio C Hamano -- gitster -- in commit 867b520 合并,2022 年 2 月 18 日)

receive-pack: purge temporary data if no command is ready to run

Helped-by: Jiang Xin
Helped-by: Teng Long
Signed-off-by: Chen Bojun

When pushing a hidden ref, e.g.:

$ git push origin HEAD:refs/hidden/foo

"receive-pack" will reject our request with an error message like this:

! [remote rejected] HEAD -> refs/hidden/foo (deny updating a hidden ref)

The remote side ("git-receive-pack") will not create the hidden ref as expected, but the pack file sent by "git-send-pack" is left inside the remote repository.
I.e. the quarantine directory is not purged as it should be.

Add a checkpoint before calling "tmp_objdir_migrate()" and after calling the "pre-receive" hook to purge that temporary data in the quarantine area when there is no command ready to run.

The reason we do not add the checkpoint before the "pre-receive" hook, but after it, is that the "pre-receive" hook is called with a switch-off "skip_broken" flag, and all commands, even broken ones, should be fed by calling "feed_receive_hook()".