diff --git a/mercurial/exchange.py b/mercurial/exchange.py --- a/mercurial/exchange.py +++ b/mercurial/exchange.py @@ -1441,7 +1441,8 @@ def unbundle(repo, cg, heads, source, ur If the push was raced as PushRaced exception is raised.""" r = 0 # need a transaction when processing a bundle2 stream - wlock = lock = tr = None + # [wlock, lock, tr] - needs to be an array so nested functions can modify it + lockandtr = [None, None, None] recordout = None # quick fix for output mismatch with bundle2 in 3.4 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture', @@ -1454,13 +1455,22 @@ def unbundle(repo, cg, heads, source, ur if util.safehasattr(cg, 'params'): r = None try: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction(source) - tr.hookargs['source'] = source - tr.hookargs['url'] = url - tr.hookargs['bundle2'] = '1' - op = bundle2.bundleoperation(repo, lambda: tr, + def gettransaction(): + if not lockandtr[2]: + lockandtr[0] = repo.wlock() + lockandtr[1] = repo.lock() + lockandtr[2] = repo.transaction(source) + lockandtr[2].hookargs['source'] = source + lockandtr[2].hookargs['url'] = url + lockandtr[2].hookargs['bundle2'] = '1' + return lockandtr[2] + + # Do greedy locking by default until we're satisfied with lazy + # locking. + if not repo.ui.configbool('experimental', 'bundle2lazylocking'): + gettransaction() + + op = bundle2.bundleoperation(repo, gettransaction, captureoutput=captureoutput) try: op = bundle2.processbundle(repo, cg, op=op) @@ -1470,7 +1480,8 @@ def unbundle(repo, cg, heads, source, ur repo.ui.pushbuffer(error=True, subproc=True) def recordout(output): r.newpart('output', data=output, mandatory=False) - tr.close() + if lockandtr[2] is not None: + lockandtr[2].close() except BaseException as exc: exc.duringunbundle2 = True if captureoutput and r is not None: @@ -1481,10 +1492,10 @@ def unbundle(repo, cg, heads, source, ur parts.append(part) raise else: - lock = repo.lock() + lockandtr[1] = repo.lock() r = changegroup.addchangegroup(repo, cg, source, url) finally: - lockmod.release(tr, lock, wlock) + lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0]) if recordout is not None: recordout(repo.ui.popbuffer()) return r diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t --- a/tests/test-bundle2-exchange.t +++ b/tests/test-bundle2-exchange.t @@ -7,6 +7,7 @@ Test exchange of common information usin enable obsolescence + $ cp $HGRCPATH $TESTTMP/hgrc.orig $ cat > $TESTTMP/bundle2-pushkey-hook.sh << EOF > echo pushkey: lock state after \"\$HG_NAMESPACE\" > hg debuglock @@ -897,3 +898,47 @@ Check abort from mandatory pushkey abort: Clown phase push failed [255] +Test lazily acquiring the lock during unbundle + $ cp $TESTTMP/hgrc.orig $HGRCPATH + $ cat >> $HGRCPATH < [ui] + > ssh=python "$TESTDIR/dummyssh" + > EOF + + $ cat >> $TESTTMP/locktester.py < import os + > from mercurial import extensions, bundle2, util + > def checklock(orig, repo, *args, **kwargs): + > if repo.svfs.lexists("lock"): + > raise util.Abort("Lock should not be taken") + > return orig(repo, *args, **kwargs) + > def extsetup(ui): + > extensions.wrapfunction(bundle2, 'processbundle', checklock) + > EOF + + $ hg init lazylock + $ cat >> lazylock/.hg/hgrc < [extensions] + > locktester=$TESTTMP/locktester.py + > EOF + + $ hg clone -q ssh://user@dummy/lazylock lazylockclient + $ cd lazylockclient + $ touch a && hg ci -Aqm a + $ hg push + pushing to ssh://user@dummy/lazylock + searching for changes + abort: Lock should not be taken + [255] + + $ cat >> ../lazylock/.hg/hgrc < [experimental] + > bundle2lazylocking=True + > EOF + $ hg push + pushing to ssh://user@dummy/lazylock + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files