changeset 26047:d9d3d49c4cf7

merge with stable
author Matt Mackall <mpm@selenic.com>
date Tue, 18 Aug 2015 18:38:56 -0500
parents b3ad349d0e50 (diff) b930d4ef7739 (current diff)
children 0be2f81aadc3
files tests/test-convert-git.t
diffstat 99 files changed, 1831 insertions(+), 608 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/revsetbenchmarks.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/contrib/revsetbenchmarks.py	Tue Aug 18 18:38:56 2015 -0500
@@ -33,6 +33,8 @@
     """update the repo to a revision"""
     try:
         check_call(['hg', 'update', '--quiet', '--check', str(rev)])
+        check_output(['make', 'local'],
+                     stderr=None)  # suppress output except for error/warning
     except CalledProcessError as exc:
         print >> sys.stderr, 'update to revision %s failed, aborting' % rev
         sys.exit(exc.returncode)
--- a/hgext/convert/common.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/hgext/convert/common.py	Tue Aug 18 18:38:56 2015 -0500
@@ -82,6 +82,13 @@
     def after(self):
         pass
 
+    def targetfilebelongstosource(self, targetfilename):
+        """Returns true if the given targetfile belongs to the source repo. This
+        is useful when only a subdirectory of the target belongs to the source
+        repo."""
+        # For normal full repo converts, this is always True.
+        return True
+
     def setrevmap(self, revmap):
         """set the map of already-converted revisions"""
         pass
--- a/hgext/convert/convcmd.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/hgext/convert/convcmd.py	Tue Aug 18 18:38:56 2015 -0500
@@ -120,6 +120,9 @@
                          item=file, total=self.filecount)
         return self.source.getfile(file, rev)
 
+    def targetfilebelongstosource(self, targetfilename):
+        return self.source.targetfilebelongstosource(targetfilename)
+
     def lookuprev(self, rev):
         return self.source.lookuprev(rev)
 
--- a/hgext/convert/filemap.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/hgext/convert/filemap.py	Tue Aug 18 18:38:56 2015 -0500
@@ -42,6 +42,7 @@
         self.include = {}
         self.exclude = {}
         self.rename = {}
+        self.targetprefixes = None
         if path:
             if self.parse(path):
                 raise util.Abort(_('errors in filemap'))
@@ -100,6 +101,30 @@
                 pass
         return '', name, ''
 
+    def istargetfile(self, filename):
+        """Return true if the given target filename is covered as a destination
+        of the filemap. This is useful for identifying what parts of the target
+        repo belong to the source repo and what parts don't."""
+        if self.targetprefixes is None:
+            self.targetprefixes = set()
+            for before, after in self.rename.iteritems():
+                self.targetprefixes.add(after)
+
+        # If "." is a target, then all target files are considered from the
+        # source.
+        if not self.targetprefixes or '.' in self.targetprefixes:
+            return True
+
+        filename = normalize(filename)
+        for pre, suf in rpairs(filename):
+            # This check is imperfect since it doesn't account for the
+            # include/exclude list, but it should work in filemaps that don't
+            # apply include/exclude to the same source directories they are
+            # renaming.
+            if pre in self.targetprefixes:
+                return True
+        return False
+
     def __call__(self, name):
         if self.include:
             inc = self.lookup(name, self.include)[0]
@@ -410,6 +435,9 @@
 
         return files, ncopies, ncleanp2
 
+    def targetfilebelongstosource(self, targetfilename):
+        return self.filemapper.istargetfile(targetfilename)
+
     def getfile(self, name, rev):
         realname, realrev = rev
         return self.base.getfile(realname, realrev)
--- a/hgext/convert/git.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/hgext/convert/git.py	Tue Aug 18 18:38:56 2015 -0500
@@ -377,28 +377,31 @@
     def getbookmarks(self):
         bookmarks = {}
 
-        # Interesting references in git are prefixed
-        prefix = 'refs/heads/'
-        prefixlen = len(prefix)
+        # Handle local and remote branches
+        remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
+        reftypes = [
+            # (git prefix, hg prefix)
+            ('refs/remotes/origin/', remoteprefix + '/'),
+            ('refs/heads/', '')
+        ]
 
-        # factor two commands
-        remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
-        gitcmd = { remoteprefix + '/': 'git ls-remote --heads origin',
-                                   '': 'git show-ref'}
+        exclude = set([
+            'refs/remotes/origin/HEAD',
+        ])
 
-        # Origin heads
-        for reftype in gitcmd:
-            try:
-                fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
-                for line in fh:
-                    line = line.strip()
-                    rev, name = line.split(None, 1)
-                    if not name.startswith(prefix):
+        try:
+            fh = self.gitopen('git show-ref', err=subprocess.PIPE)
+            for line in fh:
+                line = line.strip()
+                rev, name = line.split(None, 1)
+                # Process each type of branch
+                for gitprefix, hgprefix in reftypes:
+                    if not name.startswith(gitprefix) or name in exclude:
                         continue
-                    name = '%s%s' % (reftype, name[prefixlen:])
+                    name = '%s%s' % (hgprefix, name[len(gitprefix):])
                     bookmarks[name] = rev
-            except Exception:
-                pass
+        except Exception:
+            pass
 
         return bookmarks
 
--- a/hgext/convert/hg.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/hgext/convert/hg.py	Tue Aug 18 18:38:56 2015 -0500
@@ -23,6 +23,7 @@
 from mercurial.node import bin, hex, nullid
 from mercurial import hg, util, context, bookmarks, error, scmutil, exchange
 from mercurial import phases
+from mercurial import merge as mergemod
 
 from common import NoRepo, commit, converter_source, converter_sink, mapfile
 
@@ -176,12 +177,51 @@
 
         return fp.getvalue()
 
+    def _calculatemergedfiles(self, source, p1ctx, p2ctx):
+        """Calculates the files from p2 that we need to pull in when merging p1
+        and p2, given that the merge is coming from the given source.
+
+        This prevents us from losing files that only exist in the target p2 and
+        that don't come from the source repo (like if you're merging multiple
+        repositories together).
+        """
+        anc = [p1ctx.ancestor(p2ctx)]
+        # Calculate what files are coming from p2
+        actions, diverge, rename = mergemod.calculateupdates(
+            self.repo, p1ctx, p2ctx, anc,
+            True,  # branchmerge
+            True,  # force
+            False, # partial
+            False, # acceptremote
+            False, # followcopies
+        )
+
+        for file, (action, info, msg) in actions.iteritems():
+            if source.targetfilebelongstosource(file):
+                # If the file belongs to the source repo, ignore the p2
+                # since it will be covered by the existing fileset.
+                continue
+
+            # If the file requires actual merging, abort. We don't have enough
+            # context to resolve merges correctly.
+            if action in ['m', 'dm', 'cd', 'dc']:
+                raise util.Abort(_("unable to convert merge commit "
+                    "since target parents do not merge cleanly (file "
+                    "%s, parents %s and %s)") % (file, p1ctx,
+                                                 p2ctx))
+            elif action == 'k':
+                # 'keep' means nothing changed from p1
+                continue
+            else:
+                # Any other change means we want to take the p2 version
+                yield file
+
     def putcommit(self, files, copies, parents, commit, source, revmap, full,
                   cleanp2):
         files = dict(files)
 
         def getfilectx(repo, memctx, f):
-            if p2ctx and f in cleanp2 and f not in copies:
+            if p2ctx and f in p2files and f not in copies:
                 self.ui.debug('reusing %s from p2\n' % f)
                 return p2ctx[f]
             try:
@@ -255,6 +295,7 @@
         while parents:
             p1 = p2
             p2 = parents.pop(0)
+            p1ctx = self.repo[p1]
             p2ctx = None
             if p2 != nullid:
                 p2ctx = self.repo[p2]
@@ -262,6 +303,13 @@
             if full:
                 fileset.update(self.repo[p1])
                 fileset.update(self.repo[p2])
+
+            if p2ctx:
+                p2files = set(cleanp2)
+                for file in self._calculatemergedfiles(source, p1ctx, p2ctx):
+                    p2files.add(file)
+                    fileset.add(file)
+
             ctx = context.memctx(self.repo, (p1, p2), text, fileset,
                                  getfilectx, commit.author, commit.date, extra)
 
--- a/hgext/highlight/highlight.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/hgext/highlight/highlight.py	Tue Aug 18 18:38:56 2015 -0500
@@ -49,7 +49,12 @@
         try:
             lexer = guess_lexer(text[:1024], stripnl=False)
         except (ClassNotFound, ValueError):
-            lexer = TextLexer(stripnl=False)
+            # Don't highlight unknown files
+            return
+
+    # Don't highlight text files
+    if isinstance(lexer, TextLexer):
+        return
 
     formatter = HtmlFormatter(nowrap=True, style=style)
 
--- a/hgext/histedit.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/hgext/histedit.py	Tue Aug 18 18:38:56 2015 -0500
@@ -778,7 +778,7 @@
         return
     elif goal == 'abort':
         state.read()
-        mapping, tmpnodes, leafs, _ntm = processreplacement(state)
+        tmpnodes, leafs = newnodestoabort(state)
         ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
 
         # Recover our old commits if necessary
@@ -791,13 +791,9 @@
             os.remove(backupfile)
 
         # check whether we should update away
-        parentnodes = [c.node() for c in repo[None].parents()]
-        for n in leafs | set([state.parentctxnode]):
-            if n in parentnodes:
-                hg.clean(repo, state.topmost)
-                break
-        else:
-            pass
+        if repo.unfiltered().revs('parents() and (%n  or %ln::)',
+                                  state.parentctxnode, leafs | tmpnodes):
+            hg.clean(repo, state.topmost)
         cleanupnode(ui, repo, 'created', tmpnodes)
         cleanupnode(ui, repo, 'temp', leafs)
         state.clear()
@@ -1009,6 +1005,25 @@
                 hint=_('do you want to use the drop action?'))
     return parsed
 
+def newnodestoabort(state):
+    """process the list of replacements to return
+
+    1) the list of final node
+    2) the list of temporary node
+
+    This meant to be used on abort as less data are required in this case.
+    """
+    replacements = state.replacements
+    allsuccs = set()
+    replaced = set()
+    for rep in replacements:
+        allsuccs.update(rep[1])
+        replaced.add(rep[0])
+    newnodes = allsuccs - replaced
+    tmpnodes = allsuccs & replaced
+    return newnodes, tmpnodes
+
+
 def processreplacement(state):
     """process the list of replacements to return
 
@@ -1019,15 +1034,15 @@
     allsuccs = set()
     replaced = set()
     fullmapping = {}
-    # initialise basic set
-    # fullmapping record all operation recorded in replacement
+    # initialize basic set
+    # fullmapping records all operations recorded in replacement
     for rep in replacements:
         allsuccs.update(rep[1])
         replaced.add(rep[0])
         fullmapping.setdefault(rep[0], set()).update(rep[1])
     new = allsuccs - replaced
     tmpnodes = allsuccs & replaced
-    # Reduce content fullmapping  into direct relation between original nodes
+    # Reduce content fullmapping into direct relation between original nodes
     # and final node created during history edition
     # Dropped changeset are replaced by an empty list
     toproceed = set(fullmapping)
@@ -1113,6 +1128,10 @@
     lock = None
     try:
         lock = repo.lock()
+        # do not let filtering get in the way of the cleanse
+        # we should probably get ride of obsolescence marker created during the
+        # histedit, but we currently do not have such information.
+        repo = repo.unfiltered()
         # Find all node that need to be stripped
         # (we hg %lr instead of %ln to silently ignore unknown item
         nm = repo.changelog.nodemap
--- a/mercurial/ancestor.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/ancestor.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,12 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 import collections
 import heapq
-from node import nullrev
+
+from .node import nullrev
 
 def commonancestorsheads(pfunc, *nodes):
     """Returns a set with the heads of all common ancestors of all nodes,
--- a/mercurial/archival.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/archival.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,14 +5,27 @@
 # 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 _
-import match as matchmod
-import cmdutil
-import scmutil, util, encoding
-import cStringIO, os, tarfile, time, zipfile
-import zlib, gzip
+from __future__ import absolute_import
+
+import cStringIO
+import gzip
+import os
 import struct
-import error
+import tarfile
+import time
+import zipfile
+import zlib
+
+from .i18n import _
+
+from . import (
+    cmdutil,
+    encoding,
+    error,
+    match as matchmod,
+    scmutil,
+    util,
+)
 
 # from unzip source code:
 _UNX_IFREG = 0x8000
--- a/mercurial/bookmarks.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/bookmarks.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,11 +5,22 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
+import errno
 import os
-from mercurial.i18n import _
-from mercurial.node import hex, bin
-from mercurial import encoding, util, obsolete, lock as lockmod
-import errno
+
+from .i18n import _
+from .node import (
+    bin,
+    hex,
+)
+from . import (
+    encoding,
+    lock as lockmod,
+    obsolete,
+    util,
+)
 
 class bmstore(dict):
     """Storage for bookmarks.
@@ -79,6 +90,11 @@
         can be copied back on rollback.
         '''
         repo = self._repo
+        if (repo.ui.configbool('devel', 'all-warnings')
+                or repo.ui.configbool('devel', 'check-locks')):
+            l = repo._wlockref and repo._wlockref()
+            if l is None or not l.held:
+                repo.ui.develwarn('bookmarks write with no wlock')
         self._writerepo(repo)
         repo.invalidatevolatilesets()
 
--- a/mercurial/branchmap.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/branchmap.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,13 +5,28 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from node import bin, hex, nullid, nullrev
-import encoding
-import scmutil
-import util
+from __future__ import absolute_import
+
+import array
+import struct
 import time
-from array import array
-from struct import calcsize, pack, unpack
+
+from .node import (
+    bin,
+    hex,
+    nullid,
+    nullrev,
+)
+from . import (
+    encoding,
+    scmutil,
+    util,
+)
+
+array = array.array
+calcsize = struct.calcsize
+pack = struct.pack
+unpack = struct.unpack
 
 def _filename(repo):
     """name of a branchcache file for a given repo or repoview"""
--- a/mercurial/bundle2.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/bundle2.py	Tue Aug 18 18:38:56 2015 -0500
@@ -145,19 +145,25 @@
 preserve.
 """
 
+from __future__ import absolute_import
+
 import errno
-import sys
-import util
-import struct
-import urllib
+import re
 import string
-import obsolete
-import pushkey
-import url
-import re
+import struct
+import sys
+import urllib
 
-import changegroup, error, tags
-from i18n import _
+from .i18n import _
+from . import (
+    changegroup,
+    error,
+    obsolete,
+    pushkey,
+    tags,
+    url,
+    util,
+)
 
 _pack = struct.pack
 _unpack = struct.unpack
@@ -1233,7 +1239,7 @@
     # we need to make sure we trigger the creation of a transaction object used
     # for the whole processing scope.
     op.gettransaction()
-    import exchange
+    from . import exchange
     cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
     if not isinstance(cg, changegroup.cg1unpacker):
         raise util.Abort(_('%s: not a bundle version 1.0') %
--- a/mercurial/bundlerepo.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/bundlerepo.py	Tue Aug 18 18:38:56 2015 -0500
@@ -11,12 +11,33 @@
 were part of the actual repository.
 """
 
-from node import nullid
-from i18n import _
-import os, tempfile, shutil
-import changegroup, util, mdiff, discovery, cmdutil, scmutil, exchange
-import localrepo, changelog, manifest, filelog, revlog, error, phases, bundle2
-import pathutil
+from __future__ import absolute_import
+
+import os
+import shutil
+import tempfile
+
+from .i18n import _
+from .node import nullid
+
+from . import (
+    bundle2,
+    changegroup,
+    changelog,
+    cmdutil,
+    discovery,
+    error,
+    exchange,
+    filelog,
+    localrepo,
+    manifest,
+    mdiff,
+    pathutil,
+    phases,
+    revlog,
+    scmutil,
+    util,
+)
 
 class bundlerevlog(revlog.revlog):
     def __init__(self, opener, indexfile, bundle, linkmapper):
--- a/mercurial/changegroup.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/changegroup.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,12 +5,32 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
+import bz2
+import os
+import struct
+import tempfile
 import weakref
-from i18n import _
-from node import nullrev, nullid, hex, short
-import mdiff, util, dagutil
-import struct, os, bz2, zlib, tempfile
-import discovery, error, phases, branchmap
+import zlib
+
+from .i18n import _
+from .node import (
+    hex,
+    nullid,
+    nullrev,
+    short,
+)
+
+from . import (
+    branchmap,
+    dagutil,
+    discovery,
+    error,
+    mdiff,
+    phases,
+    util,
+)
 
 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
@@ -103,7 +123,7 @@
         cleanup = filename
 
         if bundletype == "HG20":
-            import bundle2
+            from . import bundle2
             bundle = bundle2.bundle20(ui)
             part = bundle.newpart('changegroup', data=cg.getchunks())
             part.addparam('version', cg.version)
--- a/mercurial/changelog.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/changelog.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,22 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from node import bin, hex, nullid
-from i18n import _
-import util, error, revlog, encoding
+from __future__ import absolute_import
+
+from .i18n import _
+from .node import (
+    bin,
+    hex,
+    nullid,
+)
+
+from . import (
+    encoding,
+    error,
+    revlog,
+    revset,
+    util,
+)
 
 _defaultextra = {'branch': 'default'}
 
@@ -172,6 +185,10 @@
         self.rev(self.node(0))
         return self._nodecache
 
+    def reachableroots(self, minroot, heads, roots, includepath=False):
+        return revset.baseset(sorted(
+            self.index.reachableroots(minroot, heads, roots, includepath)))
+
     def headrevs(self):
         if self.filteredrevs:
             try:
--- a/mercurial/cmdutil.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/cmdutil.py	Tue Aug 18 18:38:56 2015 -0500
@@ -10,7 +10,7 @@
 import os, sys, errno, re, tempfile, cStringIO, shutil
 import util, scmutil, templater, patch, error, templatekw, revlog, copies
 import match as matchmod
-import context, repair, graphmod, revset, phases, obsolete, pathutil
+import repair, graphmod, revset, phases, obsolete, pathutil
 import changelog
 import bookmarks
 import encoding
@@ -848,6 +848,8 @@
     :updatefunc: a function that update a repo to a given node
                  updatefunc(<repo>, <node>)
     """
+    # avoid cycle context -> subrepo -> cmdutil
+    import context
     tmpname, message, user, date, branch, nodeid, p1, p2 = \
         patch.extract(ui, hunk)
 
@@ -2464,6 +2466,9 @@
     return commitfunc(ui, repo, message, matcher, opts)
 
 def amend(ui, repo, commitfunc, old, extra, pats, opts):
+    # avoid cycle context -> subrepo -> cmdutil
+    import context
+
     # amend will reuse the existing user if not specified, but the obsolete
     # marker creation requires that the current user's name is specified.
     if obsolete.isenabled(repo, obsolete.createmarkersopt):
--- a/mercurial/commands.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/commands.py	Tue Aug 18 18:38:56 2015 -0500
@@ -13,7 +13,7 @@
 import hg, scmutil, util, revlog, copies, error, bookmarks
 import patch, help, encoding, templatekw, discovery
 import archival, changegroup, cmdutil, hbisect
-import sshserver, hgweb, commandserver
+import sshserver, hgweb
 import extensions
 from hgweb import server as hgweb_server
 import merge as mergemod
@@ -2700,9 +2700,12 @@
               pa.distance(pb), rel))
 
 @command('debugrebuilddirstate|debugrebuildstate',
-    [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
+    [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
+     ('', 'minimal', None, _('only rebuild files that are inconsistent with '
+                             'the working copy parent')),
+    ],
     _('[-r REV]'))
-def debugrebuilddirstate(ui, repo, rev):
+def debugrebuilddirstate(ui, repo, rev, **opts):
     """rebuild the dirstate as it would look like for the given revision
 
     If no revision is specified the first current parent will be used.
@@ -2711,13 +2714,33 @@
     The actual working directory content or existing dirstate
     information such as adds or removes is not considered.
 
+    ``minimal`` will only rebuild the dirstate status for files that claim to be
+    tracked but are not in the parent manifest, or that exist in the parent
+    manifest but are not in the dirstate. It will not change adds, removes, or
+    modified files that are in the working copy parent.
+
     One use of this command is to make the next :hg:`status` invocation
     check the actual file content.
     """
     ctx = scmutil.revsingle(repo, rev)
     wlock = repo.wlock()
     try:
-        repo.dirstate.rebuild(ctx.node(), ctx.manifest())
+        dirstate = repo.dirstate
+
+        # See command doc for what minimal does.
+        if opts.get('minimal'):
+            dirstatefiles = set(dirstate)
+            ctxfiles = set(ctx.manifest().keys())
+            for file in (dirstatefiles | ctxfiles):
+                indirstate = file in dirstatefiles
+                inctx = file in ctxfiles
+
+                if indirstate and not inctx and dirstate[file] != 'a':
+                    dirstate.drop(file)
+                elif inctx and not indirstate:
+                    dirstate.normallookup(file)
+        else:
+            dirstate.rebuild(ctx.node(), ctx.manifest())
     finally:
         wlock.release()
 
@@ -2933,7 +2956,7 @@
     expansion.
     """
     if ui.verbose:
-        tree = revset.parse(expr)
+        tree = revset.parse(expr, lookup=repo.__contains__)
         ui.note(revset.prettyformat(tree), "\n")
         newtree = revset.findaliases(ui, tree)
         if newtree != tree:
@@ -2945,7 +2968,7 @@
         if opts["optimize"]:
             weight, optimizedtree = revset.optimize(newtree, True)
             ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
-    func = revset.match(ui, expr)
+    func = revset.match(ui, expr, repo)
     revs = func(repo)
     if ui.verbose:
         ui.note("* set:\n", revset.prettyformatset(revs), "\n")
@@ -5713,6 +5736,7 @@
         s.serve_forever()
 
     if opts["cmdserver"]:
+        import commandserver
         service = commandserver.createservice(ui, repo, opts)
         return cmdutil.service(opts, initfn=service.init, runfn=service.run)
 
@@ -6248,7 +6272,7 @@
                         raise util.Abort(_("tag '%s' is not a global tag") % n)
                     else:
                         raise util.Abort(_("tag '%s' is not a local tag") % n)
-            rev_ = nullid
+            rev_ = 'null'
             if not message:
                 # we don't translate commit messages
                 message = 'Removed tag %s' % ', '.join(names)
--- a/mercurial/config.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/config.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,16 @@
 # 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 _
-import error, util
-import os, errno
+from __future__ import absolute_import
+
+import errno
+import os
+
+from .i18n import _
+from . import (
+    error,
+    util,
+)
 
 class config(object):
     def __init__(self, data=None, includepaths=[]):
--- a/mercurial/copies.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/copies.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,15 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import util, pathutil
+from __future__ import absolute_import
+
 import heapq
 
+from . import (
+    pathutil,
+    util,
+)
+
 def _findlimit(repo, a, b):
     """
     Find the last revision that needs to be checked to ensure that a full
@@ -185,6 +191,9 @@
     return cm
 
 def _backwardrenames(a, b):
+    if a._repo.ui.configbool('experimental', 'disablecopytrace'):
+        return {}
+
     # Even though we're not taking copies into account, 1:n rename situations
     # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
     # arbitrarily pick one of the renames.
@@ -258,6 +267,12 @@
     if c2.node() is None and c1.node() == repo.dirstate.p1():
         return repo.dirstate.copies(), {}, {}, {}
 
+    # Copy trace disabling is explicitly below the node == p1 logic above
+    # because the logic above is required for a simple copy to be kept across a
+    # rebase.
+    if repo.ui.configbool('experimental', 'disablecopytrace'):
+        return {}, {}, {}, {}
+
     limit = _findlimit(repo, c1.rev(), c2.rev())
     if limit is None:
         # no common ancestor, no copies
@@ -507,7 +522,12 @@
     copies between fromrev and rev.
     '''
     exclude = {}
-    if skiprev is not None:
+    if (skiprev is not None and
+        not repo.ui.configbool('experimental', 'disablecopytrace')):
+        # disablecopytrace skips this line, but not the entire function because
+        # the line below is O(size of the repo) during a rebase, while the rest
+        # of the function is much faster (and is required for carrying copy
+        # metadata across the rebase anyway).
         exclude = pathcopies(repo[fromrev], repo[skiprev])
     for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
         # copies.pathcopies returns backward renames, so dst might not
--- a/mercurial/crecord.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/crecord.py	Tue Aug 18 18:38:56 2015 -0500
@@ -8,11 +8,23 @@
 # This code is based on the Mark Edgington's crecord extension.
 # (Itself based on Bryan O'Sullivan's record extension.)
 
-from i18n import _
-import patch as patchmod
-import util, encoding
+from __future__ import absolute_import
 
-import os, re, sys, struct, signal, tempfile, locale, cStringIO
+import cStringIO
+import locale
+import os
+import re
+import signal
+import struct
+import sys
+import tempfile
+
+from .i18n import _
+from . import (
+    encoding,
+    patch as patchmod,
+    util,
+)
 
 # This is required for ncurses to display non-ASCII characters in default user
 # locale encoding correctly.  --immerrr
@@ -21,7 +33,8 @@
 # os.name is one of: 'posix', 'nt', 'dos', 'os2', 'mac', or 'ce'
 if os.name == 'posix':
     import curses
-    import fcntl, termios
+    import fcntl
+    import termios
 else:
     # I have no idea if wcurses works with crecord...
     try:
--- a/mercurial/dagparser.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/dagparser.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,13 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import re, string
-import util
-from i18n import _
+from __future__ import absolute_import
+
+import re
+import string
+
+from .i18n import _
+from . import util
 
 def parsedag(desc):
     '''parses a DAG from a concise textual description; generates events
--- a/mercurial/dagutil.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/dagutil.py	Tue Aug 18 18:38:56 2015 -0500
@@ -6,9 +6,10 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from node import nullrev
-from i18n import _
+from __future__ import absolute_import
 
+from .i18n import _
+from .node import nullrev
 
 class basedag(object):
     '''generic interface for DAGs
--- a/mercurial/demandimport.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/demandimport.py	Tue Aug 18 18:38:56 2015 -0500
@@ -24,8 +24,11 @@
   b = __import__(a)
 '''
 
-import os, sys
-from contextlib import contextmanager
+from __future__ import absolute_import
+
+import contextlib
+import os
+import sys
 
 # __builtin__ in Python 2, builtins in Python 3.
 try:
@@ -33,26 +36,21 @@
 except ImportError:
     import builtins
 
+contextmanager = contextlib.contextmanager
+
 _origimport = __import__
 
 nothing = object()
 
-try:
-    # Python 3 doesn't have relative imports nor level -1.
-    level = -1
-    if sys.version_info[0] >= 3:
-        level = 0
-    _origimport(builtins.__name__, {}, {}, None, level)
-except TypeError: # no level argument
-    def _import(name, globals, locals, fromlist, level):
-        "call _origimport with no level argument"
-        return _origimport(name, globals, locals, fromlist)
-else:
-    _import = _origimport
+# Python 3 doesn't have relative imports nor level -1.
+level = -1
+if sys.version_info[0] >= 3:
+    level = 0
+_import = _origimport
 
-def _hgextimport(importfunc, name, globals, *args):
+def _hgextimport(importfunc, name, globals, *args, **kwargs):
     try:
-        return importfunc(name, globals, *args)
+        return importfunc(name, globals, *args, **kwargs)
     except ImportError:
         if not globals:
             raise
@@ -63,7 +61,7 @@
         if nameroot != contextroot:
             raise
         # retry to import with "hgext_" prefix
-        return importfunc(hgextname, globals, *args)
+        return importfunc(hgextname, globals, *args, **kwargs)
 
 class _demandmod(object):
     """module demand-loader and proxy"""
@@ -135,15 +133,44 @@
                 return locals[base]
         return _demandmod(name, globals, locals, level)
     else:
-        if level != -1:
-            # from . import b,c,d or from .a import b,c,d
-            return _origimport(name, globals, locals, fromlist, level)
+        # There is a fromlist.
         # from a import b,c,d
+        # from . import b,c,d
+        # from .a import b,c,d
+
+        # level == -1: relative and absolute attempted (Python 2 only).
+        # level >= 0: absolute only (Python 2 w/ absolute_import and Python 3).
+        # The modern Mercurial convention is to use absolute_import everywhere,
+        # so modern Mercurial code will have level >= 0.
+
+        if level >= 0:
+            # Mercurial's enforced import style does not use
+            # "from a import b,c,d" or "from .a import b,c,d" syntax. In
+            # addition, this appears to be giving errors with some modules
+            # for unknown reasons. Since we shouldn't be using this syntax
+            # much, work around the problems.
+            if name:
+                return _hgextimport(_origimport, name, globals, locals,
+                                    fromlist, level)
+
+            mod = _hgextimport(_origimport, name, globals, locals, level=level)
+            for x in fromlist:
+                # Missing symbols mean they weren't defined in the module
+                # itself which means they are sub-modules.
+                if getattr(mod, x, nothing) is nothing:
+                    setattr(mod, x,
+                            _demandmod(x, mod.__dict__, locals, level=level))
+
+            return mod
+
+        # But, we still need to support lazy loading of standard library and 3rd
+        # party modules. So handle level == -1.
         mod = _hgextimport(_origimport, name, globals, locals)
         # recurse down the module chain
         for comp in name.split('.')[1:]:
             if getattr(mod, comp, nothing) is nothing:
-                setattr(mod, comp, _demandmod(comp, mod.__dict__, mod.__dict__))
+                setattr(mod, comp,
+                        _demandmod(comp, mod.__dict__, mod.__dict__))
             mod = getattr(mod, comp)
         for x in fromlist:
             # set requested submodules for demand load
@@ -152,6 +179,7 @@
         return mod
 
 ignore = [
+    '__future__',
     '_hashlib',
     '_xmlplus',
     'fcntl',
--- a/mercurial/discovery.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/discovery.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,10 +5,23 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from node import nullid, short
-from i18n import _
-import util, setdiscovery, treediscovery, phases, obsolete, bookmarks
-import branchmap
+from __future__ import absolute_import
+
+from .i18n import _
+from .node import (
+    nullid,
+    short,
+)
+
+from . import (
+    bookmarks,
+    branchmap,
+    obsolete,
+    phases,
+    setdiscovery,
+    treediscovery,
+    util,
+)
 
 def findcommonincoming(repo, remote, heads=None, force=False):
     """Return a tuple (common, anyincoming, heads) used to identify the common
--- a/mercurial/dispatch.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/dispatch.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,13 +5,37 @@
 # 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 _
-import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
+from __future__ import absolute_import
+
+import atexit
 import difflib
-import util, commands, hg, fancyopts, extensions, hook, error
-import cmdutil, encoding
-import ui as uimod
-import demandimport
+import errno
+import os
+import pdb
+import re
+import shlex
+import signal
+import socket
+import sys
+import time
+import traceback
+
+
+from .i18n import _
+
+from . import (
+    cmdutil,
+    commands,
+    demandimport,
+    encoding,
+    error,
+    extensions,
+    fancyopts,
+    hg,
+    hook,
+    ui as uimod,
+    util,
+)
 
 class request(object):
     def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
@@ -909,7 +933,7 @@
         format = 'text'
 
     try:
-        from mercurial import lsprof
+        from . import lsprof
     except ImportError:
         raise util.Abort(_(
             'lsprof not available - install from '
@@ -922,7 +946,7 @@
         p.disable()
 
         if format == 'kcachegrind':
-            import lsprofcalltree
+            from . import lsprofcalltree
             calltree = lsprofcalltree.KCacheGrind(p)
             calltree.output(fp)
         else:
--- a/mercurial/error.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/error.py	Tue Aug 18 18:38:56 2015 -0500
@@ -11,6 +11,8 @@
 imports.
 """
 
+from __future__ import absolute_import
+
 # Do not import anything here, please
 
 class HintException(Exception):
@@ -32,7 +34,7 @@
         # Python 2.6+ complain about the 'message' property being deprecated
         self.lookupmessage = message
         if isinstance(name, str) and len(name) == 20:
-            from node import short
+            from .node import short
             name = short(name)
         RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
 
@@ -78,7 +80,7 @@
     """Exception raised when a {rev,file}set references an unknown identifier"""
 
     def __init__(self, function, symbols):
-        from i18n import _
+        from .i18n import _
         ParseError.__init__(self, _("unknown identifier: %s") % function)
         self.function = function
         self.symbols = symbols
@@ -173,7 +175,7 @@
     """
 
     def __init__(self, filename, node, tombstone):
-        from node import short
+        from .node import short
         RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
         self.tombstone = tombstone
 
--- a/mercurial/exchange.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/exchange.py	Tue Aug 18 18:38:56 2015 -0500
@@ -571,7 +571,7 @@
 
 @b2partsgenerator('bookmarks')
 def _pushb2bookmarks(pushop, bundler):
-    """handle phase push through bundle2"""
+    """handle bookmark push through bundle2"""
     if 'bookmarks' in pushop.stepsdone:
         return
     b2caps = bundle2.bundle2caps(pushop.remote)
@@ -1419,7 +1419,7 @@
                 op = bundle2.bundleoperation(repo, lambda: tr,
                                              captureoutput=captureoutput)
                 try:
-                    r = bundle2.processbundle(repo, cg, op=op)
+                    op = bundle2.processbundle(repo, cg, op=op)
                 finally:
                     r = op.reply
                     if captureoutput and r is not None:
--- a/mercurial/extensions.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/extensions.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,21 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import imp, os
-import util, cmdutil, error
-from i18n import _, gettext
+from __future__ import absolute_import
+
+import imp
+import os
+
+from .i18n import (
+    _,
+    gettext,
+)
+
+from . import (
+    cmdutil,
+    error,
+    util,
+)
 
 _extensions = {}
 _aftercallbacks = {}
--- a/mercurial/fancyopts.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/fancyopts.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,12 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 import getopt
-import util
-from i18n import _
+
+from .i18n import _
+from . import util
 
 def gnugetopt(args, options, longoptions):
     """Parse options mostly like getopt.gnu_getopt.
--- a/mercurial/filelog.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/filelog.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,8 +5,16 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import error, mdiff, revlog
-import re, struct
+from __future__ import absolute_import
+
+import re
+import struct
+
+from . import (
+    error,
+    mdiff,
+    revlog,
+)
 
 _mdre = re.compile('\1\n')
 def parsemeta(text):
--- a/mercurial/filemerge.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/filemerge.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,11 +5,25 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from node import short
-from i18n import _
-import util, simplemerge, match, error, templater, templatekw
-import os, tempfile, re, filecmp
-import tagmerge
+from __future__ import absolute_import
+
+import filecmp
+import os
+import re
+import tempfile
+
+from .i18n import _
+from .node import short
+
+from . import (
+    error,
+    match,
+    simplemerge,
+    tagmerge,
+    templatekw,
+    templater,
+    util,
+)
 
 def _toolstr(ui, tool, part, default=""):
     return ui.config("merge-tools", tool + "." + part, default)
--- a/mercurial/fileset.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/fileset.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,17 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 import re
-import parser, error, util, merge
-from i18n import _
+
+from .i18n import _
+from . import (
+    error,
+    merge,
+    parser,
+    util,
+)
 
 elements = {
     # token-type: binding-strength, primary, prefix, infix, suffix
@@ -410,7 +418,7 @@
         # i18n: "subrepo" is a keyword
         pat = getstring(x, _("subrepo requires a pattern or no arguments"))
 
-        import match as matchmod # avoid circular import issues
+        from . import match as matchmod # avoid circular import issues
         fast = not matchmod.patkind(pat)
         if fast:
             def m(s):
--- a/mercurial/formatter.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/formatter.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,13 +5,23 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 import cPickle
-from node import hex, short
-from i18n import _
-import encoding, util
-import templater
 import os
 
+from .i18n import _
+from .node import (
+    hex,
+    short,
+)
+
+from . import (
+    encoding,
+    templater,
+    util,
+)
+
 class baseformatter(object):
     def __init__(self, ui, topic, opts):
         self._ui = ui
--- a/mercurial/graphmod.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/graphmod.py	Tue Aug 18 18:38:56 2015 -0500
@@ -17,11 +17,16 @@
 Data depends on type.
 """
 
-from mercurial.node import nullrev
-import util
+from __future__ import absolute_import
 
 import heapq
 
+from .node import nullrev
+from . import (
+    revset,
+    util,
+)
+
 CHANGESET = 'C'
 
 def groupbranchiter(revs, parentsfunc, firstbranch=()):
@@ -233,8 +238,6 @@
     if not revs:
         return
 
-    cl = repo.changelog
-    lowestrev = revs.min()
     gpcache = {}
 
     if repo.ui.configbool('experimental', 'graph-group-branches', False):
@@ -256,7 +259,7 @@
         for mpar in mpars:
             gp = gpcache.get(mpar)
             if gp is None:
-                gp = gpcache[mpar] = grandparent(cl, lowestrev, revs, mpar)
+                gp = gpcache[mpar] = revset.reachableroots(repo, revs, [mpar])
             if not gp:
                 parents.append(mpar)
             else:
@@ -354,24 +357,6 @@
         yield (cur, type, data, (col, color), edges)
         seen = next
 
-def grandparent(cl, lowestrev, roots, head):
-    """Return all ancestors of head in roots which revision is
-    greater or equal to lowestrev.
-    """
-    pending = set([head])
-    seen = set()
-    kept = set()
-    llowestrev = max(nullrev, lowestrev)
-    while pending:
-        r = pending.pop()
-        if r >= llowestrev and r not in seen:
-            if r in roots:
-                kept.add(r)
-            else:
-                pending.update([p for p in cl.parentrevs(r)])
-            seen.add(r)
-    return sorted(kept)
-
 def asciiedges(type, char, lines, seen, rev, parents):
     """adds edge info to changelog DAG walk suitable for ascii()"""
     if rev not in seen:
--- a/mercurial/hbisect.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/hbisect.py	Tue Aug 18 18:38:56 2015 -0500
@@ -8,12 +8,20 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 import collections
 import os
-import error
-from i18n import _
-from node import short, hex
-import util
+
+from .i18n import _
+from .node import (
+    hex,
+    short,
+)
+from . import (
+    error,
+    util,
+)
 
 def bisect(changelog, state):
     """find the next node (if any) for testing during a bisect search.
--- a/mercurial/hg.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/hg.py	Tue Aug 18 18:38:56 2015 -0500
@@ -6,17 +6,41 @@
 # 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 lock import release
-from node import nullid
+from __future__ import absolute_import
+
+import errno
+import os
+import shutil
+
+from .i18n import _
+from .node import nullid
 
-import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
-import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
-import cmdutil, discovery, repoview, exchange
-import ui as uimod
-import merge as mergemod
-import verify as verifymod
-import errno, os, shutil
+from . import (
+    bookmarks,
+    bundlerepo,
+    cmdutil,
+    discovery,
+    error,
+    exchange,
+    extensions,
+    httppeer,
+    localrepo,
+    lock,
+    merge as mergemod,
+    node,
+    phases,
+    repoview,
+    scmutil,
+    sshpeer,
+    statichttprepo,
+    ui as uimod,
+    unionrepo,
+    url,
+    util,
+    verify as verifymod,
+)
+
+release = lock.release
 
 def _local(path):
     path = util.expandpath(util.urllocalpath(path))
--- a/mercurial/hgweb/webutil.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/hgweb/webutil.py	Tue Aug 18 18:38:56 2015 -0500
@@ -203,7 +203,7 @@
     path = path.lstrip('/')
     return pathutil.canonpath(repo.root, '', path)
 
-def changeidctx (repo, changeid):
+def changeidctx(repo, changeid):
     try:
         ctx = repo[changeid]
     except error.RepoError:
@@ -212,11 +212,11 @@
 
     return ctx
 
-def changectx (repo, req):
+def changectx(repo, req):
     changeid = "tip"
     if 'node' in req.form:
         changeid = req.form['node'][0]
-        ipos=changeid.find(':')
+        ipos = changeid.find(':')
         if ipos != -1:
             changeid = changeid[(ipos + 1):]
     elif 'manifest' in req.form:
@@ -227,7 +227,7 @@
 def basechangectx(repo, req):
     if 'node' in req.form:
         changeid = req.form['node'][0]
-        ipos=changeid.find(':')
+        ipos = changeid.find(':')
         if ipos != -1:
             changeid = changeid[:ipos]
             return changeidctx(repo, changeid)
--- a/mercurial/hook.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/hook.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,19 @@
 # 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 _
-import os, sys, time
-import extensions, util, demandimport, error
+from __future__ import absolute_import
+
+import os
+import sys
+import time
+
+from .i18n import _
+from . import (
+    demandimport,
+    error,
+    extensions,
+    util,
+)
 
 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
     '''call python hook. hook is callable object, looked up as
--- a/mercurial/httppeer.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/httppeer.py	Tue Aug 18 18:38:56 2015 -0500
@@ -6,12 +6,28 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from node import nullid
-from i18n import _
+from __future__ import absolute_import
+
+import errno
+import httplib
+import os
+import socket
 import tempfile
-import changegroup, statichttprepo, error, httpconnection, url, util, wireproto
-import os, urllib, urllib2, zlib, httplib
-import errno, socket
+import urllib
+import urllib2
+import zlib
+
+from .i18n import _
+from .node import nullid
+from . import (
+    changegroup,
+    error,
+    httpconnection,
+    statichttprepo,
+    url,
+    util,
+    wireproto,
+)
 
 def zgenerator(f):
     zd = zlib.decompressobj()
--- a/mercurial/i18n.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/i18n.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,8 +5,14 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import encoding
-import gettext as gettextmod, sys, os, locale
+from __future__ import absolute_import
+
+import gettext as gettextmod
+import locale
+import os
+import sys
+
+from . import encoding
 
 # modelled after templater.templatepath:
 if getattr(sys, 'frozen', None) is not None:
--- a/mercurial/lock.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/lock.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,10 +5,19 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import util, error
-import errno, os, socket, time
+from __future__ import absolute_import
+
+import errno
+import os
+import socket
+import time
 import warnings
 
+from . import (
+    error,
+    util,
+)
+
 class lock(object):
     '''An advisory lock held by one process to control access to a set
     of files.  Non-cooperating processes or incorrectly written scripts
--- a/mercurial/mail.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/mail.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,10 +5,22 @@
 # 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 _
-import util, encoding, sslutil
-import os, smtplib, socket, quopri, time, sys
+from __future__ import absolute_import
+
 import email
+import os
+import quopri
+import smtplib
+import socket
+import sys
+import time
+
+from .i18n import _
+from . import (
+    encoding,
+    sslutil,
+    util,
+)
 
 _oldheaderinit = email.Header.Header.__init__
 def _unifiedheaderinit(self, *args, **kw):
--- a/mercurial/match.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/match.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,17 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import copy, os, re
-import util, pathutil
-from i18n import _
+from __future__ import absolute_import
+
+import copy
+import os
+import re
+
+from .i18n import _
+from . import (
+    pathutil,
+    util,
+)
 
 propertycache = util.propertycache
 
--- a/mercurial/merge.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/merge.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,13 +5,29 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
+import errno
+import os
+import shutil
 import struct
 
-from node import nullid, nullrev, hex, bin
-from i18n import _
-from mercurial import obsolete
-import error as errormod, util, filemerge, copies, subrepo, worker
-import errno, os, shutil
+from .i18n import _
+from .node import (
+    bin,
+    hex,
+    nullid,
+    nullrev,
+)
+from . import (
+    copies,
+    error as errormod,
+    filemerge,
+    obsolete,
+    subrepo,
+    util,
+    worker,
+)
 
 _pack = struct.pack
 _unpack = struct.unpack
--- a/mercurial/minirst.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/minirst.py	Tue Aug 18 18:38:56 2015 -0500
@@ -18,11 +18,16 @@
 when adding support for new constructs.
 """
 
-import re
-import util, encoding
-from i18n import _
+from __future__ import absolute_import
 
 import cgi
+import re
+
+from .i18n import _
+from . import (
+    encoding,
+    util,
+)
 
 def section(s):
     return "%s\n%s\n\n" % (s, "\"" * encoding.colwidth(s))
--- a/mercurial/namespaces.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/namespaces.py	Tue Aug 18 18:38:56 2015 -0500
@@ -1,6 +1,10 @@
-from i18n import _
-from mercurial import util
-import templatekw
+from __future__ import absolute_import
+
+from .i18n import _
+from . import (
+    templatekw,
+    util,
+)
 
 def tolist(val):
     """
--- a/mercurial/node.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/node.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,6 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 import binascii
 
 nullrev = -1
--- a/mercurial/parser.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/parser.py	Tue Aug 18 18:38:56 2015 -0500
@@ -16,8 +16,10 @@
 # an action is a tree node name, a tree label, and an optional match
 # __call__(program) parses program into a labeled tree
 
-import error
-from i18n import _
+from __future__ import absolute_import
+
+from .i18n import _
+from . import error
 
 class parser(object):
     def __init__(self, elements, methods=None):
--- a/mercurial/parsers.c	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/parsers.c	Tue Aug 18 18:38:56 2015 -0500
@@ -1105,6 +1105,153 @@
 		phases[i] = phases[parent_2];
 }
 
+static PyObject *reachableroots(indexObject *self, PyObject *args)
+{
+
+	/* Input */
+	long minroot;
+	PyObject *includepatharg = NULL;
+	int includepath = 0;
+	/* heads is a list */
+	PyObject *heads = NULL;
+	/* roots is a set */
+	PyObject *roots = NULL;
+	PyObject *reachable = NULL;
+
+	PyObject *val;
+	Py_ssize_t len = index_length(self) - 1;
+	long revnum;
+	Py_ssize_t k;
+	Py_ssize_t i;
+	Py_ssize_t l;
+	int r;
+	int minidx;
+	int parents[2];
+
+	/* Internal data structure:
+	 * tovisit: array of length len+1 (all revs + nullrev), filled upto lentovisit
+	 * revstates: array of length len+1 (all revs + nullrev) */
+	int *tovisit = NULL;
+	long lentovisit = 0;
+	enum { RS_SEEN = 1 };
+	char *revstates = NULL;
+
+	/* Get arguments */
+	if (!PyArg_ParseTuple(args, "lO!O!O!", &minroot, &PyList_Type, &heads,
+				&PySet_Type, &roots, &PyBool_Type, &includepatharg))
+		goto bail;
+
+	if (includepatharg == Py_True)
+		includepath = 1;
+
+	/* Initialize return set */
+	reachable = PySet_New(NULL);
+	if (reachable == NULL) {
+		PyErr_NoMemory();
+		goto bail;
+	}
+
+	/* Initialize internal datastructures */
+	tovisit = (int *)malloc((len + 1) * sizeof(int));
+	if (tovisit == NULL) {
+		PyErr_NoMemory();
+		goto bail;
+	}
+
+	revstates = (char *)calloc(len + 1, 1);
+	if (revstates == NULL) {
+		PyErr_NoMemory();
+		goto bail;
+	}
+
+	/* Populate tovisit with all the heads */
+	l = PyList_GET_SIZE(heads);
+	for (i = 0; i < l; i++) {
+		revnum = PyInt_AsLong(PyList_GET_ITEM(heads, i));
+		if (revnum == -1 && PyErr_Occurred())
+			goto bail;
+		if (revnum + 1 < 0 || revnum + 1 >= len + 1) {
+			PyErr_SetString(PyExc_IndexError, "head out of range");
+			goto bail;
+		}
+		if (!(revstates[revnum + 1] & RS_SEEN)) {
+			tovisit[lentovisit++] = revnum;
+			revstates[revnum + 1] |= RS_SEEN;
+		}
+	}
+
+	/* Visit the tovisit list and find the reachable roots */
+	k = 0;
+	while (k < lentovisit) {
+		/* Add the node to reachable if it is a root*/
+		revnum = tovisit[k++];
+		val = PyInt_FromLong(revnum);
+		if (val == NULL)
+			goto bail;
+		if (PySet_Contains(roots, val) == 1) {
+			PySet_Add(reachable, val);
+			if (includepath == 0) {
+				Py_DECREF(val);
+				continue;
+			}
+		}
+		Py_DECREF(val);
+
+		/* Add its parents to the list of nodes to visit */
+		if (revnum == -1)
+			continue;
+		r = index_get_parents(self, revnum, parents, (int)len - 1);
+		if (r < 0)
+			goto bail;
+		for (i = 0; i < 2; i++) {
+			if (!(revstates[parents[i] + 1] & RS_SEEN)
+			    && parents[i] >= minroot) {
+				tovisit[lentovisit++] = parents[i];
+				revstates[parents[i] + 1] |= RS_SEEN;
+			}
+		}
+	}
+
+	/* Find all the nodes in between the roots we found and the heads
+	 * and add them to the reachable set */
+	if (includepath == 1) {
+		minidx = minroot;
+		if (minidx < 0)
+			minidx = 0;
+		for (i = minidx; i < len; i++) {
+			if (!(revstates[i + 1] & RS_SEEN))
+				continue;
+			r = index_get_parents(self, i, parents, (int)len - 1);
+			/* Corrupted index file, error is set from
+			 * index_get_parents */
+			if (r < 0)
+				goto bail;
+			for (k = 0; k < 2; k++) {
+				PyObject *p = PyInt_FromLong(parents[k]);
+				if (p == NULL)
+					goto bail;
+				if (PySet_Contains(reachable, p) == 1) {
+					val = PyInt_FromLong(i);
+					if (val == NULL)
+						goto bail;
+					PySet_Add(reachable, val);
+					Py_DECREF(val);
+				}
+				Py_DECREF(p);
+			}
+		}
+	}
+
+	free(revstates);
+	free(tovisit);
+	return reachable;
+bail:
+	Py_XDECREF(reachable);
+	free(revstates);
+	free(tovisit);
+	return NULL;
+}
+
 static PyObject *compute_phases_map_sets(indexObject *self, PyObject *args)
 {
 	PyObject *roots = Py_None;
@@ -2282,6 +2429,8 @@
 	 "get an index entry"},
 	{"computephasesmapsets", (PyCFunction)compute_phases_map_sets,
 			METH_VARARGS, "compute phases"},
+	{"reachableroots", (PyCFunction)reachableroots, METH_VARARGS,
+		"reachableroots"},
 	{"headrevs", (PyCFunction)index_headrevs, METH_VARARGS,
 	 "get head revisions"}, /* Can do filtering since 3.2 */
 	{"headrevsfiltered", (PyCFunction)index_headrevs, METH_VARARGS,
--- a/mercurial/pathutil.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/pathutil.py	Tue Aug 18 18:38:56 2015 -0500
@@ -1,8 +1,15 @@
-import os, errno, stat, posixpath
+from __future__ import absolute_import
 
-import encoding
-import util
-from i18n import _
+import errno
+import os
+import posixpath
+import stat
+
+from .i18n import _
+from . import (
+    encoding,
+    util,
+)
 
 def _lowerclean(s):
     return encoding.hfsignoreclean(s.lower())
--- a/mercurial/peer.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/peer.py	Tue Aug 18 18:38:56 2015 -0500
@@ -6,11 +6,91 @@
 # 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 _
-import error
+from __future__ import absolute_import
+
+from .i18n import _
+from . import (
+    error,
+    util,
+)
+
+# abstract batching support
+
+class future(object):
+    '''placeholder for a value to be set later'''
+    def set(self, value):
+        if util.safehasattr(self, 'value'):
+            raise error.RepoError("future is already set")
+        self.value = value
+
+class batcher(object):
+    '''base class for batches of commands submittable in a single request
+
+    All methods invoked on instances of this class are simply queued and
+    return a a future for the result. Once you call submit(), all the queued
+    calls are performed and the results set in their respective futures.
+    '''
+    def __init__(self):
+        self.calls = []
+    def __getattr__(self, name):
+        def call(*args, **opts):
+            resref = future()
+            self.calls.append((name, args, opts, resref,))
+            return resref
+        return call
+    def submit(self):
+        pass
+
+class localbatch(batcher):
+    '''performs the queued calls directly'''
+    def __init__(self, local):
+        batcher.__init__(self)
+        self.local = local
+    def submit(self):
+        for name, args, opts, resref in self.calls:
+            resref.set(getattr(self.local, name)(*args, **opts))
+
+def batchable(f):
+    '''annotation for batchable methods
+
+    Such methods must implement a coroutine as follows:
+
+    @batchable
+    def sample(self, one, two=None):
+        # Handle locally computable results first:
+        if not one:
+            yield "a local result", None
+        # Build list of encoded arguments suitable for your wire protocol:
+        encargs = [('one', encode(one),), ('two', encode(two),)]
+        # Create future for injection of encoded result:
+        encresref = future()
+        # Return encoded arguments and future:
+        yield encargs, encresref
+        # Assuming the future to be filled with the result from the batched
+        # request now. Decode it:
+        yield decode(encresref.value)
+
+    The decorator returns a function which wraps this coroutine as a plain
+    method, but adds the original method as an attribute called "batchable",
+    which is used by remotebatch to split the call into separate encoding and
+    decoding phases.
+    '''
+    def plain(*args, **opts):
+        batchable = f(*args, **opts)
+        encargsorres, encresref = batchable.next()
+        if not encresref:
+            return encargsorres # a local result in this case
+        self = args[0]
+        encresref.set(self._submitone(f.func_name, encargsorres))
+        return batchable.next()
+    setattr(plain, 'batchable', f)
+    return plain
 
 class peerrepository(object):
 
+    def batch(self):
+        return localbatch(self)
+
     def capable(self, name):
         '''tell whether repo supports named capability.
         return False if not supported.
--- a/mercurial/phases.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/phases.py	Tue Aug 18 18:38:56 2015 -0500
@@ -100,11 +100,23 @@
 
 """
 
-import os
+from __future__ import absolute_import
+
 import errno
-from node import nullid, nullrev, bin, hex, short
-from i18n import _
-import util, error
+import os
+
+from .i18n import _
+from .node import (
+    bin,
+    hex,
+    nullid,
+    nullrev,
+    short,
+)
+from . import (
+    error,
+    util,
+)
 
 allphases = public, draft, secret = range(3)
 trackedphases = allphases[1:]
--- a/mercurial/posix.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/posix.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,11 +5,26 @@
 # 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 _
-import encoding
-import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
+from __future__ import absolute_import
+
+import errno
+import fcntl
+import getpass
+import grp
+import os
+import pwd
+import re
 import select
-import fcntl, re
+import socket
+import stat
+import sys
+import tempfile
+import unicodedata
+
+from .i18n import _
+from . import (
+    encoding,
+)
 
 posixfile = open
 normpath = os.path.normpath
@@ -459,7 +474,8 @@
 
 def termwidth():
     try:
-        import termios, array
+        import array
+        import termios
         for dev in (sys.stderr, sys.stdout, sys.stdin):
             try:
                 try:
--- a/mercurial/progress.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/progress.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,13 +5,14 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 import sys
-import time
 import threading
-from mercurial import encoding
+import time
 
-from mercurial.i18n import _
-
+from .i18n import _
+from . import encoding
 
 def spacejoin(*args):
     return ' '.join(s for s in args if s)
--- a/mercurial/pushkey.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/pushkey.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,7 +5,14 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import bookmarks, phases, obsolete, encoding
+from __future__ import absolute_import
+
+from . import (
+    bookmarks,
+    encoding,
+    obsolete,
+    phases,
+)
 
 def _nslist(repo):
     n = {}
--- a/mercurial/repair.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/repair.py	Tue Aug 18 18:38:56 2015 -0500
@@ -6,11 +6,19 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from mercurial import changegroup, exchange, util, bundle2
-from mercurial.node import short
-from mercurial.i18n import _
+from __future__ import absolute_import
+
 import errno
 
+from .i18n import _
+from .node import short
+from . import (
+    bundle2,
+    changegroup,
+    exchange,
+    util,
+)
+
 def _bundle(repo, bases, heads, node, suffix, compress=True):
     """create a bundle with the specified revisions as a backup"""
     usebundle2 = (repo.ui.configbool('experimental', 'bundle2-exp', True) and
--- a/mercurial/repoview.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/repoview.py	Tue Aug 18 18:38:56 2015 -0500
@@ -6,15 +6,20 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import heapq
+from __future__ import absolute_import
+
 import copy
-import error
-import phases
-import util
-import obsolete
+import heapq
 import struct
-import tags as tagsmod
-from node import nullrev
+
+from .node import nullrev
+from . import (
+    error,
+    obsolete,
+    phases,
+    tags as tagsmod,
+    util,
+)
 
 def hideablerevs(repo):
     """Revisions candidates to be hidden
--- a/mercurial/revlog.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/revlog.py	Tue Aug 18 18:38:56 2015 -0500
@@ -100,11 +100,10 @@
 #  4 bytes: compressed length
 #  4 bytes: base rev
 #  4 bytes: link rev
-# 32 bytes: parent 1 nodeid
-# 32 bytes: parent 2 nodeid
-# 32 bytes: nodeid
+# 20 bytes: parent 1 nodeid
+# 20 bytes: parent 2 nodeid
+# 20 bytes: nodeid
 indexformatv0 = ">4l20s20s20s"
-v0shaoffset = 56
 
 class revlogoldio(object):
     def __init__(self):
@@ -150,7 +149,6 @@
 #  4 bytes: parent 2 rev
 # 32 bytes: nodeid
 indexformatng = ">Qiiiiii20s12x"
-ngshaoffset = 32
 versionformat = ">I"
 
 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
--- a/mercurial/revset.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/revset.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,16 +5,25 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import re
-import parser, util, error, hbisect, phases
-import node
+from __future__ import absolute_import
+
 import heapq
-import match as matchmod
-from i18n import _
-import encoding
-import obsolete as obsmod
-import pathutil
-import repoview
+import re
+
+from .i18n import _
+from . import (
+    encoding,
+    error,
+    hbisect,
+    match as matchmod,
+    node,
+    obsolete as obsmod,
+    parser,
+    pathutil,
+    phases,
+    repoview,
+    util,
+)
 
 def _revancestors(repo, revs, followfirst):
     """Like revlog.ancestors(), but supports followfirst."""
@@ -78,19 +87,16 @@
 
     return generatorset(iterate(), iterasc=True)
 
-def _revsbetween(repo, roots, heads):
-    """Return all paths between roots and heads, inclusive of both endpoint
-    sets."""
+def reachablerootspure(repo, minroot, roots, heads, includepath):
+    """return (heads(::<roots> and ::<heads>))
+
+    If includepath is True, return (<roots>::<heads>)."""
     if not roots:
         return baseset()
     parentrevs = repo.changelog.parentrevs
     visit = list(heads)
     reachable = set()
     seen = {}
-    # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
-    # (and if it is not, it should.)
-    minroot = min(roots)
-    roots = set(roots)
     # prefetch all the things! (because python is slow)
     reached = reachable.add
     dovisit = visit.append
@@ -101,6 +107,8 @@
         rev = nextvisit()
         if rev in roots:
             reached(rev)
+            if not includepath:
+                continue
         parents = parentrevs(rev)
         seen[rev] = parents
         for parent in parents:
@@ -108,12 +116,30 @@
                 dovisit(parent)
     if not reachable:
         return baseset()
+    if not includepath:
+        return reachable
     for rev in sorted(seen):
         for parent in seen[rev]:
             if parent in reachable:
                 reached(rev)
     return baseset(sorted(reachable))
 
+def reachableroots(repo, roots, heads, includepath=False):
+    """return (heads(::<roots> and ::<heads>))
+
+    If includepath is True, return (<roots>::<heads>)."""
+    if not roots:
+        return baseset()
+    # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
+    # (and if it is not, it should.)
+    minroot = min(roots)
+    roots = set(roots)
+    heads = list(heads)
+    try:
+        return repo.changelog.reachableroots(minroot, heads, roots, includepath)
+    except AttributeError:
+        return reachablerootspure(repo, minroot, roots, heads, includepath)
+
 elements = {
     # token-type: binding-strength, primary, prefix, infix, suffix
     "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
@@ -178,6 +204,21 @@
     if symletters is None:
         symletters = _symletters
 
+    if program and lookup:
+        # attempt to parse old-style ranges first to deal with
+        # things like old-tag which contain query metacharacters
+        parts = program.split(':', 1)
+        if all(lookup(sym) for sym in parts if sym):
+            if parts[0]:
+                yield ('symbol', parts[0], 0)
+            if len(parts) > 1:
+                s = len(parts[0])
+                yield (':', None, s)
+                if parts[1]:
+                    yield ('symbol', parts[1], s + 1)
+            yield ('end', None, len(program))
+            return
+
     pos, l = 0, len(program)
     while pos < l:
         c = program[pos]
@@ -382,7 +423,8 @@
 
 def dagrange(repo, subset, x, y):
     r = fullreposet(repo)
-    xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
+    xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
+                         includepath=True)
     # XXX We should combine with subset first: 'subset & baseset(...)'. This is
     # necessary to ensure we preserve the order in subset.
     return xs & subset
@@ -391,8 +433,13 @@
     return getset(repo, getset(repo, subset, x), y)
 
 def orset(repo, subset, *xs):
-    rs = [getset(repo, subset, x) for x in xs]
-    return _combinesets(rs)
+    assert xs
+    if len(xs) == 1:
+        return getset(repo, subset, xs[0])
+    p = len(xs) // 2
+    a = orset(repo, subset, *xs[:p])
+    b = orset(repo, subset, *xs[p:])
+    return a + b
 
 def notset(repo, subset, x):
     return subset - getset(repo, subset, x)
@@ -1415,8 +1462,10 @@
     default push location.
     """
     # Avoid cycles.
-    import discovery
-    import hg
+    from . import (
+        discovery,
+        hg,
+    )
     # i18n: "outgoing" is a keyword
     l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
     # i18n: "outgoing" is a keyword
@@ -1597,7 +1646,7 @@
     synonym for the current local branch.
     """
 
-    import hg # avoid start-up nasties
+    from . import hg # avoid start-up nasties
     # i18n: "remote" is a keyword
     l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
 
@@ -2654,6 +2703,27 @@
     if repo:
         lookup = repo.__contains__
     tree = parse(spec, lookup)
+    return _makematcher(ui, tree, repo)
+
+def matchany(ui, specs, repo=None):
+    """Create a matcher that will include any revisions matching one of the
+    given specs"""
+    if not specs:
+        def mfunc(repo, subset=None):
+            return baseset()
+        return mfunc
+    if not all(specs):
+        raise error.ParseError(_("empty query"))
+    lookup = None
+    if repo:
+        lookup = repo.__contains__
+    if len(specs) == 1:
+        tree = parse(specs[0], lookup)
+    else:
+        tree = ('or',) + tuple(parse(s, lookup) for s in specs)
+    return _makematcher(ui, tree, repo)
+
+def _makematcher(ui, tree, repo):
     if ui:
         tree = findaliases(ui, tree, showwarning=ui.warn)
     tree = foldconcat(tree)
@@ -3073,20 +3143,6 @@
     def __repr__(self):
         return '<%s %r>' % (type(self).__name__, self._subset)
 
-# this function will be removed, or merged to addset or orset, when
-# - scmutil.revrange() can be rewritten to not combine calculated smartsets
-# - or addset can handle more than two sets without balanced tree
-def _combinesets(subsets):
-    """Create balanced tree of addsets representing union of given sets"""
-    if not subsets:
-        return baseset()
-    if len(subsets) == 1:
-        return subsets[0]
-    p = len(subsets) // 2
-    xs = _combinesets(subsets[:p])
-    ys = _combinesets(subsets[p:])
-    return addset(xs, ys)
-
 def _iterordered(ascending, iter1, iter2):
     """produce an ordered iteration from two iterators with the same order
 
--- a/mercurial/scmutil.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/scmutil.py	Tue Aug 18 18:38:56 2015 -0500
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import _
-from mercurial.node import nullrev, wdirrev
+from mercurial.node import wdirrev
 import util, error, osutil, revset, similar, encoding, phases
 import pathutil
 import match as matchmod
@@ -690,6 +690,11 @@
         raise util.Abort(_('empty revision set'))
     return repo[l.last()]
 
+def _pairspec(revspec):
+    tree = revset.parse(revspec)
+    tree = revset.optimize(tree, True)[1]  # fix up "x^:y" -> "(x^):y"
+    return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
+
 def revpair(repo, revs):
     if not revs:
         return repo.dirstate.p1(), None
@@ -711,67 +716,21 @@
     if first is None:
         raise util.Abort(_('empty revision range'))
 
-    if first == second and len(revs) == 1 and _revrangesep not in revs[0]:
+    # if top-level is range expression, the result must always be a pair
+    if first == second and len(revs) == 1 and not _pairspec(revs[0]):
         return repo.lookup(first), None
 
     return repo.lookup(first), repo.lookup(second)
 
-_revrangesep = ':'
-
 def revrange(repo, revs):
     """Yield revision as strings from a list of revision specifications."""
-
-    def revfix(repo, val, defval):
-        if not val and val != 0 and defval is not None:
-            return defval
-        return repo[val].rev()
-
-    subsets = []
-
-    revsetaliases = [alias for (alias, _) in
-                     repo.ui.configitems("revsetalias")]
-
+    allspecs = []
     for spec in revs:
-        # attempt to parse old-style ranges first to deal with
-        # things like old-tag which contain query metacharacters
-        try:
-            # ... except for revset aliases without arguments. These
-            # should be parsed as soon as possible, because they might
-            # clash with a hash prefix.
-            if spec in revsetaliases:
-                raise error.RepoLookupError
-
-            if isinstance(spec, int):
-                subsets.append(revset.baseset([spec]))
-                continue
-
-            if _revrangesep in spec:
-                start, end = spec.split(_revrangesep, 1)
-                if start in revsetaliases or end in revsetaliases:
-                    raise error.RepoLookupError
-
-                start = revfix(repo, start, 0)
-                end = revfix(repo, end, len(repo) - 1)
-                if end == nullrev and start < 0:
-                    start = nullrev
-                if start < end:
-                    l = revset.spanset(repo, start, end + 1)
-                else:
-                    l = revset.spanset(repo, start, end - 1)
-                subsets.append(l)
-                continue
-            elif spec and spec in repo: # single unquoted rev
-                rev = revfix(repo, spec, None)
-                subsets.append(revset.baseset([rev]))
-                continue
-        except error.RepoLookupError:
-            pass
-
-        # fall through to new-style queries if old-style fails
-        m = revset.match(repo.ui, spec, repo)
-        subsets.append(m(repo))
-
-    return revset._combinesets(subsets)
+        if isinstance(spec, int):
+            spec = revset.formatspec('rev(%d)', spec)
+        allspecs.append(spec)
+    m = revset.matchany(repo.ui, allspecs, repo)
+    return m(repo)
 
 def expandpats(pats):
     '''Expand bare globs when running on windows.
--- a/mercurial/setdiscovery.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/setdiscovery.py	Tue Aug 18 18:38:56 2015 -0500
@@ -40,11 +40,20 @@
 classified with it (since all ancestors or descendants will be marked as well).
 """
 
+from __future__ import absolute_import
+
 import collections
-from node import nullid, nullrev
-from i18n import _
 import random
-import util, dagutil
+
+from .i18n import _
+from .node import (
+    nullid,
+    nullrev,
+)
+from . import (
+    dagutil,
+    util,
+)
 
 def _updatesample(dag, nodes, sample, quicksamplesize=0):
     """update an existing sample to match the expected size
@@ -138,22 +147,12 @@
     sample = _limitsample(ownheads, initialsamplesize)
     # indices between sample and externalized version must match
     sample = list(sample)
-    if remote.local():
-        # stopgap until we have a proper localpeer that supports batch()
-        srvheadhashes = remote.heads()
-        yesno = remote.known(dag.externalizeall(sample))
-    elif remote.capable('batch'):
-        batch = remote.batch()
-        srvheadhashesref = batch.heads()
-        yesnoref = batch.known(dag.externalizeall(sample))
-        batch.submit()
-        srvheadhashes = srvheadhashesref.value
-        yesno = yesnoref.value
-    else:
-        # compatibility with pre-batch, but post-known remotes during 1.9
-        # development
-        srvheadhashes = remote.heads()
-        sample = []
+    batch = remote.batch()
+    srvheadhashesref = batch.heads()
+    yesnoref = batch.known(dag.externalizeall(sample))
+    batch.submit()
+    srvheadhashes = srvheadhashesref.value
+    yesno = yesnoref.value
 
     if cl.tip() == nullid:
         if srvheadhashes != [nullid]:
--- a/mercurial/simplemerge.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/simplemerge.py	Tue Aug 18 18:38:56 2015 -0500
@@ -16,9 +16,17 @@
 # mbp: "you know that thing where cvs gives you conflict markers?"
 # s: "i hate that."
 
-from i18n import _
-import scmutil, util, mdiff
-import sys, os
+from __future__ import absolute_import
+
+import os
+import sys
+
+from .i18n import _
+from . import (
+    mdiff,
+    scmutil,
+    util,
+)
 
 class CantReprocessAndShowBase(Exception):
     pass
--- a/mercurial/sshpeer.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/sshpeer.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,16 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 import re
-from i18n import _
-import util, error, wireproto
+
+from .i18n import _
+from . import (
+    error,
+    util,
+    wireproto,
+)
 
 class remotelock(object):
     def __init__(self, repo):
--- a/mercurial/sshserver.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/sshserver.py	Tue Aug 18 18:38:56 2015 -0500
@@ -6,8 +6,16 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import util, hook, wireproto
-import os, sys
+from __future__ import absolute_import
+
+import os
+import sys
+
+from . import (
+    hook,
+    util,
+    wireproto,
+)
 
 class sshserver(wireproto.abstractserverproto):
     def __init__(self, ui, repo):
--- a/mercurial/sslutil.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/sslutil.py	Tue Aug 18 18:38:56 2015 -0500
@@ -6,10 +6,15 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-import os, sys, ssl
+
+from __future__ import absolute_import
 
-from mercurial import util
-from mercurial.i18n import _
+import os
+import ssl
+import sys
+
+from .i18n import _
+from . import util
 
 _canloaddefaultcerts = False
 try:
--- a/mercurial/statichttprepo.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/statichttprepo.py	Tue Aug 18 18:38:56 2015 -0500
@@ -7,10 +7,26 @@
 # 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 _
-import changelog, byterange, url, error, namespaces
-import localrepo, manifest, util, scmutil, store
-import urllib, urllib2, errno, os
+from __future__ import absolute_import
+
+import errno
+import os
+import urllib
+import urllib2
+
+from .i18n import _
+from . import (
+    byterange,
+    changelog,
+    error,
+    localrepo,
+    manifest,
+    namespaces,
+    scmutil,
+    store,
+    url,
+    util,
+)
 
 class httprangereader(object):
     def __init__(self, url, opener):
--- a/mercurial/strutil.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/strutil.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,6 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 def findall(haystack, needle, start=0, end=None):
     if end is None:
         end = len(haystack)
--- a/mercurial/subrepo.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/subrepo.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,15 +5,34 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 import copy
-import errno, os, re, posixpath, sys
+import errno
+import os
+import posixpath
+import re
+import stat
+import subprocess
+import sys
+import tarfile
 import xml.dom.minidom
-import stat, subprocess, tarfile
-from i18n import _
-import config, util, node, error, cmdutil, scmutil, match as matchmod
-import phases
-import pathutil
-import exchange
+
+
+from .i18n import _
+from . import (
+    cmdutil,
+    config,
+    error,
+    exchange,
+    match as matchmod,
+    node,
+    pathutil,
+    phases,
+    scmutil,
+    util,
+)
+
 hg = None
 propertycache = util.propertycache
 
@@ -328,7 +347,7 @@
     # so we manually delay the circular imports to not break
     # scripts that don't use our demand-loading
     global hg
-    import hg as h
+    from . import hg as h
     hg = h
 
     pathutil.pathauditor(ctx.repo().root)(path)
@@ -346,7 +365,7 @@
     # so we manually delay the circular imports to not break
     # scripts that don't use our demand-loading
     global hg
-    import hg as h
+    from . import hg as h
     hg = h
 
     pathutil.pathauditor(ctx.repo().root)(path)
--- a/mercurial/tagmerge.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/tagmerge.py	Tue Aug 18 18:38:56 2015 -0500
@@ -71,11 +71,20 @@
 #         - put blocks whose nodes come all from p2 first
 #     - write the tag blocks in the sorted order
 
-import tags as tagsmod
-import util
-from node import nullid, hex
-from i18n import _
+from __future__ import absolute_import
+
 import operator
+
+from .i18n import _
+from .node import (
+    hex,
+    nullid,
+)
+from .import (
+    tags as tagsmod,
+    util,
+)
+
 hexnullid = hex(nullid)
 
 def readtagsformerge(ui, repo, lines, fn='', keeplinenums=False):
--- a/mercurial/tags.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/tags.py	Tue Aug 18 18:38:56 2015 -0500
@@ -10,15 +10,27 @@
 # Eventually, it could take care of updating (adding/removing/moving)
 # tags too.
 
-from node import nullid, bin, hex, short
-from i18n import _
-import util
-import encoding
-import error
-from array import array
+from __future__ import absolute_import
+
+import array
 import errno
 import time
 
+from .i18n import _
+from .node import (
+    bin,
+    hex,
+    nullid,
+    short,
+)
+from . import (
+    encoding,
+    error,
+    util,
+)
+
+array = array.array
+
 # Tags computation can be expensive and caches exist to make it fast in
 # the common case.
 #
@@ -263,7 +275,7 @@
     If the cache is not up to date, the caller is responsible for reading tag
     info from each returned head. (See findglobaltags().)
     '''
-    import scmutil  # avoid cycle
+    from . import scmutil  # avoid cycle
 
     try:
         cachefile = repo.vfs(_filename(repo), 'r')
--- a/mercurial/templatefilters.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/templatefilters.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,10 +5,21 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import cgi, re, os, time, urllib
-import encoding, node, util
-import hbisect
-import templatekw
+from __future__ import absolute_import
+
+import cgi
+import os
+import re
+import time
+import urllib
+
+from . import (
+    encoding,
+    hbisect,
+    node,
+    templatekw,
+    util,
+)
 
 def addbreaks(text):
     """:addbreaks: Any text. Add an XHTML "<br />" tag before the end of
--- a/mercurial/templatekw.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/templatekw.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,16 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from node import hex
-import patch, scmutil, util, error
-import hbisect
+from __future__ import absolute_import
+
+from .node import hex
+from . import (
+    error,
+    hbisect,
+    patch,
+    scmutil,
+    util,
+)
 
 # This helper class allows us to handle both:
 #  "{files}" (legacy command-line-specific list hack) and
--- a/mercurial/templater.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/templater.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,12 +5,23 @@
 # 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 _
-import os, re
-import util, config, templatefilters, templatekw, parser, error
-import revset as revsetmod
+from __future__ import absolute_import
+
+import os
+import re
 import types
-import minirst
+
+from .i18n import _
+from . import (
+    config,
+    error,
+    minirst,
+    parser,
+    revset as revsetmod,
+    templatefilters,
+    templatekw,
+    util,
+)
 
 # template parsing
 
--- a/mercurial/templates/monoblue/footer.tmpl	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/templates/monoblue/footer.tmpl	Tue Aug 18 18:38:56 2015 -0500
@@ -12,11 +12,6 @@
         <p><a href="{logourl}" title="Mercurial"><img src="{staticurl|urlescape}{logoimg}" width=75 height=90 border=0 alt="mercurial" /></a></p>
     </div>
 
-    <div id="corner-top-left"></div>
-    <div id="corner-top-right"></div>
-    <div id="corner-bottom-left"></div>
-    <div id="corner-bottom-right"></div>
-
 </div>
 
 </body>
--- a/mercurial/templates/monoblue/index.tmpl	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/templates/monoblue/index.tmpl	Tue Aug 18 18:38:56 2015 -0500
@@ -29,11 +29,6 @@
         <p><a href="{logourl}" title="Mercurial"><img src="{staticurl|urlescape}{logoimg}" width=75 height=90 border=0 alt="mercurial"></a></p>
     </div>
 
-    <div id="corner-top-left"></div>
-    <div id="corner-top-right"></div>
-    <div id="corner-bottom-left"></div>
-    <div id="corner-bottom-right"></div>
-
 </div>
 <script type="text/javascript">process_dates()</script>
 </body>
--- a/mercurial/templates/static/style-monoblue.css	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/templates/static/style-monoblue.css	Tue Aug 18 18:38:56 2015 -0500
@@ -263,7 +263,6 @@
 
 td.source {
   white-space: pre;
-  font-family: monospace;
   margin: 10px 30px 0;
   font-size: 1.2em;
   font-family: monospace;
@@ -311,44 +310,6 @@
 div#powered-by a:hover {
   text-decoration: underline;
 }
-/*
-div#monoblue-corner-top-left {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 10px;
-  height: 10px;
-  background: url(./monoblue-corner.png) top left no-repeat !important;
-  background: none;
-}
-div#monoblue-corner-top-right {
-  position: absolute;
-  top: 0;
-  right: 0;
-  width: 10px;
-  height: 10px;
-  background: url(./monoblue-corner.png) top right no-repeat !important;
-  background: none;
-}
-div#monoblue-corner-bottom-left {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  width: 10px;
-  height: 10px;
-  background: url(./monoblue-corner.png) bottom left no-repeat !important;
-  background: none;
-}
-div#monoblue-corner-bottom-right {
-  position: absolute;
-  bottom: 0;
-  right: 0;
-  width: 10px;
-  height: 10px;
-  background: url(./monoblue-corner.png) bottom right no-repeat !important;
-  background: none;
-}
-*/
 /** end of common settings **/
 
 /** summary **/
--- a/mercurial/transaction.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/transaction.py	Tue Aug 18 18:38:56 2015 -0500
@@ -11,9 +11,15 @@
 # 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 __future__ import absolute_import
+
 import errno
-import error, util
+
+from .i18n import _
+from . import (
+    error,
+    util,
+)
 
 version = 2
 
--- a/mercurial/treediscovery.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/treediscovery.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,10 +5,19 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
 import collections
-from node import nullid, short
-from i18n import _
-import util, error
+
+from .i18n import _
+from .node import (
+    nullid,
+    short,
+)
+from . import (
+    error,
+    util,
+)
 
 def findcommonincoming(repo, remote, heads=None, force=False):
     """Return a tuple (common, fetch, heads) used to identify the common
--- a/mercurial/ui.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/ui.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,11 +5,28 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+from __future__ import absolute_import
+
+import errno
+import getpass
 import inspect
-from i18n import _
-import errno, getpass, os, socket, sys, tempfile, traceback
-import config, scmutil, util, error, formatter, progress
-from node import hex
+import os
+import socket
+import sys
+import tempfile
+import traceback
+
+from .i18n import _
+from .node import hex
+
+from . import (
+    config,
+    error,
+    formatter,
+    progress,
+    scmutil,
+    util,
+)
 
 samplehgrcs = {
     'user':
--- a/mercurial/unionrepo.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/unionrepo.py	Tue Aug 18 18:38:56 2015 -0500
@@ -11,11 +11,25 @@
 allowing operations like diff and log with revsets.
 """
 
-from node import nullid
-from i18n import _
+from __future__ import absolute_import
+
 import os
-import util, mdiff, cmdutil, scmutil
-import localrepo, changelog, manifest, filelog, revlog, pathutil
+
+from .i18n import _
+from .node import nullid
+
+from . import (
+    changelog,
+    cmdutil,
+    filelog,
+    localrepo,
+    manifest,
+    mdiff,
+    pathutil,
+    revlog,
+    scmutil,
+    util,
+)
 
 class unionrevlog(revlog.revlog):
     def __init__(self, opener, indexfile, revlog2, linkmapper):
--- a/mercurial/url.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/url.py	Tue Aug 18 18:38:56 2015 -0500
@@ -7,10 +7,23 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import urllib, urllib2, httplib, os, socket, cStringIO, base64
-from i18n import _
-import keepalive, util, sslutil
-import httpconnection as httpconnectionmod
+from __future__ import absolute_import
+
+import base64
+import cStringIO
+import httplib
+import os
+import socket
+import urllib
+import urllib2
+
+from .i18n import _
+from . import (
+    httpconnection as httpconnectionmod,
+    keepalive,
+    sslutil,
+    util,
+)
 
 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
     def __init__(self, ui):
--- a/mercurial/verify.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/verify.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,10 +5,21 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from node import nullid, short
-from i18n import _
+from __future__ import absolute_import
+
 import os
-import revlog, util, error
+
+from .i18n import _
+from .node import (
+    nullid,
+    short,
+)
+
+from . import (
+    error,
+    revlog,
+    util,
+)
 
 def verify(repo):
     lock = repo.lock()
--- a/mercurial/win32.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/win32.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,7 +5,14 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import ctypes, errno, msvcrt, os, subprocess, random
+from __future__ import absolute_import
+
+import ctypes
+import errno
+import msvcrt
+import os
+import random
+import subprocess
 
 _kernel32 = ctypes.windll.kernel32
 _advapi32 = ctypes.windll.advapi32
--- a/mercurial/wireproto.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/wireproto.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,12 +5,29 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import urllib, tempfile, os, sys
-from i18n import _
-from node import bin, hex
-import changegroup as changegroupmod, bundle2, pushkey as pushkeymod
-import peer, error, encoding, util, exchange
+from __future__ import absolute_import
+
+import os
+import sys
+import tempfile
+import urllib
 
+from .i18n import _
+from .node import (
+    bin,
+    hex,
+)
+
+from . import (
+    bundle2,
+    changegroup as changegroupmod,
+    encoding,
+    error,
+    exchange,
+    peer,
+    pushkey as pushkeymod,
+    util,
+)
 
 class abstractserverproto(object):
     """abstract class that summarizes the protocol API
@@ -58,48 +75,12 @@
         Some protocols may have compressed the contents."""
         raise NotImplementedError()
 
-# abstract batching support
-
-class future(object):
-    '''placeholder for a value to be set later'''
-    def set(self, value):
-        if util.safehasattr(self, 'value'):
-            raise error.RepoError("future is already set")
-        self.value = value
-
-class batcher(object):
-    '''base class for batches of commands submittable in a single request
-
-    All methods invoked on instances of this class are simply queued and
-    return a a future for the result. Once you call submit(), all the queued
-    calls are performed and the results set in their respective futures.
-    '''
-    def __init__(self):
-        self.calls = []
-    def __getattr__(self, name):
-        def call(*args, **opts):
-            resref = future()
-            self.calls.append((name, args, opts, resref,))
-            return resref
-        return call
-    def submit(self):
-        pass
-
-class localbatch(batcher):
-    '''performs the queued calls directly'''
-    def __init__(self, local):
-        batcher.__init__(self)
-        self.local = local
-    def submit(self):
-        for name, args, opts, resref in self.calls:
-            resref.set(getattr(self.local, name)(*args, **opts))
-
-class remotebatch(batcher):
+class remotebatch(peer.batcher):
     '''batches the queued calls; uses as few roundtrips as possible'''
     def __init__(self, remote):
         '''remote must support _submitbatch(encbatch) and
         _submitone(op, encargs)'''
-        batcher.__init__(self)
+        peer.batcher.__init__(self)
         self.remote = remote
     def submit(self):
         req, rsp = [], []
@@ -128,41 +109,10 @@
             encresref.set(encres)
             resref.set(batchable.next())
 
-def batchable(f):
-    '''annotation for batchable methods
-
-    Such methods must implement a coroutine as follows:
-
-    @batchable
-    def sample(self, one, two=None):
-        # Handle locally computable results first:
-        if not one:
-            yield "a local result", None
-        # Build list of encoded arguments suitable for your wire protocol:
-        encargs = [('one', encode(one),), ('two', encode(two),)]
-        # Create future for injection of encoded result:
-        encresref = future()
-        # Return encoded arguments and future:
-        yield encargs, encresref
-        # Assuming the future to be filled with the result from the batched
-        # request now. Decode it:
-        yield decode(encresref.value)
-
-    The decorator returns a function which wraps this coroutine as a plain
-    method, but adds the original method as an attribute called "batchable",
-    which is used by remotebatch to split the call into separate encoding and
-    decoding phases.
-    '''
-    def plain(*args, **opts):
-        batchable = f(*args, **opts)
-        encargsorres, encresref = batchable.next()
-        if not encresref:
-            return encargsorres # a local result in this case
-        self = args[0]
-        encresref.set(self._submitone(f.func_name, encargsorres))
-        return batchable.next()
-    setattr(plain, 'batchable', f)
-    return plain
+# Forward a couple of names from peer to make wireproto interactions
+# slightly more sensible.
+batchable = peer.batchable
+future = peer.future
 
 # list of nodes encoding / decoding
 
@@ -216,7 +166,10 @@
 class wirepeer(peer.peerrepository):
 
     def batch(self):
-        return remotebatch(self)
+        if self.capable('batch'):
+            return remotebatch(self)
+        else:
+            return peer.localbatch(self)
     def _submitbatch(self, req):
         cmds = []
         for op, argsdict in req:
--- a/mercurial/worker.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/mercurial/worker.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,9 +5,16 @@
 # 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 _
-import errno, os, signal, sys, threading
-import util
+from __future__ import absolute_import
+
+import errno
+import os
+import signal
+import sys
+import threading
+
+from .i18n import _
+from . import util
 
 def countcpus():
     '''try to count the number of CPUs on the system'''
--- a/tests/filterpyflakes.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/filterpyflakes.py	Tue Aug 18 18:38:56 2015 -0500
@@ -2,7 +2,7 @@
 
 # Filter output by pyflakes to control which warnings we check
 
-import sys, re, os
+import sys, re
 
 def makekey(typeandline):
     """
@@ -42,7 +42,7 @@
     else:
         continue # no pattern matched, next line
     fn = line.split(':', 1)[0]
-    f = open(os.path.join(os.path.dirname(os.path.dirname(__file__)), fn))
+    f = open(fn)
     data = f.read()
     f.close()
     if 'no-' 'check-code' in data:
--- a/tests/test-batching.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-batching.py	Tue Aug 18 18:38:56 2015 -0500
@@ -5,7 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from mercurial.wireproto import localbatch, remotebatch, batchable, future
+from mercurial.peer import localbatch, batchable, future
+from mercurial.wireproto import remotebatch
 
 # equivalent of repo.repository
 class thing(object):
--- a/tests/test-bookmarks.t	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-bookmarks.t	Tue Aug 18 18:38:56 2015 -0500
@@ -511,10 +511,10 @@
 
 test clone with update to a bookmark
 
-  $ hg clone -u Z . cloned-bookmarks-update
+  $ hg clone -u Z . ../cloned-bookmarks-update
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg -R cloned-bookmarks-update bookmarks
+  $ hg -R ../cloned-bookmarks-update bookmarks
      X2                        1:925d80f479bb
      Y                         2:db815d6d32e6
    * Z                         2:db815d6d32e6
@@ -569,10 +569,10 @@
 
   $ hg bookmark -r3 Y
   moving bookmark 'Y' forward from db815d6d32e6
-  $ hg -R cloned-bookmarks-update update Y
+  $ hg -R ../cloned-bookmarks-update update Y
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark Y)
-  $ hg -R cloned-bookmarks-update pull --update .
+  $ hg -R ../cloned-bookmarks-update pull --update .
   pulling from .
   searching for changes
   adding changesets
--- a/tests/test-completion.t	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-completion.t	Tue Aug 18 18:38:56 2015 -0500
@@ -254,7 +254,7 @@
   debugpathcomplete: full, normal, added, removed
   debugpushkey: 
   debugpvec: 
-  debugrebuilddirstate: rev
+  debugrebuilddirstate: rev, minimal
   debugrebuildfncache: 
   debugrename: rev
   debugrevlog: changelog, manifest, dir, dump
--- a/tests/test-convert-filemap.t	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-convert-filemap.t	Tue Aug 18 18:38:56 2015 -0500
@@ -671,3 +671,61 @@
   |/
   o  0:c334dc3be0da@default "add" files: a
   
+  $ cd ..
+
+test converting merges into a repo that contains other files
+
+  $ hg init merge-test1
+  $ cd merge-test1
+  $ touch a && hg commit -Aqm a
+  $ hg up -q null
+  $ touch b && hg commit -Aqm b
+  $ hg merge -q 0 && hg commit -qm merge
+  $ cd ..
+  $ hg init merge-test2
+  $ cd merge-test2
+  $ mkdir converted
+  $ touch converted/a && hg commit -Aqm 'a'
+  $ touch x && hg commit -Aqm 'x'
+  $ cd ..
+  $ hg log -G -T '{node}' -R merge-test1
+  @    ea7c1a7ae9588677a715ce4f204cd89c28d5471f
+  |\
+  | o  d7486e00c6f1b633dcadc0582f78006d805c7a0f
+  |
+  o  3903775176ed42b1458a6281db4a0ccf4d9f287a
+  
+  $ hg log -G -T '{node}' -R merge-test2
+  @  34f1aa7da42559bae87920880b522d47b3ddbc0d
+  |
+  o  e01a12b07b4fdfd61ff90a2a1b4560a7a776f323
+  
+- Build a shamap where the target converted/a is in on top of an unrelated
+- change to 'x'. This simulates using convert to merge several repositories
+- together.
+  $ cat >> merge-test2/.hg/shamap <<EOF
+  > 3903775176ed42b1458a6281db4a0ccf4d9f287a 34f1aa7da42559bae87920880b522d47b3ddbc0d
+  > EOF
+  $ cat >> merge-test-filemap <<EOF
+  > rename . converted/
+  > EOF
+  $ hg convert --filemap merge-test-filemap merge-test1 merge-test2 --traceback
+  scanning source...
+  sorting...
+  converting...
+  1 b
+  0 merge
+  $ hg -R merge-test2 manifest -r tip
+  converted/a
+  converted/b
+  x
+  $ hg -R merge-test2 log -G -T '{node}\n{files % "{file}\n"}'
+  o    4b5e2f0218d3442a0c14892b18685bf9c8059c4a
+  |\
+  | o  214325dd2e4cff981dcf00cb120cd39e1ea36dcc
+  |    converted/b
+  @  34f1aa7da42559bae87920880b522d47b3ddbc0d
+  |  x
+  o  e01a12b07b4fdfd61ff90a2a1b4560a7a776f323
+     converted/a
+
--- a/tests/test-convert-git.t	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-convert-git.t	Tue Aug 18 18:38:56 2015 -0500
@@ -678,6 +678,28 @@
      master                    0:03bf38caa4c6
      origin/master             0:03bf38caa4c6
 
+Run convert when the remote branches have changed
+(there was an old bug where the local convert read branches from the server)
+
+  $ cd git-repo7
+  $ echo a >> a
+  $ git commit -am "move master forward"
+  [master 0c81947] move master forward
+   Author: nottest <test@example.org>
+   1 file changed, 1 insertion(+)
+  $ cd ..
+  $ rm -rf hg-repo7
+  $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
+  initializing destination hg-repo7 repository
+  scanning source...
+  sorting...
+  converting...
+  0 commit a
+  updating bookmarks
+  $ hg -R hg-repo7 bookmarks
+     master                    0:03bf38caa4c6
+     origin/master             0:03bf38caa4c6
+
 damaged git repository tests:
 In case the hard-coded hashes change, the following commands can be used to
 list the hashes and their corresponding types in the repository:
--- a/tests/test-copy-move-merge.t	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-copy-move-merge.t	Tue Aug 18 18:38:56 2015 -0500
@@ -59,4 +59,107 @@
   1
   2
 
+Test disabling copy tracing
+
+- first verify copy metadata was kept
+
+  $ hg up -qC 2
+  $ hg rebase --keep -d 1 -b 2 --config extensions.rebase=
+  rebasing 2:add3f11052fa "other" (tip)
+  merging b and a to b
+  merging c and a to c
+
+  $ cat b
+  0
+  1
+  2
+
+- next verify copy metadata is lost when disabled
+
+  $ hg strip -r . --config extensions.strip=
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/t/.hg/strip-backup/550bd84c0cd3-fc575957-backup.hg (glob)
+  $ hg up -qC 2
+  $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True
+  rebasing 2:add3f11052fa "other" (tip)
+  remote changed a which local deleted
+  use (c)hanged version or leave (d)eleted? c
+
+  $ cat b
+  1
+  2
+
   $ cd ..
+
+Verify disabling copy tracing still keeps copies from rebase source
+
+  $ hg init copydisable
+  $ cd copydisable
+  $ touch a
+  $ hg ci -Aqm 'add a'
+  $ touch b
+  $ hg ci -Aqm 'add b, c'
+  $ hg cp b x
+  $ echo x >> x
+  $ hg ci -qm 'copy b->x'
+  $ hg up -q 1
+  $ touch z
+  $ hg ci -Aqm 'add z'
+  $ hg log -G -T '{rev} {desc}\n'
+  @  3 add z
+  |
+  | o  2 copy b->x
+  |/
+  o  1 add b, c
+  |
+  o  0 add a
+  
+  $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True
+  rebasing 2:6adcf8c12e7d "copy b->x"
+  saved backup bundle to $TESTTMP/copydisable/.hg/strip-backup/6adcf8c12e7d-ce4b3e75-backup.hg (glob)
+  $ hg up -q 3
+  $ hg log -f x -T '{rev} {desc}\n'
+  3 copy b->x
+  1 add b, c
+
+  $ cd ../
+
+Verify we duplicate existing copies, instead of detecting them
+
+  $ hg init copydisable3
+  $ cd copydisable3
+  $ touch a
+  $ hg ci -Aqm 'add a'
+  $ hg cp a b
+  $ hg ci -Aqm 'copy a->b'
+  $ hg mv b c
+  $ hg ci -Aqm 'move b->c'
+  $ hg up -q 0
+  $ hg cp a b
+  $ echo b >> b
+  $ hg ci -Aqm 'copy a->b (2)'
+  $ hg log -G -T '{rev} {desc}\n'
+  @  3 copy a->b (2)
+  |
+  | o  2 move b->c
+  | |
+  | o  1 copy a->b
+  |/
+  o  0 add a
+  
+  $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.disablecopytrace=True
+  rebasing 3:47e1a9e6273b "copy a->b (2)" (tip)
+  saved backup bundle to $TESTTMP/copydisable3/.hg/strip-backup/47e1a9e6273b-2d099c59-backup.hg (glob)
+
+  $ hg log -G -f b
+  @  changeset:   3:76024fb4b05b
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     copy a->b (2)
+  |
+  o  changeset:   0:ac82d8b1f7c4
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add a
+  
--- a/tests/test-diff-change.t	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-diff-change.t	Tue Aug 18 18:38:56 2015 -0500
@@ -29,15 +29,59 @@
   -first
   +second
 
-Test dumb revspecs (issue3474)
+  $ cd ..
+
+Test dumb revspecs: top-level "x:y", "x:", ":y" and ":" ranges should be handled
+as pairs even if x == y, but not for "f(x:y)" nor "x::y" (issue3474, issue4774)
+
+  $ hg clone -q a dumbspec
+  $ cd dumbspec
+  $ echo "wdir" > file.txt
 
   $ hg diff -r 2:2
+  $ hg diff -r 2:.
+  $ hg diff -r 2:
+  $ hg diff -r :0
+  $ hg diff -r '2:first(2:2)'
+  $ hg diff -r 'first(2:2)' --nodates
+  diff -r bf5ff72eb7e0 file.txt
+  --- a/file.txt
+  +++ b/file.txt
+  @@ -1,1 +1,1 @@
+  -third
+  +wdir
+  $ hg diff -r 2::2 --nodates
+  diff -r bf5ff72eb7e0 file.txt
+  --- a/file.txt
+  +++ b/file.txt
+  @@ -1,1 +1,1 @@
+  -third
+  +wdir
   $ hg diff -r "2 and 1"
   abort: empty revision range
   [255]
 
+  $ cd ..
+
+  $ hg clone -qr0 a dumbspec-rev0
+  $ cd dumbspec-rev0
+  $ echo "wdir" > file.txt
+
+  $ hg diff -r :
+  $ hg diff -r 'first(:)' --nodates
+  diff -r 4bb65dda5db4 file.txt
+  --- a/file.txt
+  +++ b/file.txt
+  @@ -1,1 +1,1 @@
+  -first
+  +wdir
+
+  $ cd ..
+
 Testing diff --change when merge:
 
+  $ cd a
+
   $ for i in 1 2 3 4 5 6 7 8 9 10; do
   >    echo $i >> file.txt
   > done
--- a/tests/test-hgweb-descend-empties.t	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-hgweb-descend-empties.t	Tue Aug 18 18:38:56 2015 -0500
@@ -373,11 +373,6 @@
           <p><a href="http://mercurial.selenic.com/" title="Mercurial"><img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a></p>
       </div>
   
-      <div id="corner-top-left"></div>
-      <div id="corner-top-right"></div>
-      <div id="corner-bottom-left"></div>
-      <div id="corner-bottom-right"></div>
-  
   </div>
   
   </body>
--- a/tests/test-module-imports.t	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-module-imports.t	Tue Aug 18 18:38:56 2015 -0500
@@ -112,22 +112,5 @@
 these may expose other cycles.
 
   $ hg locate 'mercurial/**.py' 'hgext/**.py' | sed 's-\\-/-g' | python "$import_checker" -
-  mercurial/dispatch.py mixed imports
-     stdlib:    commands
-     relative:  error, extensions, fancyopts, hg, hook, util
-  mercurial/fileset.py mixed imports
-     stdlib:    parser
-     relative:  error, merge, util
-  mercurial/revset.py mixed imports
-     stdlib:    parser
-     relative:  error, hbisect, phases, util
-  mercurial/templater.py mixed imports
-     stdlib:    parser
-     relative:  config, error, templatefilters, templatekw, util
-  mercurial/ui.py mixed imports
-     stdlib:    formatter
-     relative:  config, error, progress, scmutil, util
-  Import cycle: mercurial.cmdutil -> mercurial.context -> mercurial.subrepo -> mercurial.cmdutil
   Import cycle: hgext.largefiles.basestore -> hgext.largefiles.localstore -> hgext.largefiles.basestore
-  Import cycle: mercurial.commands -> mercurial.commandserver -> mercurial.dispatch -> mercurial.commands
   [1]
--- a/tests/test-parseindex.t	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-parseindex.t	Tue Aug 18 18:38:56 2015 -0500
@@ -60,9 +60,41 @@
 
   $ cd ..
 
-Test corrupted p1/p2 fields that could cause SEGV at parsers.c:
+#if no-pure
+
+Test SEGV caused by bad revision passed to reachableroots() (issue4775):
+
+  $ cd a
 
-#if no-pure
+  $ python <<EOF
+  > from mercurial import changelog, scmutil
+  > cl = changelog.changelog(scmutil.vfs('.hg/store'))
+  > print 'goods:'
+  > for head in [0, len(cl) - 1, -1]:
+  >     print'%s: %r' % (head, cl.reachableroots(0, [head], set([0])))
+  > print 'bads:'
+  > for head in [len(cl), 10000, -2, -10000, None]:
+  >     print '%s:' % head,
+  >     try:
+  >         cl.reachableroots(0, [head], set([0]))
+  >         print 'uncaught buffer overflow?'
+  >     except (IndexError, TypeError) as inst:
+  >         print inst
+  > EOF
+  goods:
+  0: <baseset [0]>
+  1: <baseset [0]>
+  -1: <baseset []>
+  bads:
+  2: head out of range
+  10000: head out of range
+  -2: head out of range
+  -10000: head out of range
+  None: an integer is required
+
+  $ cd ..
+
+Test corrupted p1/p2 fields that could cause SEGV at parsers.c:
 
   $ mkdir invalidparent
   $ cd invalidparent
@@ -94,6 +126,8 @@
   > cl = changelog.changelog(scmutil.vfs(sys.argv[1]))
   > n0, n1 = cl.node(0), cl.node(1)
   > ops = [
+  >     ('reachableroots',
+  >      lambda: cl.index.reachableroots(0, [1], set([0]), False)),
   >     ('compute_phases_map_sets', lambda: cl.computephases([[0], []])),
   >     ('index_headrevs', lambda: cl.headrevs()),
   >     ('find_gca_candidates', lambda: cl.commonancestorsheads(n0, n1)),
@@ -109,11 +143,13 @@
   > EOF
 
   $ python test.py limit/.hg/store
+  reachableroots: parent out of range
   compute_phases_map_sets: parent out of range
   index_headrevs: parent out of range
   find_gca_candidates: parent out of range
   find_deepest: parent out of range
   $ python test.py segv/.hg/store
+  reachableroots: parent out of range
   compute_phases_map_sets: parent out of range
   index_headrevs: parent out of range
   find_gca_candidates: parent out of range
--- a/tests/test-revset.t	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-revset.t	Tue Aug 18 18:38:56 2015 -0500
@@ -197,11 +197,53 @@
   <filteredset
     <baseset [7]>>
   7
-  $ try -- '-a-b-c-' # complains
-  hg: parse error at 7: not a prefix: end
-  [255]
-  $ log -a-b-c- # succeeds with fallback
+
+names that should be caught by fallback mechanism
+
+  $ try -- '-a-b-c-'
+  ('symbol', '-a-b-c-')
+  * set:
+  <baseset [4]>
+  4
+  $ log -a-b-c-
+  4
+  $ try '+a+b+c+'
+  ('symbol', '+a+b+c+')
+  * set:
+  <baseset [3]>
+  3
+  $ try '+a+b+c+:'
+  (rangepost
+    ('symbol', '+a+b+c+'))
+  * set:
+  <spanset+ 3:9>
+  3
   4
+  5
+  6
+  7
+  8
+  9
+  $ try ':+a+b+c+'
+  (rangepre
+    ('symbol', '+a+b+c+'))
+  * set:
+  <spanset+ 0:3>
+  0
+  1
+  2
+  3
+  $ try -- '-a-b-c-:+a+b+c+'
+  (range
+    ('symbol', '-a-b-c-')
+    ('symbol', '+a+b+c+'))
+  * set:
+  <spanset- 3:4>
+  4
+  3
+  $ log '-a-b-c-:+a+b+c+'
+  4
+  3
 
   $ try -- -a-b-c--a # complains
   (minus
@@ -1473,10 +1515,16 @@
 (single rev)
 
   $ hg diff -r 'tip^' -r 'tip^'
-  $ hg diff -r 'tip^::tip^ or tip^'
+  $ hg diff -r 'tip^:tip^'
 
 (single rev that does not looks like a range)
 
+  $ hg diff -r 'tip^::tip^ or tip^'
+  diff -r d5d0dcbdc4d9 .hgtags
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hgtags	* (glob)
+  @@ -0,0 +1,1 @@
+  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
   $ hg diff -r 'tip^ or tip^'
   diff -r d5d0dcbdc4d9 .hgtags
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
--- a/tests/test-wireproto.py	Tue Aug 18 18:37:50 2015 -0500
+++ b/tests/test-wireproto.py	Tue Aug 18 18:38:56 2015 -0500
@@ -12,6 +12,10 @@
 class clientpeer(wireproto.wirepeer):
     def __init__(self, serverrepo):
         self.serverrepo = serverrepo
+
+    def _capabilities(self):
+        return ['batch']
+
     def _call(self, cmd, **args):
         return wireproto.dispatch(self.serverrepo, proto(args), cmd)