fix backtrace printed when cannot get lock.
change lock error handling code so exceptions have useful info and
exception handling in one place.
add test case for when cannot get lock.
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1244,12 +1244,8 @@ def copy(ui, repo, *pats, **opts):
should properly record copied files, this information is not yet
fully used by merge, nor fully reported by log.
"""
- try:
- wlock = repo.wlock(0)
- errs, copied = docopy(ui, repo, pats, opts, wlock)
- except lock.LockHeld, inst:
- ui.warn(_("repository lock held by %s\n") % inst.args[0])
- errs = 1
+ wlock = repo.wlock(0)
+ errs, copied = docopy(ui, repo, pats, opts, wlock)
return errs
def debugancestor(ui, index, rev1, rev2):
@@ -2256,18 +2252,14 @@ def rename(ui, repo, *pats, **opts):
should properly record rename files, this information is not yet
fully used by merge, nor fully reported by log.
"""
- try:
- wlock = repo.wlock(0)
- errs, copied = docopy(ui, repo, pats, opts, wlock)
- names = []
- for abs, rel, exact in copied:
- if ui.verbose or not exact:
- ui.status(_('removing %s\n') % rel)
- names.append(abs)
- repo.remove(names, True, wlock)
- except lock.LockHeld, inst:
- ui.warn(_("repository lock held by %s\n") % inst.args[0])
- errs = 1
+ wlock = repo.wlock(0)
+ errs, copied = docopy(ui, repo, pats, opts, wlock)
+ names = []
+ for abs, rel, exact in copied:
+ if ui.verbose or not exact:
+ ui.status(_('removing %s\n') % rel)
+ names.append(abs)
+ repo.remove(names, True, wlock)
return errs
def revert(ui, repo, *pats, **opts):
@@ -3252,6 +3244,15 @@ def dispatch(args):
sys.exit(1)
except hg.RepoError, inst:
u.warn(_("abort: "), inst, "!\n")
+ except lock.LockHeld, inst:
+ if inst.errno == errno.ETIMEDOUT:
+ reason = _('timed out waiting for lock held by %s') % inst.locker
+ else:
+ reason = _('lock held by %s') % inst.locker
+ u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
+ except lock.LockUnavailable, inst:
+ u.warn(_("abort: could not lock %s: %s\n") %
+ (inst.desc or inst.filename, inst.strerror))
except revlog.RevlogError, inst:
u.warn(_("abort: "), inst, "!\n")
except SignalInterrupt:
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -31,6 +31,7 @@ class localrepository(object):
raise repo.RepoError(_("repository %s not found") % path)
self.root = os.path.abspath(path)
+ self.origroot = path
self.ui = ui.ui(parentui=parentui)
self.opener = util.opener(self.path)
self.wopener = util.opener(self.root)
@@ -261,32 +262,31 @@ class localrepository(object):
self.tagscache = None
self.nodetagscache = None
- def do_lock(self, lockname, wait, releasefn=None, acquirefn=None):
+ def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
+ desc=None):
try:
- l = lock.lock(self.join(lockname), 0, releasefn)
+ l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
except lock.LockHeld, inst:
if not wait:
- raise inst
- self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
- try:
- # default to 600 seconds timeout
- l = lock.lock(self.join(lockname),
- int(self.ui.config("ui", "timeout") or 600),
- releasefn)
- except lock.LockHeld, inst:
- raise util.Abort(_("timeout while waiting for "
- "lock held by %s") % inst.args[0])
+ raise
+ self.ui.warn(_("waiting for lock on %s held by %s\n") %
+ (desc, inst.args[0]))
+ # default to 600 seconds timeout
+ l = lock.lock(self.join(lockname),
+ int(self.ui.config("ui", "timeout") or 600),
+ releasefn, desc=desc)
if acquirefn:
acquirefn()
return l
def lock(self, wait=1):
- return self.do_lock("lock", wait, acquirefn=self.reload)
+ return self.do_lock("lock", wait, acquirefn=self.reload,
+ desc=_('repository %s') % self.origroot)
def wlock(self, wait=1):
- return self.do_lock("wlock", wait,
- self.dirstate.write,
- self.wreload)
+ return self.do_lock("wlock", wait, self.dirstate.write,
+ self.wreload,
+ desc=_('working directory of %s') % self.origroot)
def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
"determine whether a new filenode is needed"
--- a/mercurial/lock.py
+++ b/mercurial/lock.py
@@ -8,10 +8,16 @@
from demandload import *
demandload(globals(), 'errno os socket time util')
-class LockException(Exception):
- pass
+class LockException(IOError):
+ def __init__(self, errno, strerror, filename, desc):
+ IOError.__init__(self, errno, strerror, filename)
+ self.desc = desc
+
class LockHeld(LockException):
- pass
+ def __init__(self, errno, filename, desc, locker):
+ LockException.__init__(self, errno, 'Lock held', filename, desc)
+ self.locker = locker
+
class LockUnavailable(LockException):
pass
@@ -24,7 +30,7 @@ class lock(object):
# old-style lock: symlink to pid
# new-style lock: symlink to hostname:pid
- def __init__(self, file, timeout=-1, releasefn=None):
+ def __init__(self, file, timeout=-1, releasefn=None, desc=None):
self.f = file
self.held = 0
self.timeout = timeout
@@ -32,6 +38,7 @@ class lock(object):
self.id = None
self.host = None
self.pid = None
+ self.desc = desc
self.lock()
def __del__(self):
@@ -49,7 +56,8 @@ class lock(object):
if timeout > 0:
timeout -= 1
continue
- raise inst
+ raise LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
+ inst.locker)
def trylock(self):
if self.id is None:
@@ -64,9 +72,11 @@ class lock(object):
if why.errno == errno.EEXIST:
locker = self.testlock()
if locker:
- raise LockHeld(locker)
+ raise LockHeld(errno.EAGAIN, self.f, self.desc,
+ locker)
else:
- raise LockUnavailable(why)
+ raise LockUnavailable(why.errno, why.strerror,
+ why.filename, self.desc)
def testlock(self):
'''return id of locker if lock is valid, else None.'''
new file mode 100755
--- /dev/null
+++ b/tests/test-lock-badness
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+hg init a
+echo a > a/a
+hg --cwd a ci -A -m a
+hg clone a b
+echo b > b/b
+hg --cwd b ci -A -m b
+chmod 100 a/.hg
+hg --cwd b push ../a
+chmod 700 a/.hg
new file mode 100644
--- /dev/null
+++ b/tests/test-lock-badness.out
@@ -0,0 +1,4 @@
+adding a
+adding b
+pushing to ../a
+abort: could not lock repository ../a: Permission denied