[PATCH 15 of 19] mq: use decorator to wrap writing commands with locks and automatic savedirty

Mads Kiilerich mads at kiilerich.com
Thu Jan 12 19:32:49 CST 2012


# HG changeset patch
# User Mads Kiilerich <mads at kiilerich.com>
# Date 1326245685 -3600
# Node ID 8bccb99f4ff21f335503798db047cf27c79cd77c
# Parent  342cbabf227347bd3cd65464ce9a19980df0be9b
mq: use decorator to wrap writing commands with locks and automatic savedirty

This cleans up the mq code and makes it less fragile and ensures that all
non-read-only commands see and modify a consistent snapshot of the repo and the
mq meta data.

The mq state files are not append only, and consistency for unlocked read-only
operations can thus not be guaranteed with Mercurials locking scheme. This
patch introduces locking the places where it seems most critical to have
consistent data: when modifying the repo in any way.

The new locking might be too broad for some operations and it might prevent
some kinds of read-only operations on read-only repositories, such as read-only
sub-commands of commands that also can modify the repository.

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -57,6 +57,23 @@
 cmdtable = {}
 command = cmdutil.command(cmdtable)
 
+def mqlocksavedirty(func):
+    """Wrap a command in a wlock and lock and create a new queue instance that
+    thus (assuming this protocol is followed consistently) is guaranteed to be
+    consistent. mq.savedirty is run before releasing the locks."""
+    def wrapper(ui, repo, *args, **kwargs):
+        wlock = lock = None
+        try:
+            wlock = repo.wlock()
+            lock = repo.lock()
+            repo.mq = queue(repo.ui, repo.path)
+            return func(ui, repo, *args, **kwargs)
+        finally:
+            repo.mq.savedirty()
+            release(lock, wlock)
+    wrapper.__doc__ = getattr(func, '__doc__', None)
+    return wrapper
+
 # Patch names looks like unix-file names.
 # They must be joinable with queue directory and result in the patch path.
 normname = util.normpath
@@ -1853,6 +1870,7 @@
           ('r', 'rev', [],
            _('stop managing a revision (DEPRECATED)'), _('REV'))],
          _('hg qdelete [-k] [PATCH]...'))
+ at mqlocksavedirty
 def delete(ui, repo, *patches, **opts):
     """remove patches from queue
 
@@ -1935,6 +1953,7 @@
           ('g', 'git', None, _('use git extended diff format')),
           ('P', 'push', None, _('qpush after importing'))],
          _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
+ at mqlocksavedirty
 def qimport(ui, repo, *filename, **opts):
     """import a patch
 
@@ -1980,6 +1999,7 @@
         return q.push(repo, None)
     return 0
 
+ at mqlocksavedirty
 def qinit(ui, repo, create):
     """initialize a new queue repository
 
@@ -2181,6 +2201,7 @@
            _('add "Date: <DATE>" to patch'), _('DATE'))
           ] + commands.walkopts + commands.commitopts,
          _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
+ at mqlocksavedirty
 def new(ui, repo, patch, *args, **opts):
     """create a new patch
 
@@ -2235,6 +2256,7 @@
            _('add/update date field in patch with given date'), _('DATE'))
           ] + commands.walkopts + commands.commitopts,
          _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
+ at mqlocksavedirty
 def refresh(ui, repo, *pats, **opts):
     """update the current patch
 
@@ -2304,6 +2326,7 @@
           ('k', 'keep', None, _('keep folded patch files')),
          ] + commands.commitopts,
          _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
+ at mqlocksavedirty
 def fold(ui, repo, *files, **opts):
     """fold the named patches into the current patch
 
@@ -2374,6 +2397,7 @@
 @command("qgoto",
          [('f', 'force', None, _('overwrite any local changes'))],
          _('hg qgoto [OPTION]... PATCH'))
+ at mqlocksavedirty
 def goto(ui, repo, patch, **opts):
     '''push or pop patches until named patch is at top of stack
 
@@ -2391,6 +2415,7 @@
          [('l', 'list', None, _('list all patches and guards')),
           ('n', 'none', None, _('drop all guards'))],
          _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
+ at mqlocksavedirty
 def guard(ui, repo, *args, **opts):
     '''set or print guards for a patch
 
@@ -2512,6 +2537,7 @@
            _('merge queue name (DEPRECATED)'), _('NAME')),
           ('', 'move', None, _('reorder patch series and apply only the patch'))],
          _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
+ at mqlocksavedirty
 def push(ui, repo, patch=None, **opts):
     """push the next patch onto the stack
 
@@ -2544,6 +2570,7 @@
            _('queue name to pop (DEPRECATED)'), _('NAME')),
           ('f', 'force', None, _('forget any local changes to patched files'))],
          _('hg qpop [-a] [-f] [PATCH | INDEX]'))
+ at mqlocksavedirty
 def pop(ui, repo, patch=None, **opts):
     """pop the current patch off the stack
 
@@ -2566,6 +2593,7 @@
     return ret
 
 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
+ at mqlocksavedirty
 def rename(ui, repo, patch, name=None, **opts):
     """rename a patch
 
@@ -2627,6 +2655,7 @@
          [('d', 'delete', None, _('delete save entry')),
           ('u', 'update', None, _('update queue working directory'))],
          _('hg qrestore [-d] [-u] REV'))
+ at mqlocksavedirty
 def restore(ui, repo, rev, **opts):
     """restore the queue state saved by a revision (DEPRECATED)
 
@@ -2645,6 +2674,7 @@
           ('e', 'empty', None, _('clear queue status file')),
           ('f', 'force', None, _('force copy'))] + commands.commitopts,
          _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
+ at mqlocksavedirty
 def save(ui, repo, **opts):
     """save current queue state (DEPRECATED)
 
@@ -2690,6 +2720,7 @@
           ('', 'nobackup', None, _('no backups (DEPRECATED)')),
           ('k', 'keep', None, _("do not modify working copy during strip"))],
           _('hg strip [-k] [-f] [-n] REV...'))
+ at mqlocksavedirty
 def strip(ui, repo, *revs, **opts):
     """strip changesets and all their descendants from the repository
 
@@ -2779,6 +2810,7 @@
           ('', 'pop', None, _('pop to before first guarded applied patch')),
           ('', 'reapply', None, _('pop, then reapply patches'))],
          _('hg qselect [OPTION]... [GUARD]...'))
+ at mqlocksavedirty
 def select(ui, repo, *args, **opts):
     '''set or print guarded patches to push
 
@@ -2887,6 +2919,7 @@
 @command("qfinish",
          [('a', 'applied', None, _('finish all applied changesets'))],
          _('hg qfinish [-a] [REV]...'))
+ at mqlocksavedirty
 def finish(ui, repo, *revrange, **opts):
     """move applied patches into repository history
 
@@ -2932,6 +2965,7 @@
           ('', 'purge', False, _('delete queue, and remove patch dir')),
          ],
          _('[OPTION] [QUEUE]'))
+ at mqlocksavedirty
 def qqueue(ui, repo, name=None, **opts):
     '''manage multiple patch queues
 


More information about the Mercurial-devel mailing list