{"thread":{"id":"26205","subject":"concurrent fetches to update same mirror","startedAt":"2011-01-05T20:33:36Z","lastAt":"2011-01-07T14:51:53Z","messageCount":12,"participants":["Neal Kreitzinger","Jeff King","Shawn Pearce","Junio C Hamano","Marc Branchaud"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"158986","messageId":"ig2kjt$f2u$1@dough.gmane.org","threadId":"26205","inReplyTo":null,"subject":"concurrent fetches to update same mirror","fromName":"Neal Kreitzinger","fromEmail":"neal@rsss.com","sentAt":"2011-01-05T20:33:36Z","receivedAt":"2011-01-05T20:33:36Z","isPatch":false,"sender":{"key":"neal@rsss.com","avatar":null},"body":"If two or more different users perform a git-fetch on the same mirror \n(--mirror) repo concurrently, could that cause corruption?  I tried a manual \ntest using the git protocol over separate machines and they both thought \nthey needed to do the full updates and they both appeared to work.  I'm not \nsure if git is serializing this, or if it is possible for concurrent fetches \nto step on each other.\n\nv/r,\nNeal \n"},{"id":"158989","messageId":"20110105204738.GA7629@sigill.intra.peff.net","threadId":"26205","inReplyTo":"ig2kjt$f2u$1@dough.gmane.org","subject":"Re: concurrent fetches to update same mirror","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-01-05T20:47:38Z","receivedAt":"2011-01-05T20:47:38Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jan 05, 2011 at 02:33:36PM -0600, Neal Kreitzinger wrote:\n\n> If two or more different users perform a git-fetch on the same mirror \n> (--mirror) repo concurrently, could that cause corruption?  I tried a manual \n> test using the git protocol over separate machines and they both thought \n> they needed to do the full updates and they both appeared to work.  I'm not \n> sure if git is serializing this, or if it is possible for concurrent fetches \n> to step on each other.\n\nNo, it shouldn't cause corruption, but it will cause wasted effort and\nit may cause one to report failure. The fetch process gets all of the\nobjects first, and then updates the ref (so we never have refs that\npoint to object we didn't get yet). So both of the concurrent fetches\nwill see that we have a big set of objects to get and will work on\ngetting them at the same time, after which they will update the refs\nappropriately (presumably to the same thing).\n\nI haven't looked specifically at how fetch does locking, but usually the\nprocedure is to lock the ref, fetch the old value, unlock it, then do\nsome long-running task (like fetching objects), then lock again, check\nthat the old value didn't change out from under us, update it, then\nunlock. In which case one of the fetches might see \"oops, somebody\nupdated while we were fetching\" and complain.\n\nHowever, in the default configuration, we fetch using a \"+\" refspec,\nwhich forces update of the ref even in the case of a non-fast-forward. I\ndon't know whether that force also would override any lock-checking.\n\n-Peff\n"},{"id":"158990","messageId":"AANLkTini61q+NtDr6oytTcfA6QNGN74L60exdLrNmakd@mail.gmail.com","threadId":"26205","inReplyTo":"20110105204738.GA7629@sigill.intra.peff.net","subject":"Re: concurrent fetches to update same mirror","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2011-01-05T20:51:12Z","receivedAt":"2011-01-05T20:51:12Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"On Wed, Jan 5, 2011 at 12:47, Jeff King <peff@peff.net> wrote:\n>\n> However, in the default configuration, we fetch using a \"+\" refspec,\n> which forces update of the ref even in the case of a non-fast-forward. I\n> don't know whether that force also would override any lock-checking.\n\nNope, it doesn't.  We still use locking to update the refs, to ensure\nthe update is seen atomically by a reader.  The + just means don't\ncheck that the old value is fully reachable from the new after the\nlock as been taken.\n\nIf both fetch processes try to update the same ref at the same time,\none will get the lock and continue, and the other will crash with an\nerror (because the lock was busy).  If one is slightly slower than the\nother, they will probably update the refs twice, with the slower fetch\nupdating what the faster one had just updated.  :-)\n\n-- \nShawn.\n"},{"id":"158991","messageId":"20110105205324.GA7808@sigill.intra.peff.net","threadId":"26205","inReplyTo":"AANLkTini61q+NtDr6oytTcfA6QNGN74L60exdLrNmakd@mail.gmail.com","subject":"Re: concurrent fetches to update same mirror","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-01-05T20:53:25Z","receivedAt":"2011-01-05T20:53:25Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jan 05, 2011 at 12:51:12PM -0800, Shawn Pearce wrote:\n\n> On Wed, Jan 5, 2011 at 12:47, Jeff King <peff@peff.net> wrote:\n> >\n> > However, in the default configuration, we fetch using a \"+\" refspec,\n> > which forces update of the ref even in the case of a non-fast-forward. I\n> > don't know whether that force also would override any lock-checking.\n> \n> Nope, it doesn't.  We still use locking to update the refs, to ensure\n> the update is seen atomically by a reader.  The + just means don't\n> check that the old value is fully reachable from the new after the\n> lock as been taken.\n\nGood, that's what IMHO it _should_ do. :)\n\n> If both fetch processes try to update the same ref at the same time,\n> one will get the lock and continue, and the other will crash with an\n> error (because the lock was busy).  If one is slightly slower than the\n> other, they will probably update the refs twice, with the slower fetch\n> updating what the faster one had just updated.  :-)\n\nI assumed it would take the \"old\" value at the very beginning of the\nfetch (before talking with the remote), and then see that the ref was\nchanged under our feet. Or does it simply do it at the end?\n\n... goes to read code ...\n\n-Peff\n"},{"id":"158993","messageId":"20110105211313.GB7808@sigill.intra.peff.net","threadId":"26205","inReplyTo":"20110105205324.GA7808@sigill.intra.peff.net","subject":"Re: concurrent fetches to update same mirror","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-01-05T21:13:14Z","receivedAt":"2011-01-05T21:13:14Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jan 05, 2011 at 03:53:25PM -0500, Jeff King wrote:\n\n> > If both fetch processes try to update the same ref at the same time,\n> > one will get the lock and continue, and the other will crash with an\n> > error (because the lock was busy).  If one is slightly slower than the\n> > other, they will probably update the refs twice, with the slower fetch\n> > updating what the faster one had just updated.  :-)\n> \n> I assumed it would take the \"old\" value at the very beginning of the\n> fetch (before talking with the remote), and then see that the ref was\n> changed under our feet. Or does it simply do it at the end?\n\nHmm. Weirder even, builtin/fetch.c:s_update_ref takes a \"check_old\"\nflag, and we do always use it for branch updates. But not for tag\nupdates. I can't think of why. The code blames all the way back to the\noriginal builtin-fetch.\n\nAnyway, when we do check, we check the value from the beginning of the\nfetch. So you can get lock conflicts. For example, doing this:\n\n  mkdir repo && cd repo && git init\n  echo contents >foo && git add . && git commit -m one\n  git update-ref refs/remotes/origin/master refs/heads/master\n  git remote add origin some-remote-repo-that-takes-a-few-seconds\n  xterm -e 'git fetch -v; read' & xterm -e 'git fetch -v; read'\n\nI.e., putting some cruft into the ref and then updating it. One fetch\nwill force-write over the ref properly:\n\n   + ac32203...4e64590 master     -> origin/master  (forced update)\n\nbut the other one will barf on the lock:\n\n  error: Ref refs/remotes/origin/master is at 4e6459052ab329914c7712a926773e566b8c821d but expected ac32203727daa3bcb5fc041786aa45adbbe86299\n  ...\n   ! ac32203...4e64590 master     -> origin/master  (unable to update local ref)\n\nInterestingly, in the case of ref _creation_, not update, like this:\n\n  mkdir repo && cd repo && git init\n  git remote add origin some-remote-repo-that-takes-a-few-seconds\n  xterm -e 'git fetch -v; read' & xterm -e 'git fetch -v; read'\n\nthen both will happily update, the second one overwriting the results of\nthe first. It seems in the case of locking a ref which previously didn't\nexist, we don't enforce that it still doesn't exist.\n\nI wonder if we should, but perhaps there is some corner case I am not\nconsidering. The code is in lock_ref_sha1_basic, but blaming didn't turn\nup anything helpful.\n\n-Peff\n"},{"id":"158997","messageId":"4D24F1FE.5060400@gmail.com","threadId":"26205","inReplyTo":"20110105211313.GB7808@sigill.intra.peff.net","subject":"Re: concurrent fetches to update same mirror","fromName":"Neal Kreitzinger","fromEmail":"nkreitzinger@gmail.com","sentAt":"2011-01-05T22:34:38Z","receivedAt":"2011-01-05T22:34:38Z","isPatch":false,"sender":{"key":"nkreitzinger@gmail.com","avatar":null},"body":"On 1/5/2011 3:13 PM, Jeff King wrote:\n> On Wed, Jan 05, 2011 at 03:53:25PM -0500, Jeff King wrote:\n>\n>>> If both fetch processes try to update the same ref at the same time,\n>>> one will get the lock and continue, and the other will crash with an\n>>> error (because the lock was busy).  If one is slightly slower than the\n>>> other, they will probably update the refs twice, with the slower fetch\n>>> updating what the faster one had just updated.  :-)\n>>\n>> I assumed it would take the \"old\" value at the very beginning of the\n>> fetch (before talking with the remote), and then see that the ref was\n>> changed under our feet. Or does it simply do it at the end?\n>\n> Hmm. Weirder even, builtin/fetch.c:s_update_ref takes a \"check_old\"\n> flag, and we do always use it for branch updates. But not for tag\n> updates. I can't think of why. The code blames all the way back to the\n> original builtin-fetch.\n>\n> Anyway, when we do check, we check the value from the beginning of the\n> fetch. So you can get lock conflicts. For example, doing this:\n>\n>    mkdir repo&&  cd repo&&  git init\n>    echo contents>foo&&  git add .&&  git commit -m one\n>    git update-ref refs/remotes/origin/master refs/heads/master\n>    git remote add origin some-remote-repo-that-takes-a-few-seconds\n>    xterm -e 'git fetch -v; read'&  xterm -e 'git fetch -v; read'\n>\n> I.e., putting some cruft into the ref and then updating it. One fetch\n> will force-write over the ref properly:\n>\n>     + ac32203...4e64590 master     ->  origin/master  (forced update)\n>\n> but the other one will barf on the lock:\n>\n>    error: Ref refs/remotes/origin/master is at 4e6459052ab329914c7712a926773e566b8c821d but expected ac32203727daa3bcb5fc041786aa45adbbe86299\n>    ...\n>     ! ac32203...4e64590 master     ->  origin/master  (unable to update local ref)\n>\n> Interestingly, in the case of ref _creation_, not update, like this:\n>\n>    mkdir repo&&  cd repo&&  git init\n>    git remote add origin some-remote-repo-that-takes-a-few-seconds\n>    xterm -e 'git fetch -v; read'&  xterm -e 'git fetch -v; read'\n>\n> then both will happily update, the second one overwriting the results of\n> the first. It seems in the case of locking a ref which previously didn't\n> exist, we don't enforce that it still doesn't exist.\n>\n> I wonder if we should, but perhaps there is some corner case I am not\n> considering. The code is in lock_ref_sha1_basic, but blaming didn't turn\n> up anything helpful.\n>\n> -Peff\n\nThis was actually the case in my test.  Updates to the mirror are always \nnew branches except for master.  The only pre-existing branch that might \nget updated is master, but in that test it didn't.  The new branches and \ntags were updated.  The new tags always point to the new branches.  I'm \nrunning 1.7.1 on both servers.\n\nv/r,\nNeal\n"},{"id":"158998","messageId":"4D24F3E9.3070904@gmail.com","threadId":"26205","inReplyTo":"20110105211313.GB7808@sigill.intra.peff.net","subject":"Re: concurrent fetches to update same mirror","fromName":"Neal Kreitzinger","fromEmail":"nkreitzinger@gmail.com","sentAt":"2011-01-05T22:42:49Z","receivedAt":"2011-01-05T22:42:49Z","isPatch":false,"sender":{"key":"nkreitzinger@gmail.com","avatar":null},"body":"On 1/5/2011 3:13 PM, Jeff King wrote:\n> On Wed, Jan 05, 2011 at 03:53:25PM -0500, Jeff King wrote:\n>\n>>> If both fetch processes try to update the same ref at the same time,\n>>> one will get the lock and continue, and the other will crash with an\n>>> error (because the lock was busy).  If one is slightly slower than the\n>>> other, they will probably update the refs twice, with the slower fetch\n>>> updating what the faster one had just updated.  :-)\n>>\n>> I assumed it would take the \"old\" value at the very beginning of the\n>> fetch (before talking with the remote), and then see that the ref was\n>> changed under our feet. Or does it simply do it at the end?\n>\n> Hmm. Weirder even, builtin/fetch.c:s_update_ref takes a \"check_old\"\n> flag, and we do always use it for branch updates. But not for tag\n> updates. I can't think of why. The code blames all the way back to the\n> original builtin-fetch.\n>\n> Anyway, when we do check, we check the value from the beginning of the\n> fetch. So you can get lock conflicts. For example, doing this:\n>\n>    mkdir repo&&  cd repo&&  git init\n>    echo contents>foo&&  git add .&&  git commit -m one\n>    git update-ref refs/remotes/origin/master refs/heads/master\n>    git remote add origin some-remote-repo-that-takes-a-few-seconds\n>    xterm -e 'git fetch -v; read'&  xterm -e 'git fetch -v; read'\n>\n> I.e., putting some cruft into the ref and then updating it. One fetch\n> will force-write over the ref properly:\n>\n>     + ac32203...4e64590 master     ->  origin/master  (forced update)\n>\n> but the other one will barf on the lock:\n>\n>    error: Ref refs/remotes/origin/master is at 4e6459052ab329914c7712a926773e566b8c821d but expected ac32203727daa3bcb5fc041786aa45adbbe86299\n>    ...\n>     ! ac32203...4e64590 master     ->  origin/master  (unable to update local ref)\n>\n> Interestingly, in the case of ref _creation_, not update, like this:\n>\n>    mkdir repo&&  cd repo&&  git init\n>    git remote add origin some-remote-repo-that-takes-a-few-seconds\n>    xterm -e 'git fetch -v; read'&  xterm -e 'git fetch -v; read'\n>\n> then both will happily update, the second one overwriting the results of\n> the first. It seems in the case of locking a ref which previously didn't\n> exist, we don't enforce that it still doesn't exist.\n>\n> I wonder if we should, but perhaps there is some corner case I am not\n> considering. The code is in lock_ref_sha1_basic, but blaming didn't turn\n> up anything helpful.\n>\n> -Peff\n\nIn the case of concurrent pulls to the same non-bare repo, could the \nworking tree or index get corrupted, or does git have concurrency \ncontrol mechanisms for this too?\n\nv/r,\nNeal\n"},{"id":"159000","messageId":"20110105225752.GA9774@sigill.intra.peff.net","threadId":"26205","inReplyTo":"4D24F3E9.3070904@gmail.com","subject":"Re: concurrent fetches to update same mirror","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-01-05T22:57:52Z","receivedAt":"2011-01-05T22:57:52Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jan 05, 2011 at 04:42:49PM -0600, Neal Kreitzinger wrote:\n\n> In the case of concurrent pulls to the same non-bare repo, could the\n> working tree or index get corrupted, or does git have concurrency\n> control mechanisms for this too?\n\nThere's a lock on the index, so it shouldn't be corruptable; one process\nwill just end up waiting. I'm not sure offhand whether writing working\ntree files is done under any lock, but I would tend to think not, since\nit can be a long process. However, writing the same file twice should be\nOK; we unlink the old version and create the new from scratch. So the\nfirst writer will get its write-in-progress unlinked, and the second one\nwill \"win\".\n\n-Peff\n"},{"id":"159005","messageId":"7vbp3vc4k4.fsf@alter.siamese.dyndns.org","threadId":"26205","inReplyTo":"20110105211313.GB7808@sigill.intra.peff.net","subject":"Re: concurrent fetches to update same mirror","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-01-05T23:29:47Z","receivedAt":"2011-01-05T23:29:47Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> Interestingly, in the case of ref _creation_, not update, like this:\n>\n>   mkdir repo && cd repo && git init\n>   git remote add origin some-remote-repo-that-takes-a-few-seconds\n>   xterm -e 'git fetch -v; read' & xterm -e 'git fetch -v; read'\n>\n> then both will happily update, the second one overwriting the results of\n> the first. It seems in the case of locking a ref which previously didn't\n> exist, we don't enforce that it still doesn't exist.\n\nWe probably should, especially when there is no --force or +prefix is\ninvolved.\n"},{"id":"159082","messageId":"20110106234512.GA17231@sigill.intra.peff.net","threadId":"26205","inReplyTo":"7vbp3vc4k4.fsf@alter.siamese.dyndns.org","subject":"Re: concurrent fetches to update same mirror","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-01-06T23:45:12Z","receivedAt":"2011-01-06T23:45:12Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jan 05, 2011 at 03:29:47PM -0800, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > Interestingly, in the case of ref _creation_, not update, like this:\n> >\n> >   mkdir repo && cd repo && git init\n> >   git remote add origin some-remote-repo-that-takes-a-few-seconds\n> >   xterm -e 'git fetch -v; read' & xterm -e 'git fetch -v; read'\n> >\n> > then both will happily update, the second one overwriting the results of\n> > the first. It seems in the case of locking a ref which previously didn't\n> > exist, we don't enforce that it still doesn't exist.\n> \n> We probably should, especially when there is no --force or +prefix is\n> involved.\n\nHmph. So I created the test below to try to exercise this, expecting to\nsee at least one failure: according to the above example, we aren't\nactually checking \"null sha1 means ref must not exist\", so we should get\nan erroneous success for that case. And there is the added complication\nthat the null sha1 may also mean \"don't care what the old one was\". So\neven if I changed the code, we would get erroneous failures the other\nway.\n\nBut much to my surprise, it actually passes with stock git. Which means\nI need to dig a little further to see exactly what is going on.\n\nHooray for test-driven development, I guess? :)\n\ndiff --git a/t/t1403-concurrent-refs.sh b/t/t1403-concurrent-refs.sh\nnew file mode 100755\nindex 0000000..7fb4424\n--- /dev/null\n+++ b/t/t1403-concurrent-refs.sh\n@@ -0,0 +1,49 @@\n+#!/bin/sh\n+\n+test_description='test behavior of concurrent ref updates'\n+. ./test-lib.sh\n+\n+ref=refs/heads/foo\n+null=0000000000000000000000000000000000000000\n+\n+check_ref() {\n+\techo $2 >expect &&\n+\tgit rev-parse --verify $1 >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success setup '\n+\tfor name in A B C; do\n+\t\ttest_tick &&\n+\t\tT=$(git write-tree) &&\n+\t\tsha1=$(echo $name | git commit-tree $T) &&\n+\t\teval $name=$sha1\n+\tdone\n+'\n+\n+test_expect_success '(create ref, expecting non-null sha1) should fail' '\n+\ttest_must_fail git update-ref $ref $A $C &&\n+\ttest_must_fail git rev-parse --verify $ref\n+'\n+\n+test_expect_success '(create ref, expecting null sha1) should work' '\n+\tgit update-ref $ref $A $null &&\n+\tcheck_ref $ref $A\n+'\n+\n+test_expect_success '(update ref, expecting null sha1) should fail' '\n+\ttest_must_fail git update-ref $ref $B $null &&\n+\tcheck_ref $ref $A\n+'\n+\n+test_expect_success '(update ref, expecting wrong sha1) should fail' '\n+\ttest_must_fail git update-ref $ref $B $C &&\n+\tcheck_ref $ref $A\n+'\n+\n+test_expect_success '(update ref, expecting current sha1) should work' '\n+\tgit update-ref $ref $B $A &&\n+\tcheck_ref $ref $B\n+'\n+\n+test_done\n"},{"id":"159110","messageId":"4D272834.9060001@xiplink.com","threadId":"26205","inReplyTo":"20110106234512.GA17231@sigill.intra.peff.net","subject":"Re: concurrent fetches to update same mirror","fromName":"Marc Branchaud","fromEmail":"marcnarc@xiplink.com","sentAt":"2011-01-07T14:50:28Z","receivedAt":"2011-01-07T14:50:28Z","isPatch":false,"sender":{"key":"marcnarc@xiplink.com","avatar":"https://avatars.githubusercontent.com/u/14980203?v=4"},"body":"On 11-01-06 06:45 PM, Jeff King wrote:\n> On Wed, Jan 05, 2011 at 03:29:47PM -0800, Junio C Hamano wrote:\n> \n>> Jeff King <peff@peff.net> writes:\n>>\n>>> Interestingly, in the case of ref _creation_, not update, like this:\n>>>\n>>>   mkdir repo && cd repo && git init\n>>>   git remote add origin some-remote-repo-that-takes-a-few-seconds\n>>>   xterm -e 'git fetch -v; read' & xterm -e 'git fetch -v; read'\n>>>\n>>> then both will happily update, the second one overwriting the results of\n>>> the first. It seems in the case of locking a ref which previously didn't\n>>> exist, we don't enforce that it still doesn't exist.\n>>\n>> We probably should, especially when there is no --force or +prefix is\n>> involved.\n> \n> Hmph. So I created the test below to try to exercise this, expecting to\n> see at least one failure: according to the above example, we aren't\n> actually checking \"null sha1 means ref must not exist\", so we should get\n> an erroneous success for that case. And there is the added complication\n> that the null sha1 may also mean \"don't care what the old one was\". So\n> even if I changed the code, we would get erroneous failures the other\n> way.\n> \n> But much to my surprise, it actually passes with stock git. Which means\n> I need to dig a little further to see exactly what is going on.\n\nI should point out that the repository where I saw this issue is running git\n1.7.1.\n\n\t\tM.\n"},{"id":"159112","messageId":"4D272889.7080601@xiplink.com","threadId":"26205","inReplyTo":"4D272834.9060001@xiplink.com","subject":"Re: concurrent fetches to update same mirror","fromName":"Marc Branchaud","fromEmail":"marcnarc@xiplink.com","sentAt":"2011-01-07T14:51:53Z","receivedAt":"2011-01-07T14:51:53Z","isPatch":false,"sender":{"key":"marcnarc@xiplink.com","avatar":"https://avatars.githubusercontent.com/u/14980203?v=4"},"body":"On 11-01-07 09:50 AM, Marc Branchaud wrote:\n> On 11-01-06 06:45 PM, Jeff King wrote:\n>> On Wed, Jan 05, 2011 at 03:29:47PM -0800, Junio C Hamano wrote:\n>>\n>>> Jeff King <peff@peff.net> writes:\n>>>\n>>>> Interestingly, in the case of ref _creation_, not update, like this:\n>>>>\n>>>>   mkdir repo && cd repo && git init\n>>>>   git remote add origin some-remote-repo-that-takes-a-few-seconds\n>>>>   xterm -e 'git fetch -v; read' & xterm -e 'git fetch -v; read'\n>>>>\n>>>> then both will happily update, the second one overwriting the results of\n>>>> the first. It seems in the case of locking a ref which previously didn't\n>>>> exist, we don't enforce that it still doesn't exist.\n>>>\n>>> We probably should, especially when there is no --force or +prefix is\n>>> involved.\n>>\n>> Hmph. So I created the test below to try to exercise this, expecting to\n>> see at least one failure: according to the above example, we aren't\n>> actually checking \"null sha1 means ref must not exist\", so we should get\n>> an erroneous success for that case. And there is the added complication\n>> that the null sha1 may also mean \"don't care what the old one was\". So\n>> even if I changed the code, we would get erroneous failures the other\n>> way.\n>>\n>> But much to my surprise, it actually passes with stock git. Which means\n>> I need to dig a little further to see exactly what is going on.\n> \n> I should point out that the repository where I saw this issue is running git\n> 1.7.1.\n\nOops -- sorry!  I'm in the wrong concurrency thread...\n\n\t\tM.\n"}]}