Michael
Michael

Reputation: 1607

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

I have a scenario in which I need to create, add and commit a file to the repo for every .hbs file that is added or changes. I do all that in a post-receive hook because the requirements state that it must be done server-side.

In order to do that, I keep a working copy of the repo on the server (Gitlab) so I can add the file to the repo. In my hook, I generate the file then do git add, git commit and git push.

What worries me with that approach is what happens if many people push at the same time? If the push operations run concurrently, I think it might be problematic. But if they are run sequentially, then I see no problem.

So my question is: Can I assume that git push (particularly the post-receive hook) run sequentially on the server?

Upvotes: 2

Views: 217

Answers (2)

VonC
VonC

Reputation: 1324937

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.

The problem is: before Git 2.36, a failed push might not clear the quanrantine area, which could after other concurrent push.

With Git 2.36 (Q2 2022), "receive-pack" checks if it will do any ref updates (various conditions could reject a push) before received objects are taken out of the temporary directory used for quarantine purposes, so that a push that is known-to-fail will not leave crufts that a future "gc" needs to clean up.

See commit 5407764 (29 Jan 2022) by Chen Bojun (cbj-bojun).
(Merged by Junio C Hamano -- gitster -- in commit 867b520, 18 Feb 2022)

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()".

Upvotes: 0

torek
torek

Reputation: 488453

The short answer is no: you cannot make this assumption. You can make some others, though.

Any Git repository will lock any given reference while updating it. This is true even on a purely-local Git operation, without any client/server stuff going on: if you run one git commit in one window, and another git commit in another window, at the same time, on the same branch, only one at a time can actually update that branch name.

That said, the actual updating occurs before the post-receive hook runs. All locks (on branch names, and on the index itself) are released after the pre-receive and update hooks run, before the post-update hook runs. So we can construct some race scenarios like this:

  • Alice runs git push (from Alice's computer) to send commit a123456 to the server and to ask the server to set the server's master branch to identify commit a123456.
  • Bob runs git push (from Bob's computer) to send commit b789abc to the server and to ask the server to set the server's feature to identify commit b789abc.
  • Carol runs git push to send commit fedcba9 to the server and to ask the server to set the server's master to identify commit fedcba9.

One of these three will "win the race" to start updating. Let's assume, without (I hope) loss of generality, that it's Alice, and that Bob will go next and Carol will go third. Alice's commit goes into the quarantine area,1 and the server's pre-receive and update hooks get a chance to decide whether making the server's master identify a123456 is a good idea. If her git push is not forced, the server Git will first ensure that the current value of master is an ancestor of this proposed new commit, too—i.e., that the git push is a fast-forward operation.

If all goes well, Alice's commit is moved from quarantine to the main repository database and master now identifies a123456 on the server. The server releases all the locks and begins running the post-receive hook. Since Bob is queued up, the server now (almost immediately—it's a separate process that has been waiting) takes Bob's commit b789abc and puts that in quarantine and starts the vetting process for the server's refs/heads/feature. If all goes well, the commit moves from quarantine to the main database, refs/heads/feature now identifies b789abc, and Git spins off the post-receive hook. (If the push is rejected, I think Git still spins off the post-receive hook, but now there are no updates in its stdin stream. Let's assume the push is accepted.)

At this point Carol's push can run. Her commit fedcba9 almost certainly does not have a123456 as an ancestor—the only way it could have it is if Carol got this from Alice earlier, after all—so after her commit goes into quarantine, her attempt to set master will be rejected unless she used --force. The server Git then runs the post-receive hook, but with no updates in its stdin stream. (Possibly, the post-receive hook doesn't run at all.)

If the first post-receive hook is still running, there are, at this point, three post-receive hooks running. One has, in its stdin, an update to refs/heads/master from whatever hash ID it held before, to a123456. One has, in its stdin, an update to refs/heads/feature, from whatever hash ID it held before, to b789abc. The third has an empty stdin stream: there are no updates to perform.

What happens from here on out is, of course, up to you, the author of the post-receive hook.


1Quarantine 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.

Upvotes: 3

Related Questions