--- a/mercurial/commands.py Wed Sep 24 11:20:35 2014 -0700
+++ b/mercurial/commands.py Fri Sep 26 16:44:11 2014 -0500
@@ -9,7 +9,7 @@
from lock import release
from i18n import _
import os, re, difflib, time, tempfile, errno, shlex
-import sys
+import sys, socket
import hg, scmutil, util, revlog, copies, error, bookmarks
import patch, help, encoding, templatekw, discovery
import archival, changegroup, cmdutil, hbisect
@@ -2338,6 +2338,78 @@
ui.write('\n'.join(sorted(completions)))
ui.write('\n')
+@command('debuglocks',
+ [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
+ ('W', 'force-wlock', None,
+ _('free the working state lock (DANGEROUS)'))],
+ _(''))
+def debuglocks(ui, repo, **opts):
+ """show or modify state of locks
+
+ By default, this command will show which locks are held. This
+ includes the user and process holding the lock, the amount of time
+ the lock has been held, and the machine name where the process is
+ running if it's not local.
+
+ Locks protect the integrity of Mercurial's data, so should be
+ treated with care. System crashes or other interruptions may cause
+ locks to not be properly released, though Mercurial will usually
+ detect and remove such stale locks automatically.
+
+ However, detecting stale locks may not always be possible (for
+ instance, on a shared filesystem). Removing locks may also be
+ blocked by filesystem permissions.
+
+ Returns 0 if no locks are held.
+
+ """
+
+ if opts.get('force_lock'):
+ repo.svfs.unlink('lock')
+ if opts.get('force_wlock'):
+ repo.vfs.unlink('wlock')
+ if opts.get('force_lock') or opts.get('force_lock'):
+ return 0
+
+ now = time.time()
+ held = 0
+
+ def report(vfs, name, method):
+ # this causes stale locks to get reaped for more accurate reporting
+ try:
+ l = method(False)
+ except error.LockHeld:
+ l = None
+
+ if l:
+ l.release()
+ else:
+ try:
+ stat = repo.svfs.lstat(name)
+ age = now - stat.st_mtime
+ user = util.username(stat.st_uid)
+ locker = vfs.readlock(name)
+ if ":" in locker:
+ host, pid = locker.split(':')
+ if host == socket.gethostname():
+ locker = 'user %s, process %s' % (user, pid)
+ else:
+ locker = 'user %s, process %s, host %s' \
+ % (user, pid, host)
+ ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
+ return 1
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+
+ ui.write("%-6s free\n" % (name + ":"))
+ return 0
+
+ held += report(repo.svfs, "lock", repo.lock)
+ held += report(repo.vfs, "wlock", repo.wlock)
+
+ return held
+
@command('debugobsolete',
[('', 'flags', 0, _('markers flag')),
('', 'record-parents', False,
--- a/tests/test-completion.t Wed Sep 24 11:20:35 2014 -0700
+++ b/tests/test-completion.t Fri Sep 26 16:44:11 2014 -0500
@@ -89,6 +89,7 @@
debuginstall
debugknown
debuglabelcomplete
+ debuglocks
debugobsolete
debugpathcomplete
debugpushkey
@@ -245,6 +246,7 @@
debuginstall:
debugknown:
debuglabelcomplete:
+ debuglocks: force-lock, force-wlock
debugobsolete: flags, record-parents, rev, date, user
debugpathcomplete: full, normal, added, removed
debugpushkey:
--- a/tests/test-help.t Wed Sep 24 11:20:35 2014 -0700
+++ b/tests/test-help.t Fri Sep 26 16:44:11 2014 -0500
@@ -781,6 +781,7 @@
debugknown test whether node ids are known to a repo
debuglabelcomplete
complete "labels" - tags, open branch names, bookmark names
+ debuglocks show or modify state of locks
debugobsolete
create arbitrary obsolete marker
debugoptDEP (no help text available)
--- a/tests/test-push-hook-lock.t Wed Sep 24 11:20:35 2014 -0700
+++ b/tests/test-push-hook-lock.t Fri Sep 26 16:44:11 2014 -0500
@@ -16,6 +16,7 @@
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ echo '[hooks]' >> 2/.hg/hgrc
+ $ echo 'pretxnchangegroup.a = hg debuglocks; true' >> 2/.hg/hgrc
$ echo 'changegroup.push = hg push -qf ../1' >> 2/.hg/hgrc
$ echo bar >> 3/foo
@@ -28,4 +29,6 @@
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
+ lock: user *, process * (*s) (glob)
+ wlock: free