Merge with crew, fix most tests
authorMatt Mackall <mpm@selenic.com>
Wed, 09 Aug 2006 13:55:18 -0500
changeset 2830 49988d9f0758
parent 2815 4870f795f681 (current diff)
parent 2829 05316bb57d01 (diff)
child 2836 e78cad1f6b1f
child 2843 0b9ac7dfcf56
child 2995 0171a5432621
Merge with crew, fix most tests
hgext/fetch.py
hgext/mq.py
mercurial/hg.py
tests/test-merge5.out
tests/test-merge7.out
tests/test-up-local-change.out
tests/test-update-reverse.out
--- a/hgext/fetch.py	Tue Aug 08 10:56:48 2006 -0500
+++ b/hgext/fetch.py	Wed Aug 09 13:55:18 2006 -0500
@@ -24,29 +24,29 @@
         if modheads == 0:
             return 0
         if modheads == 1:
-            return hg.update(repo, repo.changelog.tip())
+            return hg.update(repo, repo.changelog.tip(), wlock=wlock)
         newheads = repo.heads(parent)
         newchildren = [n for n in repo.heads(parent) if n != parent]
         newparent = parent
         if newchildren:
-            hg.update(repo, newchildren[0])
             newparent = newchildren[0]
+            hg.update(repo, newparent, wlock=wlock)
         newheads = [n for n in repo.heads() if n != newparent]
         err = False
         if newheads:
             ui.status(_('merging with new head %d:%s\n') %
                       (repo.changelog.rev(newheads[0]), short(newheads[0])))
-            err = hg.merge(repo, newheads[0], remind=False)
+            err = hg.merge(repo, newheads[0], remind=False, wlock=wlock)
         if not err and len(newheads) > 1:
             ui.status(_('not merging with %d other new heads '
                         '(use "hg heads" and "hg merge" to merge them)') %
                       (len(newheads) - 1))
         if not err:
-            mod, add, rem = repo.status()[:3]
+            mod, add, rem = repo.status(wlock=wlock)[:3]
             message = (commands.logmessage(opts) or
                        (_('Automated merge with %s') % other.url()))
             n = repo.commit(mod + add + rem, message,
-                            opts['user'], opts['date'],
+                            opts['user'], opts['date'], lock=lock, wlock=wlock,
                             force_editor=opts.get('force_editor'))
             ui.status(_('new changeset %d:%s merges remote changes '
                         'with local\n') % (repo.changelog.rev(n),
@@ -55,13 +55,13 @@
         commands.setremoteconfig(ui, opts)
 
         other = hg.repository(ui, ui.expandpath(source))
-        ui.status(_('pulling from %s\n') % source)
+        ui.status(_('pulling from %s\n') % ui.expandpath(source))
         revs = None
         if opts['rev'] and not other.local():
             raise util.Abort(_("fetch -r doesn't work for remote repositories yet"))
         elif opts['rev']:
             revs = [other.lookup(rev) for rev in opts['rev']]
-        modheads = repo.pull(other, heads=revs)
+        modheads = repo.pull(other, heads=revs, lock=lock)
         return postincoming(other, modheads)
         
     parent, p2 = repo.dirstate.parents()
@@ -70,13 +70,19 @@
                            '(use "hg update" to check out tip)'))
     if p2 != nullid:
         raise util.Abort(_('outstanding uncommitted merge'))
-    mod, add, rem = repo.status()[:3]
-    if mod or add or rem:
-        raise util.Abort(_('outstanding uncommitted changes'))
-    if len(repo.heads()) > 1:
-        raise util.Abort(_('multiple heads in this repository '
-                           '(use "hg heads" and "hg merge" to merge them)'))
-    return pull()
+    wlock = repo.wlock()
+    lock = repo.lock()
+    try:
+        mod, add, rem = repo.status(wlock=wlock)[:3]
+        if mod or add or rem:
+            raise util.Abort(_('outstanding uncommitted changes'))
+        if len(repo.heads()) > 1:
+            raise util.Abort(_('multiple heads in this repository '
+                               '(use "hg heads" and "hg merge" to merge)'))
+        return pull()
+    finally:
+        lock.release()
+        wlock.release()
 
 cmdtable = {
     'fetch':
--- a/hgext/mq.py	Tue Aug 08 10:56:48 2006 -0500
+++ b/hgext/mq.py	Wed Aug 09 13:55:18 2006 -0500
@@ -35,14 +35,16 @@
 from mercurial.i18n import gettext as _
 from mercurial import ui, hg, revlog, commands, util
 
-versionstr = "0.45"
-
 commands.norepo += " qclone qversion"
 
-class StatusEntry:
+class statusentry:
     def __init__(self, rev, name=None):
         if not name:
-            self.rev, self.name = rev.split(':')
+            fields = rev.split(':')
+            if len(fields) == 2:
+                self.rev, self.name = fields
+            else:
+                self.rev, self.name = None, None
         else:
             self.rev, self.name = rev, name
 
@@ -52,10 +54,7 @@
 class queue:
     def __init__(self, ui, path, patchdir=None):
         self.basepath = path
-        if patchdir:
-            self.path = patchdir
-        else:
-            self.path = os.path.join(path, "patches")
+        self.path = patchdir or os.path.join(path, "patches")
         self.opener = util.opener(self.path)
         self.ui = ui
         self.applied = []
@@ -64,14 +63,20 @@
         self.series_dirty = 0
         self.series_path = "series"
         self.status_path = "status"
+        self.guards_path = "guards"
+        self.active_guards = None
+        self.guards_dirty = False
 
-        if os.path.exists(os.path.join(self.path, self.series_path)):
+        if os.path.exists(self.join(self.series_path)):
             self.full_series = self.opener(self.series_path).read().splitlines()
         self.parse_series()
 
-        if os.path.exists(os.path.join(self.path, self.status_path)):
-            self.applied = [StatusEntry(l)
-                            for l in self.opener(self.status_path).read().splitlines()]
+        if os.path.exists(self.join(self.status_path)):
+            lines = self.opener(self.status_path).read().splitlines()
+            self.applied = [statusentry(l) for l in lines]
+
+    def join(self, *p):
+        return os.path.join(self.path, *p)
 
     def find_series(self, patch):
         pre = re.compile("(\s*)([^#]+)")
@@ -86,12 +91,122 @@
             index += 1
         return None
 
+    guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
+
     def parse_series(self):
         self.series = []
+        self.series_guards = []
         for l in self.full_series:
-            s = l.split('#', 1)[0].strip()
-            if s:
-                self.series.append(s)
+            h = l.find('#')
+            if h == -1:
+                patch = l
+                comment = ''
+            elif h == 0:
+                continue
+            else:
+                patch = l[:h]
+                comment = l[h:]
+            patch = patch.strip()
+            if patch:
+                self.series.append(patch)
+                self.series_guards.append(self.guard_re.findall(comment))
+
+    def check_guard(self, guard):
+        bad_chars = '# \t\r\n\f'
+        first = guard[0]
+        for c in '-+':
+            if first == c:
+                return (_('guard %r starts with invalid character: %r') %
+                        (guard, c))
+        for c in bad_chars:
+            if c in guard:
+                return _('invalid character in guard %r: %r') % (guard, c)
+        
+    def set_active(self, guards):
+        for guard in guards:
+            bad = self.check_guard(guard)
+            if bad:
+                raise util.Abort(bad)
+        guards = dict.fromkeys(guards).keys()
+        guards.sort()
+        self.ui.debug('active guards: %s\n' % ' '.join(guards))
+        self.active_guards = guards
+        self.guards_dirty = True
+
+    def active(self):
+        if self.active_guards is None:
+            self.active_guards = []
+            try:
+                guards = self.opener(self.guards_path).read().split()
+            except IOError, err:
+                if err.errno != errno.ENOENT: raise
+                guards = []
+            for i, guard in enumerate(guards):
+                bad = self.check_guard(guard)
+                if bad:
+                    self.ui.warn('%s:%d: %s\n' %
+                                 (self.join(self.guards_path), i + 1, bad))
+                else:
+                    self.active_guards.append(guard)
+        return self.active_guards
+
+    def set_guards(self, idx, guards):
+        for g in guards:
+            if len(g) < 2:
+                raise util.Abort(_('guard %r too short') % g)
+            if g[0] not in '-+':
+                raise util.Abort(_('guard %r starts with invalid char') % g)
+            bad = self.check_guard(g[1:])
+            if bad:
+                raise util.Abort(bad)
+        drop = self.guard_re.sub('', self.full_series[idx])
+        self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
+        self.parse_series()
+        self.series_dirty = True
+        
+    def pushable(self, idx):
+        if isinstance(idx, str):
+            idx = self.series.index(idx)
+        patchguards = self.series_guards[idx]
+        if not patchguards:
+            return True, None
+        default = False
+        guards = self.active()
+        exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
+        if exactneg:
+            return False, exactneg[0]
+        pos = [g for g in patchguards if g[0] == '+']
+        nonpos = [g for g in pos if g[1:] not in guards]
+        if pos:
+            if not nonpos:
+                return True, ''
+            return False, nonpos
+        return True, ''
+
+    def explain_pushable(self, idx, all_patches=False):
+        write = all_patches and self.ui.write or self.ui.warn
+        if all_patches or self.ui.verbose:
+            if isinstance(idx, str):
+                idx = self.series.index(idx)
+            pushable, why = self.pushable(idx)
+            if all_patches and pushable:
+                if why is None:
+                    write(_('allowing %s - no guards in effect\n') %
+                          self.series[idx])
+                else:
+                    if not why:
+                        write(_('allowing %s - no matching negative guards\n') %
+                              self.series[idx])
+                    else:
+                        write(_('allowing %s - guarded by %r\n') %
+                              (self.series[idx], why))
+            if not pushable:
+                if why:
+                    write(_('skipping %s - guarded by %r\n') %
+                          (self.series[idx], ' '.join(why)))
+                else:
+                    write(_('skipping %s - no matching guards\n') %
+                          self.series[idx])
 
     def save_dirty(self):
         def write_list(items, path):
@@ -101,6 +216,7 @@
             fp.close()
         if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
         if self.series_dirty: write_list(self.full_series, self.series_path)
+        if self.guards_dirty: write_list(self.active_guards, self.guards_path)
 
     def readheaders(self, patch):
         def eatdiff(lines):
@@ -120,7 +236,7 @@
                 else:
                     break
 
-        pf = os.path.join(self.path, patch)
+        pf = self.join(patch)
         message = []
         comments = []
         user = None
@@ -243,7 +359,7 @@
             pname = ".hg.patches.merge.marker"
             n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
                             wlock=wlock)
-            self.applied.append(StatusEntry(revlog.hex(n), pname))
+            self.applied.append(statusentry(revlog.hex(n), pname))
             self.applied_dirty = 1
 
         head = self.qparents(repo)
@@ -253,7 +369,10 @@
             if not patch:
                 self.ui.warn("patch %s does not exist\n" % patch)
                 return (1, None)
-
+            pushable, reason = self.pushable(patch)
+            if not pushable:
+                self.explain_pushable(patch, all_patches=True)
+                continue
             info = mergeq.isapplied(patch)
             if not info:
                 self.ui.warn("patch %s is not applied\n" % patch)
@@ -261,7 +380,7 @@
             rev = revlog.bin(info[1])
             (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
             if head:
-                self.applied.append(StatusEntry(revlog.hex(head), patch))
+                self.applied.append(statusentry(revlog.hex(head), patch))
                 self.applied_dirty = 1
             if err:
                 return (err, head)
@@ -317,6 +436,10 @@
         tr = repo.transaction()
         n = None
         for patch in series:
+            pushable, reason = self.pushable(patch)
+            if not pushable:
+                self.explain_pushable(patch, all_patches=True)
+                continue
             self.ui.warn("applying %s\n" % patch)
             pf = os.path.join(patchdir, patch)
 
@@ -356,7 +479,7 @@
                 raise util.Abort(_("repo commit failed"))
 
             if update_status:
-                self.applied.append(StatusEntry(revlog.hex(n), patch))
+                self.applied.append(statusentry(revlog.hex(n), patch))
 
             if patcherr:
                 if not patchfound:
@@ -386,7 +509,7 @@
             if r:
                 r.remove([patch], True)
             else:
-                os.unlink(os.path.join(self.path, patch))
+                os.unlink(self.join(patch))
         i = self.find_series(patch)
         del self.full_series[i]
         self.parse_series()
@@ -405,7 +528,7 @@
         if c or a or d or r:
             raise util.Abort(_("local changes found, refresh first"))
     def new(self, repo, patch, msg=None, force=None):
-        if os.path.exists(os.path.join(self.path, patch)):
+        if os.path.exists(self.join(patch)):
             raise util.Abort(_('patch "%s" already exists') % patch)
         commitfiles = []
         (c, a, r, d, u) = repo.changes(None, None)
@@ -425,7 +548,7 @@
         if n == None:
             raise util.Abort(_("repo commit failed"))
         self.full_series[insert:insert] = [patch]
-        self.applied.append(StatusEntry(revlog.hex(n), patch))
+        self.applied.append(statusentry(revlog.hex(n), patch))
         self.parse_series()
         self.series_dirty = 1
         self.applied_dirty = 1
@@ -628,15 +751,14 @@
         if res and res == patch:
             return res
 
-        if not os.path.isfile(os.path.join(self.path, patch)):
+        if not os.path.isfile(self.join(patch)):
             try:
                 sno = int(patch)
             except(ValueError, OverflowError):
                 pass
             else:
                 if sno < len(self.series):
-                    patch = self.series[sno]
-                    return patch
+                    return self.series[sno]
             if not strict:
                 # return any partial match made above
                 if res:
@@ -900,7 +1022,7 @@
 
             self.strip(repo, top, update=False, backup='strip', wlock=wlock)
             n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
-            self.applied[-1] = StatusEntry(revlog.hex(n), patch)
+            self.applied[-1] = statusentry(revlog.hex(n), patch)
             self.applied_dirty = 1
         else:
             commands.dodiff(patchf, self.ui, repo, patchparent, None)
@@ -922,18 +1044,26 @@
             start = self.series_end()
         else:
             start = self.series.index(patch) + 1
-        return [(i, self.series[i]) for i in xrange(start, len(self.series))]
+        unapplied = []
+        for i in xrange(start, len(self.series)):
+            pushable, reason = self.pushable(i)
+            if pushable:
+                unapplied.append((i, self.series[i]))
+            self.explain_pushable(i)
+        return unapplied
 
     def qseries(self, repo, missing=None, summary=False):
-        start = self.series_end()
+        start = self.series_end(all_patches=True)
         if not missing:
             for i in range(len(self.series)):
                 patch = self.series[i]
                 if self.ui.verbose:
                     if i < start:
                         status = 'A'
+                    elif self.pushable(i)[0]:
+                        status = 'U'
                     else:
-                        status = 'U'
+                        status = 'G'
                     self.ui.write('%d %s ' % (i, status))
                 if summary:
                     msg = self.readheaders(patch)[0]
@@ -958,12 +1088,11 @@
                 self.ui.write("%s\n" % x)
 
     def issaveline(self, l):
-        name = l.split(':')[1]
-        if name == '.hg.patches.save.line':
+        if l.name == '.hg.patches.save.line':
             return True
 
     def qrepo(self, create=False):
-        if create or os.path.isdir(os.path.join(self.path, ".hg")):
+        if create or os.path.isdir(self.join(".hg")):
             return hg.repository(self.ui, path=self.path, create=create)
 
     def restore(self, repo, rev, delete=None, qupdate=None):
@@ -984,7 +1113,7 @@
                 qpp = [ hg.bin(x) for x in l ]
             elif datastart != None:
                 l = lines[i].rstrip()
-                se = StatusEntry(l)
+                se = statusentry(l)
                 file_ = se.name
                 if se.rev:
                     applied.append(se)
@@ -1039,13 +1168,13 @@
             pp = r.dirstate.parents()
             msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
         msg += "\n\nPatch Data:\n"
-        text = msg + "\n".join(str(self.applied)) + '\n' + (ar and "\n".join(ar)
-                                                       + '\n' or "")
+        text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
+                   "\n".join(ar) + '\n' or "")
         n = repo.commit(None, text, user=None, force=1)
         if not n:
             self.ui.warn("repo commit failed\n")
             return 1
-        self.applied.append(StatusEntry(revlog.hex(n),'.hg.patches.save.line'))
+        self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
         self.applied_dirty = 1
 
     def full_series_end(self):
@@ -1057,16 +1186,27 @@
             return end + 1
         return 0
 
-    def series_end(self):
+    def series_end(self, all_patches=False):
         end = 0
+        def next(start):
+            if all_patches:
+                return start
+            i = start
+            while i < len(self.series):
+                p, reason = self.pushable(i)
+                if p:
+                    break
+                self.explain_pushable(i)
+                i += 1
+            return i
         if len(self.applied) > 0:
             p = self.applied[-1].name
             try:
                 end = self.series.index(p)
             except ValueError:
                 return 0
-            return end + 1
-        return end
+            return next(end + 1)
+        return next(end)
 
     def qapplied(self, repo, patch=None):
         if patch and patch not in self.series:
@@ -1123,7 +1263,7 @@
             if existing:
                 if not patch:
                     patch = filename
-                if not os.path.isfile(os.path.join(self.path, patch)):
+                if not os.path.isfile(self.join(patch)):
                     raise util.Abort(_("patch %s does not exist") % patch)
             else:
                 try:
@@ -1132,7 +1272,7 @@
                     raise util.Abort(_("unable to read %s") % patch)
                 if not patch:
                     patch = os.path.split(filename)[1]
-                if not force and os.path.exists(os.path.join(self.path, patch)):
+                if not force and os.path.exists(self.join(patch)):
                     raise util.Abort(_('patch "%s" already exists') % patch)
                 patchf = self.opener(patch, "w")
                 patchf.write(text)
@@ -1347,7 +1487,7 @@
     for patch in patches:
         if not message:
             messages.append(q.readheaders(patch)[0])
-        pf = os.path.join(q.path, patch)
+        pf = q.join(patch)
         (patchsuccess, files, fuzz) = q.patch(repo, pf)
         if not patchsuccess:
             raise util.Abort(_('Error folding patch %s') % patch)
@@ -1369,6 +1509,51 @@
 
     q.save_dirty()
 
+def guard(ui, repo, *args, **opts):
+    '''set or print guards for a patch
+
+    guards control whether a patch can be pushed.  a patch with no
+    guards is aways pushed.  a patch with posative guard ("+foo") is
+    pushed only if qselect command enables guard "foo".  a patch with
+    nagative guard ("-foo") is never pushed if qselect command enables
+    guard "foo".
+
+    with no arguments, default is to print current active guards.
+    with arguments, set active guards for patch.
+
+    to set nagative guard "-foo" on topmost patch ("--" is needed so
+    hg will not interpret "-foo" as argument):
+      hg qguard -- -foo
+
+    to set guards on other patch:
+      hg qguard other.patch +2.6.17 -stable    
+    '''
+    def status(idx):
+        guards = q.series_guards[idx] or ['unguarded']
+        ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
+    q = repo.mq
+    patch = None
+    args = list(args)
+    if opts['list']:
+        if args or opts['none']:
+            raise util.Abort(_('cannot mix -l/--list with options or arguments'))
+        for i in xrange(len(q.series)):
+            status(i)
+        return
+    if not args or args[0][0:1] in '-+':
+        if not q.applied:
+            raise util.Abort(_('no patches applied'))
+        patch = q.applied[-1].name
+    if patch is None and args[0][0:1] not in '-+':
+        patch = args.pop(0)
+    if patch is None:
+        raise util.Abort(_('no patch to work with'))
+    if args or opts['none']:
+        q.set_guards(q.find_series(patch), args)
+        q.save_dirty()
+    else:
+        status(q.series.index(q.lookup(patch)))
+
 def header(ui, repo, patch=None):
     """Print the header of the topmost or specified patch"""
     q = repo.mq
@@ -1458,7 +1643,7 @@
     if name in q.series:
         raise util.Abort(_('A patch named %s already exists in the series file') % name)
 
-    absdest = os.path.join(q.path, name)
+    absdest = q.join(name)
     if os.path.exists(absdest):
         raise util.Abort(_('%s already exists') % absdest)
     
@@ -1479,10 +1664,10 @@
 
     info = q.isapplied(patch)
     if info:
-        q.applied[info[0]] = StatusEntry(info[1], name)
+        q.applied[info[0]] = statusentry(info[1], name)
     q.applied_dirty = 1
 
-    util.rename(os.path.join(q.path, patch), absdest)
+    util.rename(q.join(patch), absdest)
     r = q.qrepo()
     if r:
         wlock = r.wlock()
@@ -1527,7 +1712,7 @@
         util.copyfiles(path, newpath)
     if opts['empty']:
         try:
-            os.unlink(os.path.join(q.path, q.status_path))
+            os.unlink(q.join(q.status_path))
         except:
             pass
     return 0
@@ -1543,18 +1728,76 @@
     repo.mq.strip(repo, rev, backup=backup)
     return 0
 
-def version(ui, q=None):
-    """print the version number of the mq extension"""
-    ui.write("mq version %s\n" % versionstr)
-    return 0
+def select(ui, repo, *args, **opts):
+    '''set or print guarded patches to push
+
+    use qguard command to set or print guards on patch.  then use
+    qselect to tell mq which guards to use.  example:
+
+        qguard foo.patch -stable    (nagative guard)
+        qguard bar.patch +stable    (posative guard)
+        qselect stable
+
+    this sets "stable" guard.  mq will skip foo.patch (because it has
+    nagative match) but push bar.patch (because it has posative
+    match).  patch is pushed only if all posative guards match and no
+    nagative guards match.
+
+    with no arguments, default is to print current active guards.
+    with arguments, set active guards as given.
+    
+    use -n/--none to deactivate guards (no other arguments needed).
+    when no guards active, patches with posative guards are skipped,
+    patches with nagative guards are pushed.
+
+    use -s/--series to print list of all guards in series file (no
+    other arguments needed).  use -v for more information.'''
+
+    q = repo.mq
+    guards = q.active()
+    if args or opts['none']:
+        q.set_active(args)
+        q.save_dirty()
+        if not args:
+            ui.status(_('guards deactivated\n'))
+        if q.series:
+            ui.status(_('%d of %d unapplied patches active\n') %
+                      (len(q.unapplied(repo)), len(q.series)))
+    elif opts['series']:
+        guards = {}
+        noguards = 0
+        for gs in q.series_guards:
+            if not gs:
+                noguards += 1
+            for g in gs:
+                guards.setdefault(g, 0)
+                guards[g] += 1
+        if ui.verbose:
+            guards['NONE'] = noguards
+        guards = guards.items()
+        guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
+        if guards:
+            ui.note(_('guards in series file:\n'))
+            for guard, count in guards:
+                ui.note('%2d  ' % count)
+                ui.write(guard, '\n')
+        else:
+            ui.note(_('no guards in series file\n'))
+    else:
+        if guards:
+            ui.note(_('active guards:\n'))
+            for g in guards:
+                ui.write(g, '\n')
+        else:
+            ui.write(_('no active guards\n'))
 
 def reposetup(ui, repo):
-    class MqRepo(repo.__class__):
+    class mqrepo(repo.__class__):
         def tags(self):
             if self.tagscache:
                 return self.tagscache
 
-            tagscache = super(MqRepo, self).tags()
+            tagscache = super(mqrepo, self).tags()
 
             q = self.mq
             if not q.applied:
@@ -1571,7 +1814,7 @@
 
             return tagscache
 
-    repo.__class__ = MqRepo
+    repo.__class__ = mqrepo
     repo.mq = queue(ui, repo.join(""))
 
 cmdtable = {
@@ -1602,6 +1845,9 @@
           ('m', 'message', '', _('set patch header to <text>')),
           ('l', 'logfile', '', _('set patch header to contents of <file>'))],
          'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
+    'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
+                       ('n', 'none', None, _('drop all guards'))],
+               'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
     'qheader': (header, [],
                 _('hg qheader [PATCH]')),
     "^qimport":
@@ -1659,6 +1905,10 @@
           ('e', 'empty', None, 'clear queue status file'),
           ('f', 'force', None, 'force copy')],
          'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
+    "qselect": (select,
+                [('n', 'none', None, _('disable all guards')),
+                 ('s', 'series', None, _('list all guards in series file'))],
+                'hg qselect [GUARDS]'),
     "qseries":
         (series,
          [('m', 'missing', None, 'print patches not in series'),
@@ -1672,6 +1922,5 @@
          'hg strip [-f] [-b] [-n] REV'),
     "qtop": (top, [], 'hg qtop'),
     "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
-    "qversion": (version, [], 'hg qversion')
 }
 
--- a/mercurial/localrepo.py	Tue Aug 08 10:56:48 2006 -0500
+++ b/mercurial/localrepo.py	Wed Aug 09 13:55:18 2006 -0500
@@ -1177,22 +1177,29 @@
         else:
             return subset
 
-    def pull(self, remote, heads=None, force=False):
-        l = self.lock()
+    def pull(self, remote, heads=None, force=False, lock=None):
+        mylock = False
+        if not lock:
+            lock = self.lock()
+            mylock = True
 
-        fetch = self.findincoming(remote, force=force)
-        if fetch == [nullid]:
-            self.ui.status(_("requesting all changes\n"))
+        try:
+            fetch = self.findincoming(remote, force=force)
+            if fetch == [nullid]:
+                self.ui.status(_("requesting all changes\n"))
 
-        if not fetch:
-            self.ui.status(_("no changes found\n"))
-            return 0
+            if not fetch:
+                self.ui.status(_("no changes found\n"))
+                return 0
 
-        if heads is None:
-            cg = remote.changegroup(fetch, 'pull')
-        else:
-            cg = remote.changegroupsubset(fetch, heads, 'pull')
-        return self.addchangegroup(cg, 'pull', remote.url())
+            if heads is None:
+                cg = remote.changegroup(fetch, 'pull')
+            else:
+                cg = remote.changegroupsubset(fetch, heads, 'pull')
+            return self.addchangegroup(cg, 'pull', remote.url())
+        finally:
+            if mylock:
+                lock.release()
 
     def push(self, remote, force=False, revs=None):
         # there are two ways to push to remote repo:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fetch	Wed Aug 09 13:55:18 2006 -0500
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+HGRCPATH=$HGTMP/.hgrc; export HGRCPATH
+echo "[extensions]" >> $HGTMP/.hgrc
+echo "fetch=" >> $HGTMP/.hgrc
+
+hg init a
+echo a > a/a
+hg --cwd a commit -d '1 0' -Ama
+
+hg clone a b
+hg clone a c
+
+echo b > a/b
+hg --cwd a commit -d '2 0' -Amb
+hg --cwd a parents -q
+
+echo % should pull one change
+hg --cwd b fetch ../a
+hg --cwd b parents -q
+
+echo c > c/c
+hg --cwd c commit -d '3 0' -Amc
+hg --cwd c fetch -d '4 0' -m 'automated merge' ../a
+ls c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fetch.out	Wed Aug 09 13:55:18 2006 -0500
@@ -0,0 +1,27 @@
+adding a
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+adding b
+1:97d72e5f12c7
+% should pull one change
+pulling from ../a
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+1:97d72e5f12c7
+adding c
+pulling from ../a
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files (+1 heads)
+merging with new head 2:97d72e5f12c7
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+new changeset 3:cd3a41621cf0 merges remote changes with local
+a
+b
+c
--- a/tests/test-merge5.out	Tue Aug 08 10:56:48 2006 -0500
+++ b/tests/test-merge5.out	Wed Aug 09 13:55:18 2006 -0500
@@ -1,6 +1,3 @@
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 removing b
-this update spans a branch affecting the following files:
- b
-aborting update spanning branches!
-(use 'hg merge' to merge across branches or 'hg update -C' to lose changes)
+abort: update spans branches, use 'hg merge' or 'hg update -C' to lose changes
--- a/tests/test-merge7.out	Tue Aug 08 10:56:48 2006 -0500
+++ b/tests/test-merge7.out	Wed Aug 09 13:55:18 2006 -0500
@@ -22,7 +22,7 @@
 (run 'hg heads' to see heads, 'hg merge' to merge)
 merge: warning: conflicts during merge
 resolving manifests
- force False allow True moddirstate True linear False
+ overwrite None branchmerge True partial False linear False
  ancestor 055d847dd401 local 2eded9ab0a5c remote 84cf5750dd20
  test.txt versions differ, resolve
 merging test.txt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-mq-guards	Wed Aug 09 13:55:18 2006 -0500
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+HGRCPATH=$HGTMP/.hgrc; export HGRCPATH
+echo "[extensions]" >> $HGTMP/.hgrc
+echo "mq=" >> $HGTMP/.hgrc
+
+hg init
+hg qinit
+
+echo x > x
+hg ci -Ama
+
+hg qnew a.patch
+echo a > a
+hg add a
+hg qrefresh
+
+hg qnew b.patch
+echo b > b
+hg add b
+hg qrefresh
+
+hg qnew c.patch
+echo c > c
+hg add c
+hg qrefresh
+
+hg qpop -a
+
+echo % should fail
+hg qguard +fail
+
+hg qpush
+echo % should guard a.patch
+hg qguard +a
+echo % should print +a
+hg qguard
+hg qpop
+
+hg qguard a.patch
+echo % should push b.patch
+hg qpush
+
+hg qpop
+hg qselect a
+echo % should push a.patch
+hg qpush
+
+hg qguard c.patch -a
+echo % should print -a
+hg qguard c.patch
+
+echo % should skip c.patch
+hg qpush -a
+
+hg qguard -n c.patch
+echo % should push c.patch
+hg qpush -a
+
+hg qpop -a
+hg qselect -n
+echo % should push all
+hg qpush -a
+
+hg qpop -a
+hg qguard a.patch +1 +2
+hg qselect 1
+echo % should push b.patch
+hg qpush
+hg qpop -a
+
+hg qselect 2
+hg qpush
+hg qpop -a
+
+hg qselect 1 2
+echo % should push a.patch
+hg qpush
+hg qpop -a
+
+hg qguard a.patch +1 +2 -3
+hg qselect 1 2 3
+echo % should push b.patch
+hg qpush
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-mq-guards.out	Wed Aug 09 13:55:18 2006 -0500
@@ -0,0 +1,54 @@
+adding x
+Patch queue now empty
+% should fail
+abort: no patches applied
+applying a.patch
+Now at: a.patch
+% should guard a.patch
+% should print +a
+a.patch: +a
+Patch queue now empty
+a.patch: +a
+% should push b.patch
+applying b.patch
+Now at: b.patch
+Patch queue now empty
+3 of 3 unapplied patches active
+% should push a.patch
+applying a.patch
+Now at: a.patch
+% should print -a
+c.patch: -a
+% should skip c.patch
+applying b.patch
+skipping c.patch - guarded by '- a'
+Now at: b.patch
+% should push c.patch
+applying c.patch
+Now at: c.patch
+Patch queue now empty
+guards deactivated
+2 of 3 unapplied patches active
+% should push all
+applying b.patch
+applying c.patch
+Now at: c.patch
+Patch queue now empty
+2 of 3 unapplied patches active
+% should push b.patch
+applying b.patch
+Now at: b.patch
+Patch queue now empty
+2 of 3 unapplied patches active
+applying b.patch
+Now at: b.patch
+Patch queue now empty
+3 of 3 unapplied patches active
+% should push a.patch
+applying a.patch
+Now at: a.patch
+Patch queue now empty
+2 of 3 unapplied patches active
+% should push b.patch
+applying b.patch
+Now at: b.patch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-mq-qsave	Wed Aug 09 13:55:18 2006 -0500
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+HGRCPATH=$HGTMP/.hgrc; export HGRCPATH
+echo "[extensions]" >> $HGTMP/.hgrc
+echo "mq=" >> $HGTMP/.hgrc
+
+hg init a
+cd a
+
+echo 'base' > base
+hg ci -Ambase -d '1 0'
+
+hg qnew -mmqbase mqbase
+
+hg qsave
+hg qrestore 2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-mq-qsave.out	Wed Aug 09 13:55:18 2006 -0500
@@ -0,0 +1,2 @@
+adding base
+restoring status: hg patches saved state
--- a/tests/test-mq.out	Tue Aug 08 10:56:48 2006 -0500
+++ b/tests/test-mq.out	Wed Aug 09 13:55:18 2006 -0500
@@ -30,6 +30,7 @@
  qdelete      remove a patch from the series file
  qdiff        diff of the current patch
  qfold        fold the named patches into the current patch
+ qguard       set or print guards for a patch
  qheader      Print the header of the topmost or specified patch
  qimport      import a patch
  qinit        init a new queue repository
@@ -42,10 +43,10 @@
  qrename      rename a patch
  qrestore     restore the queue state saved by a rev
  qsave        save current queue state
+ qselect      set or print guarded patches to push
  qseries      print the entire series file
  qtop         print the name of the current patch
  qunapplied   print the patches not yet applied
- qversion     print the version number of the mq extension
  strip        strip a revision and all later revs on the same branch
 adding a
 adding b/z
--- a/tests/test-up-local-change.out	Tue Aug 08 10:56:48 2006 -0500
+++ b/tests/test-up-local-change.out	Wed Aug 09 13:55:18 2006 -0500
@@ -17,7 +17,7 @@
 summary:     1
 
 resolving manifests
- force False allow False moddirstate True linear True
+ overwrite False branchmerge False partial False linear True
  ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
  a versions differ, resolve
 remote created b
@@ -33,7 +33,7 @@
 summary:     2
 
 resolving manifests
- force False allow False moddirstate True linear True
+ overwrite False branchmerge False partial False linear True
  ancestor a0c8bcbbb45c local 1165e8bd193e remote a0c8bcbbb45c
 remote deleted b
 removing b
@@ -51,7 +51,7 @@
 summary:     1
 
 resolving manifests
- force False allow False moddirstate True linear True
+ overwrite False branchmerge False partial False linear True
  ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
  a versions differ, resolve
 remote created b
@@ -98,21 +98,12 @@
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     2
 
-resolving manifests
- force False allow False moddirstate True linear False
- ancestor a0c8bcbbb45c local 1165e8bd193e remote 4096f2872392
- a versions differ, resolve
- b versions differ, resolve
-this update spans a branch affecting the following files:
- a (resolve)
- b (resolve)
-aborting update spanning branches!
-(use 'hg merge' to merge across branches or 'hg update -C' to lose changes)
+abort: update spans branches, use 'hg merge' or 'hg update -C' to lose changes
 failed
 abort: outstanding uncommitted changes
 failed
 resolving manifests
- force False allow True moddirstate True linear False
+ overwrite False branchmerge True partial False linear False
  ancestor a0c8bcbbb45c local 1165e8bd193e remote 4096f2872392
  a versions differ, resolve
  b versions differ, resolve
--- a/tests/test-update-reverse.out	Tue Aug 08 10:56:48 2006 -0500
+++ b/tests/test-update-reverse.out	Wed Aug 09 13:55:18 2006 -0500
@@ -40,7 +40,7 @@
 side1
 side2
 resolving manifests
- force True allow False moddirstate True linear False
+ overwrite True branchmerge False partial False linear False
  ancestor 8515d4bfda76 local 1c0f48f8ece6 remote 0594b9004bae
 remote deleted side2, clobbering
 remote deleted side1, clobbering