I have a problem where two similar processes are running in parallel within separate clones of the same repository (typically on different computers). Each time a process runs, it fetches the latest tags from the remote and then deduces a unique number based on the tags it sees.
E.g. if these tags exist on the remote: 1.0 1.1 1.2 1.3 then a process will choose 1.4 as the next number.
Before the process starts, it creates a new tag and pushes this back to the remote:
$ git tag 1.4 HEAD
$ git push origin tag 1.4
The idea was that this is a way to atomically select numbers. The other process, if it's looking at the same time, might also decide to use 1.4, but when it comes to push it's tag, it should discover that 1.4 already exists, and choose 1.5 instead (and try again).
My hope was that I could treat git tag pushes as atomic.
Unfortunately, for some weird reason, git allows remote tags to move in certain circumstances!
For example, let's say tag 1.4 has been put on origin/master and pushed. The other process wants to put tag 1.4 on, say, origin/master^, which would involve moving the tag backwards. Git will reject this with a 'non-fast-forward' error:
Process A:
$ git tag 1.4 origin/master
$ git push origin tag 1.4
Total 0 (delta 0), reused 0 (delta 0)
To /repo1
* [new tag] 1.4 -> 1.4
Process B:
$ git tag 1.4 origin/master^
$ git push origin tag 1.4
To /repo1
! [rejected] 1.4 -> 1.4 (non-fast forward)
error: failed to push some refs to '/repo1'
Ok, that's fine, Process B can use this to try 1.5 instead.
But consider this situation:
Process A:
$ git tag 1.4 origin/master
$ git push origin tag 1.4
Total 0 (delta 0), reused 0 (delta 0)
To /repo1
* [new tag] 1.4 -> 1.4
Process B:
$ git tag 1.4 origin/master
$ git push origin tag 1.4
Everything up-to-date
Oh. That's a shame - git didn't indicate that this tag already exists on the remote. Actually, it does, with -v:
$ git push origin tag 1.4 -v
Pushing to /repo1
To /repo1
= [up to date] 1.4 -> 1.4
Everything up-to-date
Ok, so I can do some sort of stderr redirect, search for " = ", and that will allow Process B to determine that 1.4 is already in use.
But that's a bit silly. And it gets worse:
Process A:
$ git push origin tag 1.4
Total 0 (delta 0), reused 0 (delta 0)
To /repo1
* [new tag] 1.4 -> 1.4
Process B:
$ git push origin tag 1.4
Total 0 (delta 0), reused 0 (delta 0)
To /repo1
fd0e09e..c6cdac9 1.4 -> 1.4
Argg! What? Git has just moved the remote tag without warning!
So it seems to me that remote tags in git are fundamentally broken - they shouldn't just "move" without an explicit request. More to the point, they should refuse to move by default.
Also, the git-tag command should provide a way to atomically test-and-set a tag.
But clearly it doesn't. Running git fetch first isn't going to help because there's still a window of conflict and even if there is a conflict, in one of the three scenarios the tag simply moves!
What is going on here?
Is there another way to test-and-set a tag?
If not, how do people allocate and reserve build numbers in an automated build environment? How do you reliably detect when two processes have inadvertently picked up the same build number?
Using git 1.6.1.2.