changeset 20368:cc00cd6c51c2

merge with stable
author Matt Mackall <mpm@selenic.com>
date Wed, 05 Feb 2014 18:09:07 -0600
parents 2ac278aab2b4 (diff) 6863d42eb59a (current diff)
children 9c6b86dd2ed2
files
diffstat 11 files changed, 485 insertions(+), 366 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Feb 05 17:23:35 2014 -0600
+++ b/Makefile	Wed Feb 05 18:09:07 2014 -0600
@@ -102,7 +102,7 @@
 
 update-pot: i18n/hg.pot
 
-i18n/hg.pot: $(PYFILES) $(DOCFILES)
+i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
 	$(PYTHON) i18n/hggettext mercurial/commands.py \
 	  hgext/*.py hgext/*/__init__.py \
 	  mercurial/fileset.py mercurial/revset.py \
--- a/i18n/de.po	Wed Feb 05 17:23:35 2014 -0600
+++ b/i18n/de.po	Wed Feb 05 18:09:07 2014 -0600
@@ -20,7 +20,7 @@
 msgstr ""
 "Project-Id-Version: Mercurial\n"
 "Report-Msgid-Bugs-To: <mercurial-devel@selenic.com>\n"
-"POT-Creation-Date: 2014-01-25 17:51+0100\n"
+"POT-Creation-Date: 2014-01-29 16:47+0100\n"
 "PO-Revision-Date: 2013-09-30 20:52+0100\n"
 "Last-Translator: Simon Heimberg <simohe@besonet.ch>\n"
 "Language-Team: \n"
@@ -2928,6 +2928,7 @@
 "  [repository]\n"
 "  native = LF"
 
+#. do not translate: .. note::
 msgid ".. note::"
 msgstr ""
 
@@ -5029,6 +5030,7 @@
 "    Siehe Hilfe zu 'paths' zu Pfad-Kurznamen und 'urls' für erlaubte\n"
 "    Formate für die Quellangabe."
 
+#. do not translate: .. container::
 msgid "    .. container:: verbose"
 msgstr ""
 
@@ -6548,6 +6550,7 @@
 "    Ohne Argumente werden die aktuell aktiven Wächter ausgegeben.\n"
 "    Mit einem Argument wird der aktuelle Wächter gesetzt."
 
+#. do not translate: .. note::
 msgid "    .. note::"
 msgstr ""
 
@@ -15694,6 +15697,7 @@
 "    order until one or more configuration files are detected."
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 ".. note:: The registry key ``HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node"
 "\\Mercurial``\n"
@@ -15873,6 +15877,7 @@
 msgid "    stable5 = latest -b stable"
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 ".. note:: It is possible to create aliases with the same names as\n"
 "   existing commands, which will then override the original\n"
@@ -15918,6 +15923,7 @@
 "echo foo`` call above, ``$HG_ARGS`` would expand to ``echo foo``."
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 ".. note:: Some global configuration options such as ``-R`` are\n"
 "   processed before shell aliases and will thus not be passed to\n"
@@ -16101,6 +16107,7 @@
 "the command."
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 ".. note:: The tempfile mechanism is recommended for Windows systems,\n"
 "   where the standard shell I/O redirection operators often have\n"
@@ -16572,6 +16579,7 @@
 "  update failed (e.g. because conflicts not resolved), ``$HG_ERROR=1``."
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 ".. note:: It is generally better to use standard hooks rather than the\n"
 "   generic pre- and post- command hooks as they are guaranteed to be\n"
@@ -16580,6 +16588,7 @@
 "   generate a commit (e.g. tag) and not just the commit command."
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 ".. note:: Environment variables with empty values may not be passed to\n"
 "   hooks on platforms such as Windows. As an example, ``$HG_PARENT2``\n"
@@ -18967,6 +18976,7 @@
 ":Manual group:   Mercurial Manual"
 msgstr ""
 
+#. do not translate: .. contents::
 msgid ""
 ".. contents::\n"
 "   :backlinks: top\n"
@@ -19017,6 +19027,7 @@
 "    repository."
 msgstr ""
 
+#. do not translate: .. include::
 msgid ".. include:: hg.1.gendoc.txt"
 msgstr ""
 
@@ -19121,6 +19132,7 @@
 "Public License version 2 or any later version."
 msgstr ""
 
+#. do not translate: .. include::
 msgid ".. include:: common.txt\n"
 msgstr ""
 
@@ -19143,6 +19155,7 @@
 ":Manual group:   Mercurial Manual"
 msgstr ""
 
+#. do not translate: .. include::
 msgid ".. include:: hgignore.5.gendoc.txt"
 msgstr ""
 
@@ -19170,6 +19183,7 @@
 "Public License version 2 or any later version."
 msgstr ""
 
+#. do not translate: .. include::
 msgid ".. include:: common.txt"
 msgstr ""
 
@@ -19281,6 +19295,7 @@
 "regexp pattern, start it with ``^``."
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 ".. note::\n"
 "  Patterns specified in other than ``.hgignore`` are always rooted.\n"
@@ -19333,6 +19348,7 @@
 ":Manual group:   Mercurial Manual"
 msgstr ""
 
+#. do not translate: .. contents::
 msgid ""
 ".. contents::\n"
 "   :backlinks: top\n"
@@ -19348,6 +19364,7 @@
 "Beschreibung\n"
 "============"
 
+#. do not translate: .. include::
 msgid ".. include:: hgrc.5.gendoc.txt"
 msgstr ""
 
@@ -19564,6 +19581,7 @@
 msgid "8. The merge of the file fails and must be resolved before commit."
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 ".. note::\n"
 "   After selecting a merge program, Mercurial will by default attempt\n"
@@ -19633,6 +19651,7 @@
 msgid "Alternate pattern notations must be specified explicitly."
 msgstr "Andere Schreibweisen von Mustern müssen explizit angegeben werden."
 
+#. do not translate: .. note::
 msgid ""
 ".. note::\n"
 "  Patterns specified in ``.hgignore`` are not rooted.\n"
@@ -19804,6 +19823,7 @@
 msgid " - secret changesets are neither pushed, pulled, or cloned"
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 ".. note::\n"
 "  Pulling a draft changeset from a publishing server does not mark it\n"
@@ -19823,12 +19843,14 @@
 "  [phases]\n"
 "  publish = False"
 
+#. do not translate: .. note::
 msgid ""
 ".. note::\n"
 "  Servers running older versions of Mercurial are treated as\n"
 "  publishing."
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 ".. note::\n"
 "   Changesets in secret phase are not exchanged with the server. This\n"
@@ -20216,6 +20238,7 @@
 "   repositories states when committing in the parent repository."
 msgstr ""
 
+#. do not translate: .. note::
 msgid ""
 "   .. note::\n"
 "      The ``.hgsubstate`` file should not be edited manually."
--- a/i18n/posplit	Wed Feb 05 17:23:35 2014 -0600
+++ b/i18n/posplit	Wed Feb 05 18:09:07 2014 -0600
@@ -5,6 +5,7 @@
 # license: MIT/X11/Expat
 #
 
+import re
 import sys
 import polib
 
@@ -30,6 +31,7 @@
     cache = {}
     entries = po[:]
     po[:] = []
+    findd = re.compile(r' *\.\. (\w+)::') # for finding directives
     for entry in entries:
         msgids = entry.msgid.split(u'\n\n')
         if entry.msgstr:
@@ -49,8 +51,27 @@
 
         delta = 0
         for msgid, msgstr in zip(msgids, msgstrs):
-            if msgid:
+            if msgid and msgid != '::':
                 newentry = mkentry(entry, delta, msgid, msgstr)
+                mdirective = findd.match(msgid)
+                if mdirective:
+                    if not msgid[mdirective.end():].rstrip():
+                        # only directive, nothing to translate here
+                        continue
+                    directive = mdirective.group(1)
+                    if directive in ('container', 'include'):
+                        if msgid.rstrip('\n').count('\n') == 0:
+                            # only rst syntax, nothing to translate
+                            continue
+                        else:
+                            # lines following directly, unexpected
+                            print 'Warning: text follows line with directive' \
+                                  ' %s' % directive
+                    comment = 'do not translate: .. %s::' % directive
+                    if not newentry.comment:
+                        newentry.comment = comment
+                    elif comment not in newentry.comment:
+                        newentry.comment += '\n' + comment
                 addentry(po, newentry, cache)
             delta += 2 + msgid.count('\n')
     po.save()
--- a/mercurial/bookmarks.py	Wed Feb 05 17:23:35 2014 -0600
+++ b/mercurial/bookmarks.py	Wed Feb 05 18:09:07 2014 -0600
@@ -363,22 +363,6 @@
             writer(msg)
         localmarks.write()
 
-def updateremote(ui, repo, remote, revs):
-    ui.debug("checking for updated bookmarks\n")
-    revnums = map(repo.changelog.rev, revs or [])
-    ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
-    (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
-     ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
-                 srchex=hex)
-
-    for b, scid, dcid in advsrc:
-        if ancestors and repo[scid].rev() not in ancestors:
-            continue
-        if remote.pushkey('bookmarks', b, dcid, scid):
-            ui.status(_("updating bookmark %s\n") % b)
-        else:
-            ui.warn(_('updating bookmark %s failed!\n') % b)
-
 def pushtoremote(ui, repo, remote, targets):
     (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
      ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
--- a/mercurial/cmdutil.py	Wed Feb 05 17:23:35 2014 -0600
+++ b/mercurial/cmdutil.py	Wed Feb 05 18:09:07 2014 -0600
@@ -1132,7 +1132,7 @@
     elif follow:
         revs = repo.revs('reverse(:.)')
     else:
-        revs = list(repo)
+        revs = revset.baseset(repo)
         revs.reverse()
     if not revs:
         return []
@@ -1140,6 +1140,7 @@
     slowpath = match.anypats() or (match.files() and opts.get('removed'))
     fncache = {}
     change = repo.changectx
+    revs = revset.baseset(revs)
 
     # First step is to fill wanted, the set of revisions that we want to yield.
     # When it does not induce extra cost, we also fill fncache for revisions in
@@ -1471,10 +1472,11 @@
         if follow and len(repo) > 0:
             revs = repo.revs('reverse(:.)')
         else:
-            revs = list(repo.changelog)
+            revs = revset.baseset(repo.changelog)
             revs.reverse()
     if not revs:
         return [], None, None
+    revs = revset.baseset(revs)
     expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
     if possiblyunsorted:
         revs.sort(reverse=True)
--- a/mercurial/commands.py	Wed Feb 05 17:23:35 2014 -0600
+++ b/mercurial/commands.py	Wed Feb 05 18:09:07 2014 -0600
@@ -2558,7 +2558,7 @@
         if newtree != tree:
             ui.note(revset.prettyformat(newtree), "\n")
     func = revset.match(ui, expr)
-    for c in func(repo, range(len(repo))):
+    for c in func(repo, revset.baseset(range(len(repo)))):
         ui.write("%s\n" % c)
 
 @command('debugsetparents', [], _('REV1 [REV2]'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/exchange.py	Wed Feb 05 18:09:07 2014 -0600
@@ -0,0 +1,276 @@
+# exchange.py - utily to exchange data between repo.
+#
+# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from i18n import _
+from node import hex
+import errno
+import util, scmutil, changegroup
+import discovery, phases, obsolete, bookmarks
+
+
+class pushoperation(object):
+    """A object that represent a single push operation
+
+    It purpose is to carry push related state and very common operation.
+
+    A new should be created at the begining of each push and discarded
+    afterward.
+    """
+
+    def __init__(self, repo, remote, force=False, revs=None, newbranch=False):
+        # repo we push from
+        self.repo = repo
+        self.ui = repo.ui
+        # repo we push to
+        self.remote = remote
+        # force option provided
+        self.force = force
+        # revs to be pushed (None is "all")
+        self.revs = revs
+        # allow push of new branch
+        self.newbranch = newbranch
+
+def push(repo, remote, force=False, revs=None, newbranch=False):
+    '''Push outgoing changesets (limited by revs) from a local
+    repository to remote. Return an integer:
+      - None means nothing to push
+      - 0 means HTTP error
+      - 1 means we pushed and remote head count is unchanged *or*
+        we have outgoing changesets but refused to push
+      - other values as described by addchangegroup()
+    '''
+    pushop = pushoperation(repo, remote, force, revs, newbranch)
+    if pushop.remote.local():
+        missing = (set(pushop.repo.requirements)
+                   - pushop.remote.local().supported)
+        if missing:
+            msg = _("required features are not"
+                    " supported in the destination:"
+                    " %s") % (', '.join(sorted(missing)))
+            raise util.Abort(msg)
+
+    # there are two ways to push to remote repo:
+    #
+    # addchangegroup assumes local user can lock remote
+    # repo (local filesystem, old ssh servers).
+    #
+    # unbundle assumes local user cannot lock remote repo (new ssh
+    # servers, http servers).
+
+    if not pushop.remote.canpush():
+        raise util.Abort(_("destination does not support push"))
+    unfi = pushop.repo.unfiltered()
+    def localphasemove(nodes, phase=phases.public):
+        """move <nodes> to <phase> in the local source repo"""
+        if locallock is not None:
+            phases.advanceboundary(pushop.repo, phase, nodes)
+        else:
+            # repo is not locked, do not change any phases!
+            # Informs the user that phases should have been moved when
+            # applicable.
+            actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
+            phasestr = phases.phasenames[phase]
+            if actualmoves:
+                pushop.ui.status(_('cannot lock source repo, skipping '
+                                   'local %s phase update\n') % phasestr)
+    # get local lock as we might write phase data
+    locallock = None
+    try:
+        locallock = pushop.repo.lock()
+    except IOError, err:
+        if err.errno != errno.EACCES:
+            raise
+        # source repo cannot be locked.
+        # We do not abort the push, but just disable the local phase
+        # synchronisation.
+        msg = 'cannot lock source repository: %s\n' % err
+        pushop.ui.debug(msg)
+    try:
+        pushop.repo.checkpush(pushop.force, pushop.revs)
+        lock = None
+        unbundle = pushop.remote.capable('unbundle')
+        if not unbundle:
+            lock = pushop.remote.lock()
+        try:
+            # discovery
+            fci = discovery.findcommonincoming
+            commoninc = fci(unfi, pushop.remote, force=pushop.force)
+            common, inc, remoteheads = commoninc
+            fco = discovery.findcommonoutgoing
+            outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
+                           commoninc=commoninc, force=pushop.force)
+
+
+            if not outgoing.missing:
+                # nothing to push
+                scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
+                ret = None
+            else:
+                # something to push
+                if not pushop.force:
+                    # if repo.obsstore == False --> no obsolete
+                    # then, save the iteration
+                    if unfi.obsstore:
+                        # this message are here for 80 char limit reason
+                        mso = _("push includes obsolete changeset: %s!")
+                        mst = "push includes %s changeset: %s!"
+                        # plain versions for i18n tool to detect them
+                        _("push includes unstable changeset: %s!")
+                        _("push includes bumped changeset: %s!")
+                        _("push includes divergent changeset: %s!")
+                        # If we are to push if there is at least one
+                        # obsolete or unstable changeset in missing, at
+                        # least one of the missinghead will be obsolete or
+                        # unstable. So checking heads only is ok
+                        for node in outgoing.missingheads:
+                            ctx = unfi[node]
+                            if ctx.obsolete():
+                                raise util.Abort(mso % ctx)
+                            elif ctx.troubled():
+                                raise util.Abort(_(mst)
+                                                 % (ctx.troubles()[0],
+                                                    ctx))
+                    newbm = pushop.ui.configlist('bookmarks', 'pushing')
+                    discovery.checkheads(unfi, pushop.remote, outgoing,
+                                         remoteheads, pushop.newbranch,
+                                         bool(inc), newbm)
+
+                # TODO: get bundlecaps from remote
+                bundlecaps = None
+                # create a changegroup from local
+                if pushop.revs is None and not (outgoing.excluded
+                                        or pushop.repo.changelog.filteredrevs):
+                    # push everything,
+                    # use the fast path, no race possible on push
+                    bundler = changegroup.bundle10(pushop.repo, bundlecaps)
+                    cg = pushop.repo._changegroupsubset(outgoing,
+                                                        bundler,
+                                                        'push',
+                                                        fastpath=True)
+                else:
+                    cg = pushop.repo.getlocalbundle('push', outgoing,
+                                                    bundlecaps)
+
+                # apply changegroup to remote
+                if unbundle:
+                    # local repo finds heads on server, finds out what
+                    # revs it must push. once revs transferred, if server
+                    # finds it has different heads (someone else won
+                    # commit/push race), server aborts.
+                    if pushop.force:
+                        remoteheads = ['force']
+                    # ssh: return remote's addchangegroup()
+                    # http: return remote's addchangegroup() or 0 for error
+                    ret = pushop.remote.unbundle(cg, remoteheads, 'push')
+                else:
+                    # we return an integer indicating remote head count
+                    # change
+                    ret = pushop.remote.addchangegroup(cg, 'push',
+                                                       pushop.repo.url())
+
+            if ret:
+                # push succeed, synchronize target of the push
+                cheads = outgoing.missingheads
+            elif pushop.revs is None:
+                # All out push fails. synchronize all common
+                cheads = outgoing.commonheads
+            else:
+                # I want cheads = heads(::missingheads and ::commonheads)
+                # (missingheads is revs with secret changeset filtered out)
+                #
+                # This can be expressed as:
+                #     cheads = ( (missingheads and ::commonheads)
+                #              + (commonheads and ::missingheads))"
+                #              )
+                #
+                # while trying to push we already computed the following:
+                #     common = (::commonheads)
+                #     missing = ((commonheads::missingheads) - commonheads)
+                #
+                # We can pick:
+                # * missingheads part of common (::commonheads)
+                common = set(outgoing.common)
+                nm = pushop.repo.changelog.nodemap
+                cheads = [node for node in pushop.revs if nm[node] in common]
+                # and
+                # * commonheads parents on missing
+                revset = unfi.set('%ln and parents(roots(%ln))',
+                                 outgoing.commonheads,
+                                 outgoing.missing)
+                cheads.extend(c.node() for c in revset)
+            # even when we don't push, exchanging phase data is useful
+            remotephases = pushop.remote.listkeys('phases')
+            if (pushop.ui.configbool('ui', '_usedassubrepo', False)
+                and remotephases    # server supports phases
+                and ret is None # nothing was pushed
+                and remotephases.get('publishing', False)):
+                # When:
+                # - this is a subrepo push
+                # - and remote support phase
+                # - and no changeset was pushed
+                # - and remote is publishing
+                # We may be in issue 3871 case!
+                # We drop the possible phase synchronisation done by
+                # courtesy to publish changesets possibly locally draft
+                # on the remote.
+                remotephases = {'publishing': 'True'}
+            if not remotephases: # old server or public only repo
+                localphasemove(cheads)
+                # don't push any phase data as there is nothing to push
+            else:
+                ana = phases.analyzeremotephases(pushop.repo, cheads,
+                                                 remotephases)
+                pheads, droots = ana
+                ### Apply remote phase on local
+                if remotephases.get('publishing', False):
+                    localphasemove(cheads)
+                else: # publish = False
+                    localphasemove(pheads)
+                    localphasemove(cheads, phases.draft)
+                ### Apply local phase on remote
+
+                # Get the list of all revs draft on remote by public here.
+                # XXX Beware that revset break if droots is not strictly
+                # XXX root we may want to ensure it is but it is costly
+                outdated =  unfi.set('heads((%ln::%ln) and public())',
+                                     droots, cheads)
+                for newremotehead in outdated:
+                    r = pushop.remote.pushkey('phases',
+                                              newremotehead.hex(),
+                                              str(phases.draft),
+                                              str(phases.public))
+                    if not r:
+                        pushop.ui.warn(_('updating %s to public failed!\n')
+                                       % newremotehead)
+            pushop.ui.debug('try to push obsolete markers to remote\n')
+            obsolete.syncpush(pushop.repo, pushop.remote)
+        finally:
+            if lock is not None:
+                lock.release()
+    finally:
+        if locallock is not None:
+            locallock.release()
+
+    _pushbookmark(pushop.ui, unfi, pushop.remote, pushop.revs)
+    return ret
+
+def _pushbookmark(ui, repo, remote, revs):
+    """Update bookmark position on remote"""
+    ui.debug("checking for updated bookmarks\n")
+    revnums = map(repo.changelog.rev, revs or [])
+    ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
+    (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
+     ) = bookmarks.compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
+                           srchex=hex)
+
+    for b, scid, dcid in advsrc:
+        if ancestors and repo[scid].rev() not in ancestors:
+            continue
+        if remote.pushkey('bookmarks', b, dcid, scid):
+            ui.status(_("updating bookmark %s\n") % b)
+        else:
+            ui.warn(_('updating bookmark %s failed!\n') % b)
--- a/mercurial/hgweb/webcommands.py	Wed Feb 05 17:23:35 2014 -0600
+++ b/mercurial/hgweb/webcommands.py	Wed Feb 05 18:09:07 2014 -0600
@@ -187,7 +187,7 @@
 
         mfunc = revset.match(web.repo.ui, revdef)
         try:
-            revs = mfunc(web.repo, list(web.repo))
+            revs = mfunc(web.repo, revset.baseset(web.repo))
             return MODE_REVSET, revs
             # ParseError: wrongly placed tokens, wrongs arguments, etc
             # RepoLookupError: no such revision, e.g. in 'revision:'
--- a/mercurial/localrepo.py	Wed Feb 05 17:23:35 2014 -0600
+++ b/mercurial/localrepo.py	Wed Feb 05 18:09:07 2014 -0600
@@ -9,7 +9,7 @@
 import peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview
 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
 import lock as lockmod
-import transaction, store, encoding
+import transaction, store, encoding, exchange
 import scmutil, util, extensions, hook, error, revset
 import match as matchmod
 import merge as mergemod
@@ -428,7 +428,7 @@
         '''Return a list of revisions matching the given revset'''
         expr = revset.formatspec(expr, *args)
         m = revset.match(None, expr)
-        return [r for r in m(self, list(self))]
+        return revset.baseset([r for r in m(self, revset.baseset(self))])
 
     def set(self, expr, *args):
         '''
@@ -1750,223 +1750,7 @@
         pass
 
     def push(self, remote, force=False, revs=None, newbranch=False):
-        '''Push outgoing changesets (limited by revs) from the current
-        repository to remote. Return an integer:
-          - None means nothing to push
-          - 0 means HTTP error
-          - 1 means we pushed and remote head count is unchanged *or*
-            we have outgoing changesets but refused to push
-          - other values as described by addchangegroup()
-        '''
-        if remote.local():
-            missing = set(self.requirements) - remote.local().supported
-            if missing:
-                msg = _("required features are not"
-                        " supported in the destination:"
-                        " %s") % (', '.join(sorted(missing)))
-                raise util.Abort(msg)
-
-        # there are two ways to push to remote repo:
-        #
-        # addchangegroup assumes local user can lock remote
-        # repo (local filesystem, old ssh servers).
-        #
-        # unbundle assumes local user cannot lock remote repo (new ssh
-        # servers, http servers).
-
-        if not remote.canpush():
-            raise util.Abort(_("destination does not support push"))
-        unfi = self.unfiltered()
-        def localphasemove(nodes, phase=phases.public):
-            """move <nodes> to <phase> in the local source repo"""
-            if locallock is not None:
-                phases.advanceboundary(self, phase, nodes)
-            else:
-                # repo is not locked, do not change any phases!
-                # Informs the user that phases should have been moved when
-                # applicable.
-                actualmoves = [n for n in nodes if phase < self[n].phase()]
-                phasestr = phases.phasenames[phase]
-                if actualmoves:
-                    self.ui.status(_('cannot lock source repo, skipping local'
-                                     ' %s phase update\n') % phasestr)
-        # get local lock as we might write phase data
-        locallock = None
-        try:
-            locallock = self.lock()
-        except IOError, err:
-            if err.errno != errno.EACCES:
-                raise
-            # source repo cannot be locked.
-            # We do not abort the push, but just disable the local phase
-            # synchronisation.
-            msg = 'cannot lock source repository: %s\n' % err
-            self.ui.debug(msg)
-        try:
-            self.checkpush(force, revs)
-            lock = None
-            unbundle = remote.capable('unbundle')
-            if not unbundle:
-                lock = remote.lock()
-            try:
-                # discovery
-                fci = discovery.findcommonincoming
-                commoninc = fci(unfi, remote, force=force)
-                common, inc, remoteheads = commoninc
-                fco = discovery.findcommonoutgoing
-                outgoing = fco(unfi, remote, onlyheads=revs,
-                               commoninc=commoninc, force=force)
-
-
-                if not outgoing.missing:
-                    # nothing to push
-                    scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
-                    ret = None
-                else:
-                    # something to push
-                    if not force:
-                        # if self.obsstore == False --> no obsolete
-                        # then, save the iteration
-                        if unfi.obsstore:
-                            # this message are here for 80 char limit reason
-                            mso = _("push includes obsolete changeset: %s!")
-                            mst = "push includes %s changeset: %s!"
-                            # plain versions for i18n tool to detect them
-                            _("push includes unstable changeset: %s!")
-                            _("push includes bumped changeset: %s!")
-                            _("push includes divergent changeset: %s!")
-                            # If we are to push if there is at least one
-                            # obsolete or unstable changeset in missing, at
-                            # least one of the missinghead will be obsolete or
-                            # unstable. So checking heads only is ok
-                            for node in outgoing.missingheads:
-                                ctx = unfi[node]
-                                if ctx.obsolete():
-                                    raise util.Abort(mso % ctx)
-                                elif ctx.troubled():
-                                    raise util.Abort(_(mst)
-                                                     % (ctx.troubles()[0],
-                                                        ctx))
-                        newbm = self.ui.configlist('bookmarks', 'pushing')
-                        discovery.checkheads(unfi, remote, outgoing,
-                                             remoteheads, newbranch,
-                                             bool(inc), newbm)
-
-                    # TODO: get bundlecaps from remote
-                    bundlecaps = None
-                    # create a changegroup from local
-                    if revs is None and not (outgoing.excluded
-                                             or self.changelog.filteredrevs):
-                        # push everything,
-                        # use the fast path, no race possible on push
-                        bundler = changegroup.bundle10(self, bundlecaps)
-                        cg = self._changegroupsubset(outgoing,
-                                                     bundler,
-                                                     'push',
-                                                     fastpath=True)
-                    else:
-                        cg = self.getlocalbundle('push', outgoing, bundlecaps)
-
-                    # apply changegroup to remote
-                    if unbundle:
-                        # local repo finds heads on server, finds out what
-                        # revs it must push. once revs transferred, if server
-                        # finds it has different heads (someone else won
-                        # commit/push race), server aborts.
-                        if force:
-                            remoteheads = ['force']
-                        # ssh: return remote's addchangegroup()
-                        # http: return remote's addchangegroup() or 0 for error
-                        ret = remote.unbundle(cg, remoteheads, 'push')
-                    else:
-                        # we return an integer indicating remote head count
-                        # change
-                        ret = remote.addchangegroup(cg, 'push', self.url())
-
-                if ret:
-                    # push succeed, synchronize target of the push
-                    cheads = outgoing.missingheads
-                elif revs is None:
-                    # All out push fails. synchronize all common
-                    cheads = outgoing.commonheads
-                else:
-                    # I want cheads = heads(::missingheads and ::commonheads)
-                    # (missingheads is revs with secret changeset filtered out)
-                    #
-                    # This can be expressed as:
-                    #     cheads = ( (missingheads and ::commonheads)
-                    #              + (commonheads and ::missingheads))"
-                    #              )
-                    #
-                    # while trying to push we already computed the following:
-                    #     common = (::commonheads)
-                    #     missing = ((commonheads::missingheads) - commonheads)
-                    #
-                    # We can pick:
-                    # * missingheads part of common (::commonheads)
-                    common = set(outgoing.common)
-                    nm = self.changelog.nodemap
-                    cheads = [node for node in revs if nm[node] in common]
-                    # and
-                    # * commonheads parents on missing
-                    revset = unfi.set('%ln and parents(roots(%ln))',
-                                     outgoing.commonheads,
-                                     outgoing.missing)
-                    cheads.extend(c.node() for c in revset)
-                # even when we don't push, exchanging phase data is useful
-                remotephases = remote.listkeys('phases')
-                if (self.ui.configbool('ui', '_usedassubrepo', False)
-                    and remotephases    # server supports phases
-                    and ret is None # nothing was pushed
-                    and remotephases.get('publishing', False)):
-                    # When:
-                    # - this is a subrepo push
-                    # - and remote support phase
-                    # - and no changeset was pushed
-                    # - and remote is publishing
-                    # We may be in issue 3871 case!
-                    # We drop the possible phase synchronisation done by
-                    # courtesy to publish changesets possibly locally draft
-                    # on the remote.
-                    remotephases = {'publishing': 'True'}
-                if not remotephases: # old server or public only repo
-                    localphasemove(cheads)
-                    # don't push any phase data as there is nothing to push
-                else:
-                    ana = phases.analyzeremotephases(self, cheads, remotephases)
-                    pheads, droots = ana
-                    ### Apply remote phase on local
-                    if remotephases.get('publishing', False):
-                        localphasemove(cheads)
-                    else: # publish = False
-                        localphasemove(pheads)
-                        localphasemove(cheads, phases.draft)
-                    ### Apply local phase on remote
-
-                    # Get the list of all revs draft on remote by public here.
-                    # XXX Beware that revset break if droots is not strictly
-                    # XXX root we may want to ensure it is but it is costly
-                    outdated =  unfi.set('heads((%ln::%ln) and public())',
-                                         droots, cheads)
-                    for newremotehead in outdated:
-                        r = remote.pushkey('phases',
-                                           newremotehead.hex(),
-                                           str(phases.draft),
-                                           str(phases.public))
-                        if not r:
-                            self.ui.warn(_('updating %s to public failed!\n')
-                                            % newremotehead)
-                self.ui.debug('try to push obsolete markers to remote\n')
-                obsolete.syncpush(self, remote)
-            finally:
-                if lock is not None:
-                    lock.release()
-        finally:
-            if locallock is not None:
-                locallock.release()
-
-        bookmarks.updateremote(self.ui, unfi, remote, revs)
-        return ret
+        return exchange.push(self, remote, force, revs, newbranch)
 
     def changegroupinfo(self, nodes, source):
         if self.ui.verbose or source == 'bundle':
--- a/mercurial/revset.py	Wed Feb 05 17:23:35 2014 -0600
+++ b/mercurial/revset.py	Wed Feb 05 18:09:07 2014 -0600
@@ -53,9 +53,9 @@
     """Return all paths between roots and heads, inclusive of both endpoint
     sets."""
     if not roots:
-        return []
+        return baseset([])
     parentrevs = repo.changelog.parentrevs
-    visit = heads[:]
+    visit = baseset(heads)
     reachable = set()
     seen = {}
     minroot = min(roots)
@@ -72,12 +72,12 @@
             if parent >= minroot and parent not in seen:
                 visit.append(parent)
     if not reachable:
-        return []
+        return baseset([])
     for rev in sorted(seen):
         for parent in seen[rev]:
             if parent in reachable:
                 reachable.add(rev)
-    return sorted(reachable)
+    return baseset(sorted(reachable))
 
 elements = {
     "(": (20, ("group", 1, ")"), ("func", 1, ")")),
@@ -195,7 +195,7 @@
 def getset(repo, subset, x):
     if not x:
         raise error.ParseError(_("missing argument"))
-    return methods[x[0]](repo, subset, *x[1:])
+    return baseset(methods[x[0]](repo, subset, *x[1:]))
 
 def _getrevsource(repo, r):
     extra = repo[r].extra()
@@ -212,10 +212,10 @@
 def stringset(repo, subset, x):
     x = repo[x].rev()
     if x == -1 and len(subset) == len(repo):
-        return [-1]
+        return baseset([-1])
     if len(subset) == len(repo) or x in subset:
-        return [x]
-    return []
+        return baseset([x])
+    return baseset([])
 
 def symbolset(repo, subset, x):
     if x in symbols:
@@ -223,39 +223,37 @@
     return stringset(repo, subset, x)
 
 def rangeset(repo, subset, x, y):
-    cl = repo.changelog
+    cl = baseset(repo.changelog)
     m = getset(repo, cl, x)
     n = getset(repo, cl, y)
 
     if not m or not n:
-        return []
+        return baseset([])
     m, n = m[0], n[-1]
 
     if m < n:
         r = range(m, n + 1)
     else:
         r = range(m, n - 1, -1)
-    s = set(subset)
-    return [x for x in r if x in s]
+    s = subset.set()
+    return baseset([x for x in r if x in s])
 
 def dagrange(repo, subset, x, y):
-    r = list(repo)
+    r = baseset(repo)
     xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
-    s = set(subset)
-    return [r for r in xs if r in s]
+    s = subset.set()
+    return baseset([r for r in xs if r in s])
 
 def andset(repo, subset, x, y):
     return getset(repo, getset(repo, subset, x), y)
 
 def orset(repo, subset, x, y):
     xl = getset(repo, subset, x)
-    s = set(xl)
-    yl = getset(repo, [r for r in subset if r not in s], y)
-    return xl + yl
+    yl = getset(repo, subset - xl, y)
+    return baseset(xl + yl)
 
 def notset(repo, subset, x):
-    s = set(getset(repo, subset, x))
-    return [r for r in subset if r not in s]
+    return subset - getset(repo, subset, x)
 
 def listset(repo, subset, a, b):
     raise error.ParseError(_("can't use a list in this context"))
@@ -289,7 +287,7 @@
     """
     # i18n: "ancestor" is a keyword
     l = getlist(x)
-    rl = list(repo)
+    rl = baseset(repo)
     anc = None
 
     # (getset(repo, rl, i) for i in l) generates a list of lists
@@ -304,15 +302,16 @@
                 anc = rev(ancestor(node(anc), node(r)))
 
     if anc is not None and anc in subset:
-        return [anc]
-    return []
+        return baseset([anc])
+    return baseset([])
 
 def _ancestors(repo, subset, x, followfirst=False):
-    args = getset(repo, list(repo), x)
+    args = getset(repo, baseset(repo), x)
     if not args:
-        return []
+        return baseset([])
     s = set(_revancestors(repo, args, followfirst)) | set(args)
-    return [r for r in subset if r in s]
+    ss = subset.set()
+    return baseset([r for r in ss if r in s])
 
 def ancestors(repo, subset, x):
     """``ancestors(set)``
@@ -340,7 +339,8 @@
         for i in range(n):
             r = cl.parentrevs(r)[0]
         ps.add(r)
-    return [r for r in subset if r in ps]
+    s = subset.set()
+    return baseset([r for r in s if r in ps])
 
 def author(repo, subset, x):
     """``author(string)``
@@ -349,7 +349,8 @@
     # i18n: "author" is a keyword
     n = encoding.lower(getstring(x, _("author requires a string")))
     kind, pattern, matcher = _substringmatcher(n)
-    return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
+    return baseset([r for r in subset if
+        matcher(encoding.lower(repo[r].user()))])
 
 def bisect(repo, subset, x):
     """``bisect(string)``
@@ -366,7 +367,8 @@
     # i18n: "bisect" is a keyword
     status = getstring(x, _("bisect requires a string")).lower()
     state = set(hbisect.get(repo, status))
-    return [r for r in subset if r in state]
+    s = subset.set()
+    return baseset([r for r in s if r in state])
 
 # Backward-compatibility
 # - no help entry so that we do not advertise it any more
@@ -393,7 +395,7 @@
             if not bmrev:
                 raise util.Abort(_("bookmark '%s' does not exist") % bm)
             bmrev = repo[bmrev].rev()
-            return [r for r in subset if r == bmrev]
+            return baseset([r for r in subset if r == bmrev])
         else:
             matchrevs = set()
             for name, bmrev in repo._bookmarks.iteritems():
@@ -405,11 +407,12 @@
             bmrevs = set()
             for bmrev in matchrevs:
                 bmrevs.add(repo[bmrev].rev())
-            return [r for r in subset if r in bmrevs]
+            return subset & bmrevs
 
     bms = set([repo[r].rev()
                for r in repo._bookmarks.values()])
-    return [r for r in subset if r in bms]
+    s = subset.set()
+    return baseset([r for r in s if r in bms])
 
 def branch(repo, subset, x):
     """``branch(string or set)``
@@ -431,16 +434,16 @@
             # note: falls through to the revspec case if no branch with
             # this name exists
             if pattern in repo.branchmap():
-                return [r for r in subset if matcher(repo[r].branch())]
+                return baseset([r for r in subset if matcher(repo[r].branch())])
         else:
-            return [r for r in subset if matcher(repo[r].branch())]
+            return baseset([r for r in subset if matcher(repo[r].branch())])
 
-    s = getset(repo, list(repo), x)
+    s = getset(repo, baseset(repo), x)
     b = set()
     for r in s:
         b.add(repo[r].branch())
-    s = set(s)
-    return [r for r in subset if r in s or repo[r].branch() in b]
+    s = s.set()
+    return baseset([r for r in subset if r in s or repo[r].branch() in b])
 
 def bumped(repo, subset, x):
     """``bumped()``
@@ -451,7 +454,7 @@
     # i18n: "bumped" is a keyword
     getargs(x, 0, 0, _("bumped takes no arguments"))
     bumped = obsmod.getrevs(repo, 'bumped')
-    return [r for r in subset if r in bumped]
+    return subset & bumped
 
 def bundle(repo, subset, x):
     """``bundle()``
@@ -463,7 +466,7 @@
         bundlerevs = repo.changelog.bundlerevs
     except AttributeError:
         raise util.Abort(_("no bundle provided - specify with -R"))
-    return [r for r in subset if r in bundlerevs]
+    return subset & bundlerevs
 
 def checkstatus(repo, subset, pat, field):
     m = None
@@ -494,12 +497,12 @@
                 if m(f):
                     s.append(r)
                     break
-    return s
+    return baseset(s)
 
 def _children(repo, narrow, parentset):
     cs = set()
     if not parentset:
-        return cs
+        return baseset(cs)
     pr = repo.changelog.parentrevs
     minrev = min(parentset)
     for r in narrow:
@@ -508,15 +511,15 @@
         for p in pr(r):
             if p in parentset:
                 cs.add(r)
-    return cs
+    return baseset(cs)
 
 def children(repo, subset, x):
     """``children(set)``
     Child changesets of changesets in set.
     """
-    s = set(getset(repo, list(repo), x))
+    s = getset(repo, baseset(repo), x).set()
     cs = _children(repo, subset, s)
-    return [r for r in subset if r in cs]
+    return subset & cs
 
 def closed(repo, subset, x):
     """``closed()``
@@ -524,7 +527,7 @@
     """
     # i18n: "closed" is a keyword
     getargs(x, 0, 0, _("closed takes no arguments"))
-    return [r for r in subset if repo[r].closesbranch()]
+    return baseset([r for r in subset if repo[r].closesbranch()])
 
 def contains(repo, subset, x):
     """``contains(pattern)``
@@ -553,7 +556,7 @@
                 if m(f):
                     s.append(r)
                     break
-    return s
+    return baseset(s)
 
 def converted(repo, subset, x):
     """``converted([id])``
@@ -575,7 +578,7 @@
         source = repo[r].extra().get('convert_revision', None)
         return source is not None and (rev is None or source.startswith(rev))
 
-    return [r for r in subset if _matchvalue(r)]
+    return baseset([r for r in subset if _matchvalue(r)])
 
 def date(repo, subset, x):
     """``date(interval)``
@@ -584,7 +587,7 @@
     # i18n: "date" is a keyword
     ds = getstring(x, _("date requires a string"))
     dm = util.matchdate(ds)
-    return [r for r in subset if dm(repo[r].date()[0])]
+    return baseset([r for r in subset if dm(repo[r].date()[0])])
 
 def desc(repo, subset, x):
     """``desc(string)``
@@ -597,14 +600,15 @@
         c = repo[r]
         if ds in encoding.lower(c.description()):
             l.append(r)
-    return l
+    return baseset(l)
 
 def _descendants(repo, subset, x, followfirst=False):
-    args = getset(repo, list(repo), x)
+    args = getset(repo, baseset(repo), x)
     if not args:
-        return []
+        return baseset([])
     s = set(_revdescendants(repo, args, followfirst)) | set(args)
-    return [r for r in subset if r in s]
+    ss = subset.set()
+    return baseset([r for r in ss if r in s])
 
 def descendants(repo, subset, x):
     """``descendants(set)``
@@ -624,9 +628,9 @@
     is the same as passing all().
     """
     if x is not None:
-        args = set(getset(repo, list(repo), x))
+        args = getset(repo, baseset(repo), x).set()
     else:
-        args = set(getall(repo, list(repo), x))
+        args = getall(repo, baseset(repo), x).set()
 
     dests = set()
 
@@ -659,7 +663,7 @@
             r = src
             src = _getrevsource(repo, r)
 
-    return [r for r in subset if r in dests]
+    return baseset([r for r in subset if r in dests])
 
 def divergent(repo, subset, x):
     """``divergent()``
@@ -668,7 +672,7 @@
     # i18n: "divergent" is a keyword
     getargs(x, 0, 0, _("divergent takes no arguments"))
     divergent = obsmod.getrevs(repo, 'divergent')
-    return [r for r in subset if r in divergent]
+    return baseset([r for r in subset if r in divergent])
 
 def draft(repo, subset, x):
     """``draft()``
@@ -676,7 +680,7 @@
     # i18n: "draft" is a keyword
     getargs(x, 0, 0, _("draft takes no arguments"))
     pc = repo._phasecache
-    return [r for r in subset if pc.phase(repo, r) == phases.draft]
+    return baseset([r for r in subset if pc.phase(repo, r) == phases.draft])
 
 def extinct(repo, subset, x):
     """``extinct()``
@@ -685,7 +689,7 @@
     # i18n: "extinct" is a keyword
     getargs(x, 0, 0, _("extinct takes no arguments"))
     extincts = obsmod.getrevs(repo, 'extinct')
-    return [r for r in subset if r in extincts]
+    return subset & extincts
 
 def extra(repo, subset, x):
     """``extra(label, [value])``
@@ -712,7 +716,7 @@
         extra = repo[r].extra()
         return label in extra and (value is None or matcher(extra[label]))
 
-    return [r for r in subset if _matchvalue(r)]
+    return baseset([r for r in subset if _matchvalue(r)])
 
 def filelog(repo, subset, x):
     """``filelog(pattern)``
@@ -744,7 +748,8 @@
                 for fr in fl:
                     s.add(fl.linkrev(fr))
 
-    return [r for r in subset if r in s]
+    ss = subset.set()
+    return baseset([r for r in ss if r in s])
 
 def first(repo, subset, x):
     """``first(set, [n])``
@@ -763,11 +768,12 @@
             # include the revision responsible for the most recent version
             s.add(cx.linkrev())
         else:
-            return []
+            return baseset([])
     else:
         s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
 
-    return [r for r in subset if r in s]
+    ss = subset.set()
+    return baseset([r for r in ss if r in s])
 
 def follow(repo, subset, x):
     """``follow([file])``
@@ -809,7 +815,7 @@
             if gr.search(e):
                 l.append(r)
                 break
-    return l
+    return baseset(l)
 
 def _matchfiles(repo, subset, x):
     # _matchfiles takes a revset list of prefixed arguments:
@@ -872,7 +878,7 @@
             if m(f):
                 s.append(r)
                 break
-    return s
+    return baseset(s)
 
 def hasfile(repo, subset, x):
     """``file(pattern)``
@@ -896,15 +902,16 @@
     hs = set()
     for b, ls in repo.branchmap().iteritems():
         hs.update(repo[h].rev() for h in ls)
-    return [r for r in subset if r in hs]
+    s = subset.set()
+    return baseset([r for r in s if r in hs])
 
 def heads(repo, subset, x):
     """``heads(set)``
     Members of set with no children in set.
     """
     s = getset(repo, subset, x)
-    ps = set(parents(repo, subset, x))
-    return [r for r in s if r not in ps]
+    ps = parents(repo, subset, x)
+    return s - ps
 
 def hidden(repo, subset, x):
     """``hidden()``
@@ -913,7 +920,7 @@
     # i18n: "hidden" is a keyword
     getargs(x, 0, 0, _("hidden takes no arguments"))
     hiddenrevs = repoview.filterrevs(repo, 'visible')
-    return [r for r in subset if r in hiddenrevs]
+    return subset & hiddenrevs
 
 def keyword(repo, subset, x):
     """``keyword(string)``
@@ -928,7 +935,7 @@
         if util.any(kw in encoding.lower(t)
                     for t in c.files() + [c.user(), c.description()]):
             l.append(r)
-    return l
+    return baseset(l)
 
 def limit(repo, subset, x):
     """``limit(set, [n])``
@@ -944,9 +951,9 @@
     except (TypeError, ValueError):
         # i18n: "limit" is a keyword
         raise error.ParseError(_("limit expects a number"))
-    ss = set(subset)
-    os = getset(repo, list(repo), l[0])[:lim]
-    return [r for r in os if r in ss]
+    ss = subset.set()
+    os = getset(repo, baseset(repo), l[0])[:lim]
+    return baseset([r for r in os if r in ss])
 
 def last(repo, subset, x):
     """``last(set, [n])``
@@ -962,20 +969,20 @@
     except (TypeError, ValueError):
         # i18n: "last" is a keyword
         raise error.ParseError(_("last expects a number"))
-    ss = set(subset)
-    os = getset(repo, list(repo), l[0])[-lim:]
-    return [r for r in os if r in ss]
+    ss = subset.set()
+    os = getset(repo, baseset(repo), l[0])[-lim:]
+    return baseset([r for r in os if r in ss])
 
 def maxrev(repo, subset, x):
     """``max(set)``
     Changeset with highest revision number in set.
     """
-    os = getset(repo, list(repo), x)
+    os = getset(repo, baseset(repo), x)
     if os:
         m = max(os)
         if m in subset:
-            return [m]
-    return []
+            return baseset([m])
+    return baseset([])
 
 def merge(repo, subset, x):
     """``merge()``
@@ -984,7 +991,7 @@
     # i18n: "merge" is a keyword
     getargs(x, 0, 0, _("merge takes no arguments"))
     cl = repo.changelog
-    return [r for r in subset if cl.parentrevs(r)[1] != -1]
+    return baseset([r for r in subset if cl.parentrevs(r)[1] != -1])
 
 def branchpoint(repo, subset, x):
     """``branchpoint()``
@@ -994,25 +1001,25 @@
     getargs(x, 0, 0, _("branchpoint takes no arguments"))
     cl = repo.changelog
     if not subset:
-        return []
+        return baseset([])
     baserev = min(subset)
     parentscount = [0]*(len(repo) - baserev)
     for r in cl.revs(start=baserev + 1):
         for p in cl.parentrevs(r):
             if p >= baserev:
                 parentscount[p - baserev] += 1
-    return [r for r in subset if (parentscount[r - baserev] > 1)]
+    return baseset([r for r in subset if (parentscount[r - baserev] > 1)])
 
 def minrev(repo, subset, x):
     """``min(set)``
     Changeset with lowest revision number in set.
     """
-    os = getset(repo, list(repo), x)
+    os = getset(repo, baseset(repo), x)
     if os:
         m = min(os)
         if m in subset:
-            return [m]
-    return []
+            return baseset([m])
+    return baseset([])
 
 def modifies(repo, subset, x):
     """``modifies(pattern)``
@@ -1042,7 +1049,7 @@
         if pm is not None:
             rn = repo.changelog.rev(pm)
 
-    return [r for r in subset if r == rn]
+    return baseset([r for r in subset if r == rn])
 
 def obsolete(repo, subset, x):
     """``obsolete()``
@@ -1050,7 +1057,7 @@
     # i18n: "obsolete" is a keyword
     getargs(x, 0, 0, _("obsolete takes no arguments"))
     obsoletes = obsmod.getrevs(repo, 'obsolete')
-    return [r for r in subset if r in obsoletes]
+    return subset & obsoletes
 
 def origin(repo, subset, x):
     """``origin([set])``
@@ -1061,9 +1068,9 @@
     for the first operation is selected.
     """
     if x is not None:
-        args = set(getset(repo, list(repo), x))
+        args = getset(repo, baseset(repo), x).set()
     else:
-        args = set(getall(repo, list(repo), x))
+        args = getall(repo, baseset(repo), x).set()
 
     def _firstsrc(rev):
         src = _getrevsource(repo, rev)
@@ -1078,7 +1085,8 @@
             src = prev
 
     o = set([_firstsrc(r) for r in args])
-    return [r for r in subset if r in o]
+    s = subset.set()
+    return baseset([r for r in s if r in o])
 
 def outgoing(repo, subset, x):
     """``outgoing([path])``
@@ -1101,7 +1109,8 @@
     repo.ui.popbuffer()
     cl = repo.changelog
     o = set([cl.rev(r) for r in outgoing.missing])
-    return [r for r in subset if r in o]
+    s = subset.set()
+    return baseset([r for r in s if r in o])
 
 def p1(repo, subset, x):
     """``p1([set])``
@@ -1109,13 +1118,13 @@
     """
     if x is None:
         p = repo[x].p1().rev()
-        return [r for r in subset if r == p]
+        return baseset([r for r in subset if r == p])
 
     ps = set()
     cl = repo.changelog
-    for r in getset(repo, list(repo), x):
+    for r in getset(repo, baseset(repo), x):
         ps.add(cl.parentrevs(r)[0])
-    return [r for r in subset if r in ps]
+    return subset & ps
 
 def p2(repo, subset, x):
     """``p2([set])``
@@ -1125,15 +1134,15 @@
         ps = repo[x].parents()
         try:
             p = ps[1].rev()
-            return [r for r in subset if r == p]
+            return baseset([r for r in subset if r == p])
         except IndexError:
-            return []
+            return baseset([])
 
     ps = set()
     cl = repo.changelog
-    for r in getset(repo, list(repo), x):
+    for r in getset(repo, baseset(repo), x):
         ps.add(cl.parentrevs(r)[1])
-    return [r for r in subset if r in ps]
+    return subset & ps
 
 def parents(repo, subset, x):
     """``parents([set])``
@@ -1141,13 +1150,13 @@
     """
     if x is None:
         ps = tuple(p.rev() for p in repo[x].parents())
-        return [r for r in subset if r in ps]
+        return subset & ps
 
     ps = set()
     cl = repo.changelog
-    for r in getset(repo, list(repo), x):
+    for r in getset(repo, baseset(repo), x):
         ps.update(cl.parentrevs(r))
-    return [r for r in subset if r in ps]
+    return subset & ps
 
 def parentspec(repo, subset, x, n):
     """``set^0``
@@ -1163,7 +1172,7 @@
         raise error.ParseError(_("^ expects a number 0, 1, or 2"))
     ps = set()
     cl = repo.changelog
-    for r in getset(repo, cl, x):
+    for r in getset(repo, baseset(cl), x):
         if n == 0:
             ps.add(r)
         elif n == 1:
@@ -1172,7 +1181,7 @@
             parents = cl.parentrevs(r)
             if len(parents) > 1:
                 ps.add(parents[1])
-    return [r for r in subset if r in ps]
+    return subset & ps
 
 def present(repo, subset, x):
     """``present(set)``
@@ -1186,7 +1195,7 @@
     try:
         return getset(repo, subset, x)
     except error.RepoLookupError:
-        return []
+        return baseset([])
 
 def public(repo, subset, x):
     """``public()``
@@ -1194,7 +1203,7 @@
     # i18n: "public" is a keyword
     getargs(x, 0, 0, _("public takes no arguments"))
     pc = repo._phasecache
-    return [r for r in subset if pc.phase(repo, r) == phases.public]
+    return baseset([r for r in subset if pc.phase(repo, r) == phases.public])
 
 def remote(repo, subset, x):
     """``remote([id [,path]])``
@@ -1228,8 +1237,8 @@
     if n in repo:
         r = repo[n].rev()
         if r in subset:
-            return [r]
-    return []
+            return baseset([r])
+    return baseset([])
 
 def removes(repo, subset, x):
     """``removes(pattern)``
@@ -1255,7 +1264,7 @@
     except (TypeError, ValueError):
         # i18n: "rev" is a keyword
         raise error.ParseError(_("rev expects a number"))
-    return [r for r in subset if r == l]
+    return baseset([r for r in subset if r == l])
 
 def matching(repo, subset, x):
     """``matching(revision [, field])``
@@ -1285,7 +1294,7 @@
     # i18n: "matching" is a keyword
     l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
 
-    revs = getset(repo, repo.changelog, l[0])
+    revs = getset(repo, baseset(repo.changelog), l[0])
 
     fieldlist = ['metadata']
     if len(l) > 1:
@@ -1367,15 +1376,13 @@
                     break
             if match:
                 matches.add(r)
-    return [r for r in subset if r in matches]
+    return baseset([r for r in subset if r in matches])
 
 def reverse(repo, subset, x):
     """``reverse(set)``
     Reverse order of set.
     """
     l = getset(repo, subset, x)
-    if not isinstance(l, list):
-        l = list(l)
     l.reverse()
     return l
 
@@ -1383,10 +1390,10 @@
     """``roots(set)``
     Changesets in set with no parent changeset in set.
     """
-    s = set(getset(repo, repo.changelog, x))
-    subset = [r for r in subset if r in s]
+    s = getset(repo, baseset(repo.changelog), x).set()
+    subset = baseset([r for r in subset if r in s])
     cs = _children(repo, subset, s)
-    return [r for r in subset if r not in cs]
+    return subset - cs
 
 def secret(repo, subset, x):
     """``secret()``
@@ -1394,7 +1401,7 @@
     # i18n: "secret" is a keyword
     getargs(x, 0, 0, _("secret takes no arguments"))
     pc = repo._phasecache
-    return [r for r in subset if pc.phase(repo, r) == phases.secret]
+    return baseset([r for r in subset if pc.phase(repo, r) == phases.secret])
 
 def sort(repo, subset, x):
     """``sort(set[, [-]key...])``
@@ -1450,7 +1457,7 @@
         e.append(r)
         l.append(e)
     l.sort()
-    return [e[-1] for e in l]
+    return baseset([e[-1] for e in l])
 
 def _stringmatcher(pattern):
     """
@@ -1519,7 +1526,7 @@
             s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
     else:
         s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
-    return [r for r in subset if r in s]
+    return subset & s
 
 def tagged(repo, subset, x):
     return tag(repo, subset, x)
@@ -1531,7 +1538,7 @@
     # i18n: "unstable" is a keyword
     getargs(x, 0, 0, _("unstable takes no arguments"))
     unstables = obsmod.getrevs(repo, 'unstable')
-    return [r for r in subset if r in unstables]
+    return subset & unstables
 
 
 def user(repo, subset, x):
@@ -1548,11 +1555,10 @@
 def _list(repo, subset, x):
     s = getstring(x, "internal error")
     if not s:
-        return []
-    if not isinstance(subset, set):
-        subset = set(subset)
+        return baseset([])
     ls = [repo[r].rev() for r in s.split('\0')]
-    return [r for r in ls if r in subset]
+    s = subset.set()
+    return baseset([r for r in ls if r in s])
 
 symbols = {
     "adds": adds,
@@ -2046,5 +2052,28 @@
             funcs.add(tree[1][1])
         return funcs
 
+class baseset(list):
+    def __init__(self, data):
+        super(baseset, self).__init__(data)
+        self._set = None
+
+    def set(self):
+        if not self._set:
+            self._set = set(self)
+        return self._set
+
+    def __sub__(self, x):
+        if isinstance(x, baseset):
+            s = x.set()
+        else:
+            s = set(x)
+        return baseset(self.set() - s)
+
+    def __and__(self, x):
+        s = self.set()
+        if isinstance(x, baseset):
+            x = x.set()
+        return baseset([y for y in s if y in x])
+
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = symbols.values()
--- a/mercurial/scmutil.py	Wed Feb 05 17:23:35 2014 -0600
+++ b/mercurial/scmutil.py	Wed Feb 05 18:09:07 2014 -0600
@@ -524,11 +524,11 @@
 
         # fall through to new-style queries if old-style fails
         m = revset.match(repo.ui, spec)
-        dl = [r for r in m(repo, list(repo)) if r not in seen]
+        dl = [r for r in m(repo, revset.baseset(repo)) if r not in seen]
         l.extend(dl)
         seen.update(dl)
 
-    return l
+    return revset.baseset(l)
 
 def expandpats(pats):
     if not util.expandglobs: