merge with stable
authorMatt Mackall <mpm@selenic.com>
Tue, 14 May 2013 18:52:52 -0500
changeset 19196 0fbcabe523bc
parent 19195 9311cd5c09ed (diff)
parent 19193 278057693a1d (current diff)
child 19199 d6d0f1ed8ebb
merge with stable
--- a/contrib/check-code.py	Tue May 14 18:43:53 2013 -0500
+++ b/contrib/check-code.py	Tue May 14 18:52:52 2013 -0500
@@ -109,6 +109,16 @@
     (r'^  changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
      winglobmsg),
     (r'^  pulling from \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
+    (r'^  reverting .*/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
+    (r'^  cloning subrepo \S+/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
+    (r'^  pushing to \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
+    (r'^  pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg,
+     '\$TESTTMP/unix-repo$'),
+    (r'^  moving \S+/.*[^)]$', winglobmsg),
+    (r'^  no changes made to subrepo since.*/.*[^)]$',
+     winglobmsg, '\$TESTTMP/unix-repo$'),
+    (r'^  .*: largefile \S+ not available from file:.*/.*[^)]$',
+     winglobmsg, '\$TESTTMP/unix-repo$'),
   ],
   # warnings
   [
--- a/hgext/convert/common.py	Tue May 14 18:43:53 2013 -0500
+++ b/hgext/convert/common.py	Tue May 14 18:52:52 2013 -0500
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import base64, errno, subprocess, os, datetime
+import base64, errno, subprocess, os, datetime, re
 import cPickle as pickle
 from mercurial import util
 from mercurial.i18n import _
@@ -63,6 +63,14 @@
 
         self.encoding = 'utf-8'
 
+    def checkhexformat(self, revstr):
+        """ fails if revstr is not a 40 byte hex. mercurial and git both uses
+            such format for their revision numbering
+        """
+        if not re.match(r'[0-9a-fA-F]{40,40}$', revstr):
+            raise util.Abort(_('splicemap entry %s is not a valid revision'
+                               ' identifier') % revstr)
+
     def before(self):
         pass
 
@@ -164,6 +172,13 @@
         """
         return {}
 
+    def checkrevformat(self, revstr):
+        """revstr is a string that describes a revision in the given
+           source control system.  Return true if revstr has correct
+           format.
+        """
+        return True
+
 class converter_sink(object):
     """Conversion sink (target) interface"""
 
@@ -424,34 +439,6 @@
             self.fp.close()
             self.fp = None
 
-def parsesplicemap(path):
-    """Parse a splicemap, return a child/parents dictionary."""
-    if not path:
-        return {}
-    m = {}
-    try:
-        fp = open(path, 'r')
-        for i, line in enumerate(fp):
-            line = line.splitlines()[0].rstrip()
-            if not line:
-                # Ignore blank lines
-                continue
-            try:
-                child, parents = line.split(' ', 1)
-                parents = parents.replace(',', ' ').split()
-            except ValueError:
-                raise util.Abort(_('syntax error in %s(%d): child parent1'
-                                   '[,parent2] expected') % (path, i + 1))
-            pp = []
-            for p in parents:
-                if p not in pp:
-                    pp.append(p)
-            m[child] = pp
-    except IOError, e:
-        if e.errno != errno.ENOENT:
-            raise
-    return m
-
 def makedatetimestamp(t):
     """Like util.makedate() but for time t instead of current time"""
     delta = (datetime.datetime.utcfromtimestamp(t) -
--- a/hgext/convert/convcmd.py	Tue May 14 18:43:53 2013 -0500
+++ b/hgext/convert/convcmd.py	Tue May 14 18:52:52 2013 -0500
@@ -15,9 +15,9 @@
 from gnuarch import gnuarch_source
 from bzr import bzr_source
 from p4 import p4_source
-import filemap, common
+import filemap
 
-import os, shutil
+import os, shutil, shlex
 from mercurial import hg, util, encoding
 from mercurial.i18n import _
 
@@ -118,9 +118,53 @@
             self.readauthormap(opts.get('authormap'))
             self.authorfile = self.dest.authorfile()
 
-        self.splicemap = common.parsesplicemap(opts.get('splicemap'))
+        self.splicemap = self.parsesplicemap(opts.get('splicemap'))
         self.branchmap = mapfile(ui, opts.get('branchmap'))
 
+    def parsesplicemap(self, path):
+        """ check and validate the splicemap format and
+            return a child/parents dictionary.
+            Format checking has two parts.
+            1. generic format which is same across all source types
+            2. specific format checking which may be different for
+               different source type.  This logic is implemented in
+               checkrevformat function in source files like
+               hg.py, subversion.py etc.
+        """
+
+        if not path:
+            return {}
+        m = {}
+        try:
+            fp = open(path, 'r')
+            for i, line in enumerate(fp):
+                line = line.splitlines()[0].rstrip()
+                if not line:
+                    # Ignore blank lines
+                    continue
+                # split line
+                lex = shlex.shlex(line, posix=True)
+                lex.whitespace_split = True
+                lex.whitespace += ','
+                line = list(lex)
+                # check number of parents
+                if not (2 <= len(line) <= 3):
+                    raise util.Abort(_('syntax error in %s(%d): child parent1'
+                                       '[,parent2] expected') % (path, i + 1))
+                for part in line:
+                    self.source.checkrevformat(part)
+                child, p1, p2 = line[0], line[1:2], line[2:]
+                if p1 == p2:
+                    m[child] = p1
+                else:
+                    m[child] = p1 + p2
+         # if file does not exist or error reading, exit
+        except IOError:
+            raise util.Abort(_('splicemap file not found or error reading %s:')
+                               % path)
+        return m
+
+
     def walktree(self, heads):
         '''Return a mapping that identifies the uncommitted parents of every
         uncommitted changeset.'''
--- a/hgext/convert/git.py	Tue May 14 18:43:53 2013 -0500
+++ b/hgext/convert/git.py	Tue May 14 18:52:52 2013 -0500
@@ -296,3 +296,8 @@
                 pass
 
         return bookmarks
+
+    def checkrevformat(self, revstr):
+        """ git revision string is a 40 byte hex """
+        self.checkhexformat(revstr)
+
--- a/hgext/convert/hg.py	Tue May 14 18:43:53 2013 -0500
+++ b/hgext/convert/hg.py	Tue May 14 18:52:52 2013 -0500
@@ -397,3 +397,7 @@
 
     def getbookmarks(self):
         return bookmarks.listbookmarks(self.repo)
+
+    def checkrevformat(self, revstr):
+        """ Mercurial, revision string is a 40 byte hex """
+        self.checkhexformat(revstr)
--- a/hgext/convert/subversion.py	Tue May 14 18:43:53 2013 -0500
+++ b/hgext/convert/subversion.py	Tue May 14 18:52:52 2013 -0500
@@ -452,6 +452,14 @@
         del self.commits[rev]
         return commit
 
+    def checkrevformat(self, revstr):
+        """ fails if revision format does not match the correct format"""
+        if not re.match(r'svn:[0-9a-f]{8,8}-[0-9a-f]{4,4}-'
+                              '[0-9a-f]{4,4}-[0-9a-f]{4,4}-[0-9a-f]'
+                              '{12,12}(.*)\@[0-9]+$',revstr):
+            raise util.Abort(_('splicemap entry %s is not a valid revision'
+                               ' identifier') % revstr)
+
     def gettags(self):
         tags = {}
         if self.tags is None:
--- a/hgext/inotify/client.py	Tue May 14 18:43:53 2013 -0500
+++ b/hgext/inotify/client.py	Tue May 14 18:52:52 2013 -0500
@@ -159,7 +159,8 @@
             vdirs = cs.read(nbytes)
             if vdirs:
                 for vdir in vdirs.split('\0'):
-                    match.dir(vdir)
+                    if match.explicitdir:
+                        match.explicitdir(vdir)
 
         return results
 
--- a/hgext/purge.py	Tue May 14 18:43:53 2013 -0500
+++ b/hgext/purge.py	Tue May 14 18:52:52 2013 -0500
@@ -97,7 +97,7 @@
 
     directories = []
     match = scmutil.match(repo[None], dirs, opts)
-    match.dir = directories.append
+    match.explicitdir = match.traversedir = directories.append
     status = repo.status(match=match, ignored=opts['all'], unknown=True)
 
     for f in sorted(status[4] + status[5]):
--- a/mercurial/commands.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/commands.py	Tue May 14 18:52:52 2013 -0500
@@ -767,9 +767,8 @@
     ('d', 'delete', False, _('delete a given bookmark')),
     ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
     ('i', 'inactive', False, _('mark a bookmark inactive'))],
-    _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
-def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
-             rename=None, inactive=False):
+    _('hg bookmarks [OPTIONS]... [NAME]...'))
+def bookmark(ui, repo, *names, **opts):
     '''track a line of development with movable markers
 
     Bookmarks are pointers to certain commits that move when committing.
@@ -796,6 +795,12 @@
     active even if -i/--inactive is not given. If no NAME is given, the
     current active bookmark will be marked inactive.
     '''
+    force = opts.get('force')
+    rev = opts.get('rev')
+    delete = opts.get('delete')
+    rename = opts.get('rename')
+    inactive = opts.get('inactive')
+
     hexfn = ui.debugflag and hex or short
     marks = repo._bookmarks
     cur   = repo.changectx('.').node()
@@ -846,21 +851,24 @@
         raise util.Abort(_("--rev is incompatible with --delete"))
     if rename and rev:
         raise util.Abort(_("--rev is incompatible with --rename"))
-    if mark is None and (delete or rev):
+    if not names and (delete or rev):
         raise util.Abort(_("bookmark name required"))
 
     if delete:
-        if mark not in marks:
-            raise util.Abort(_("bookmark '%s' does not exist") % mark)
-        if mark == repo._bookmarkcurrent:
-            bookmarks.setcurrent(repo, None)
-        del marks[mark]
+        for mark in names:
+            if mark not in marks:
+                raise util.Abort(_("bookmark '%s' does not exist") % mark)
+            if mark == repo._bookmarkcurrent:
+                bookmarks.setcurrent(repo, None)
+            del marks[mark]
         marks.write()
 
     elif rename:
-        if mark is None:
+        if not names:
             raise util.Abort(_("new bookmark name required"))
-        mark = checkformat(mark)
+        elif len(names) > 1:
+            raise util.Abort(_("only one new bookmark name allowed"))
+        mark = checkformat(names[0])
         if rename not in marks:
             raise util.Abort(_("bookmark '%s' does not exist") % rename)
         checkconflict(repo, mark, force)
@@ -870,19 +878,23 @@
         del marks[rename]
         marks.write()
 
-    elif mark is not None:
-        mark = checkformat(mark)
-        if inactive and mark == repo._bookmarkcurrent:
-            bookmarks.setcurrent(repo, None)
-            return
-        tgt = cur
-        if rev:
-            tgt = scmutil.revsingle(repo, rev).node()
-        checkconflict(repo, mark, force, tgt)
-        marks[mark] = tgt
-        if not inactive and cur == marks[mark] and not rev:
-            bookmarks.setcurrent(repo, mark)
-        elif cur != tgt and mark == repo._bookmarkcurrent:
+    elif names:
+        newact = None
+        for mark in names:
+            mark = checkformat(mark)
+            if newact is None:
+                newact = mark
+            if inactive and mark == repo._bookmarkcurrent:
+                bookmarks.setcurrent(repo, None)
+                return
+            tgt = cur
+            if rev:
+                tgt = scmutil.revsingle(repo, rev).node()
+            checkconflict(repo, mark, force, tgt)
+            marks[mark] = tgt
+        if not inactive and cur == marks[newact] and not rev:
+            bookmarks.setcurrent(repo, newact)
+        elif cur != tgt and newact == repo._bookmarkcurrent:
             bookmarks.setcurrent(repo, None)
         marks.write()
 
--- a/mercurial/context.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/context.py	Tue May 14 18:52:52 2013 -0500
@@ -398,7 +398,7 @@
                 ("bad args: changeid=%r, fileid=%r, changectx=%r"
                  % (changeid, fileid, changectx))
 
-        if filelog:
+        if filelog is not None:
             self._filelog = filelog
 
         if changeid is not None:
@@ -437,7 +437,9 @@
 
     @propertycache
     def _changeid(self):
-        if '_changectx' in self.__dict__:
+        if '_changeid' in self.__dict__:
+            return self._changeid
+        elif '_changectx' in self.__dict__:
             return self._changectx.rev()
         else:
             return self._filelog.linkrev(self._filerev)
@@ -1167,7 +1169,7 @@
         self._changeid = None
         self._filerev = self._filenode = None
 
-        if filelog:
+        if filelog is not None:
             self._filelog = filelog
         if workingctx:
             self._changectx = workingctx
--- a/mercurial/copies.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/copies.py	Tue May 14 18:52:52 2013 -0500
@@ -222,65 +222,8 @@
     fullcopy = {}
     diverge = {}
 
-    def related(f1, f2, limit):
-        # Walk back to common ancestor to see if the two files originate
-        # from the same file. Since workingfilectx's rev() is None it messes
-        # up the integer comparison logic, hence the pre-step check for
-        # None (f1 and f2 can only be workingfilectx's initially).
-
-        if f1 == f2:
-            return f1 # a match
-
-        g1, g2 = f1.ancestors(), f2.ancestors()
-        try:
-            f1r, f2r = f1.rev(), f2.rev()
-
-            if f1r is None:
-                f1 = g1.next()
-            if f2r is None:
-                f2 = g2.next()
-
-            while True:
-                f1r, f2r = f1.rev(), f2.rev()
-                if f1r > f2r:
-                    f1 = g1.next()
-                elif f2r > f1r:
-                    f2 = g2.next()
-                elif f1 == f2:
-                    return f1 # a match
-                elif f1r == f2r or f1r < limit or f2r < limit:
-                    return False # copy no longer relevant
-        except StopIteration:
-            return False
-
-    def checkcopies(f, m1, m2):
-        '''check possible copies of f from m1 to m2'''
-        of = None
-        seen = set([f])
-        for oc in ctx(f, m1[f]).ancestors():
-            ocr = oc.rev()
-            of = oc.path()
-            if of in seen:
-                # check limit late - grab last rename before
-                if ocr < limit:
-                    break
-                continue
-            seen.add(of)
-
-            fullcopy[f] = of # remember for dir rename detection
-            if of not in m2:
-                continue # no match, keep looking
-            if m2[of] == ma.get(of):
-                break # no merge needed, quit early
-            c2 = ctx(of, m2[of])
-            cr = related(oc, c2, ca.rev())
-            if cr and (of == f or of == c2.path()): # non-divergent
-                copy[f] = of
-                of = None
-                break
-
-        if of in ma:
-            diverge.setdefault(of, []).append(f)
+    def _checkcopies(f, m1, m2):
+        checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy)
 
     repo.ui.debug("  searching for copies back to rev %d\n" % limit)
 
@@ -295,9 +238,9 @@
                       % "\n   ".join(u2))
 
     for f in u1:
-        checkcopies(f, m1, m2)
+        _checkcopies(f, m1, m2)
     for f in u2:
-        checkcopies(f, m2, m1)
+        _checkcopies(f, m2, m1)
 
     renamedelete = {}
     renamedelete2 = set()
@@ -386,3 +329,78 @@
                     break
 
     return copy, movewithdir, diverge, renamedelete
+
+def checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy):
+    """
+    check possible copies of f from m1 to m2
+
+    ctx = function accepting (filename, node) that returns a filectx.
+    f = the filename to check
+    m1 = the source manifest
+    m2 = the destination manifest
+    ca = the changectx of the common ancestor
+    limit = the rev number to not search beyond
+    diverge = record all diverges in this dict
+    copy = record all non-divergent copies in this dict
+    fullcopy = record all copies in this dict
+    """
+
+    ma = ca.manifest()
+
+    def _related(f1, f2, limit):
+        # Walk back to common ancestor to see if the two files originate
+        # from the same file. Since workingfilectx's rev() is None it messes
+        # up the integer comparison logic, hence the pre-step check for
+        # None (f1 and f2 can only be workingfilectx's initially).
+
+        if f1 == f2:
+            return f1 # a match
+
+        g1, g2 = f1.ancestors(), f2.ancestors()
+        try:
+            f1r, f2r = f1.rev(), f2.rev()
+
+            if f1r is None:
+                f1 = g1.next()
+            if f2r is None:
+                f2 = g2.next()
+
+            while True:
+                f1r, f2r = f1.rev(), f2.rev()
+                if f1r > f2r:
+                    f1 = g1.next()
+                elif f2r > f1r:
+                    f2 = g2.next()
+                elif f1 == f2:
+                    return f1 # a match
+                elif f1r == f2r or f1r < limit or f2r < limit:
+                    return False # copy no longer relevant
+        except StopIteration:
+            return False
+
+    of = None
+    seen = set([f])
+    for oc in ctx(f, m1[f]).ancestors():
+        ocr = oc.rev()
+        of = oc.path()
+        if of in seen:
+            # check limit late - grab last rename before
+            if ocr < limit:
+                break
+            continue
+        seen.add(of)
+
+        fullcopy[f] = of # remember for dir rename detection
+        if of not in m2:
+            continue # no match, keep looking
+        if m2[of] == ma.get(of):
+            break # no merge needed, quit early
+        c2 = ctx(of, m2[of])
+        cr = _related(oc, c2, ca.rev())
+        if cr and (of == f or of == c2.path()): # non-divergent
+            copy[f] = of
+            of = None
+            break
+
+    if of in ma:
+        diverge.setdefault(of, []).append(f)
--- a/mercurial/dirstate.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/dirstate.py	Tue May 14 18:52:52 2013 -0500
@@ -522,18 +522,15 @@
                 return True
         return False
 
-    def walk(self, match, subrepos, unknown, ignored):
-        '''
-        Walk recursively through the directory tree, finding all files
-        matched by match.
+    def _walkexplicit(self, match, subrepos):
+        '''Get stat data about the files explicitly specified by match.
 
-        Return a dict mapping filename to stat-like object (either
-        mercurial.osutil.stat instance or return value of os.stat()).
-        '''
-
-        def fwarn(f, msg):
-            self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
-            return False
+        Return a triple (results, dirsfound, dirsnotfound).
+        - results is a mapping from filename to stat result. It also contains
+          listings mapping subrepos and .hg to None.
+        - dirsfound is a list of files found to be directories.
+        - dirsnotfound is a list of files that the dirstate thinks are
+          directories and that were not found.'''
 
         def badtype(mode):
             kind = _('unknown')
@@ -549,41 +546,23 @@
                 kind = _('directory')
             return _('unsupported file type (type is %s)') % kind
 
-        ignore = self._ignore
-        dirignore = self._dirignore
-        if ignored:
-            ignore = util.never
-            dirignore = util.never
-        elif not unknown:
-            # if unknown and ignored are False, skip step 2
-            ignore = util.always
-            dirignore = util.always
-
-        matchfn = match.matchfn
-        matchalways = match.always()
+        matchedir = match.explicitdir
         badfn = match.bad
         dmap = self._map
         normpath = util.normpath
-        listdir = osutil.listdir
         lstat = os.lstat
         getkind = stat.S_IFMT
         dirkind = stat.S_IFDIR
         regkind = stat.S_IFREG
         lnkkind = stat.S_IFLNK
         join = self._join
-        work = []
-        wadd = work.append
+        dirsfound = []
+        foundadd = dirsfound.append
+        dirsnotfound = []
+        notfoundadd = dirsnotfound.append
 
-        exact = skipstep3 = False
-        if matchfn == match.exact: # match.exact
-            exact = True
-            dirignore = util.always # skip step 2
-        elif match.files() and not match.anypats(): # match.match, no patterns
-            skipstep3 = True
-
-        if not exact and self._checkcase:
+        if match.matchfn != match.exact and self._checkcase:
             normalize = self._normalize
-            skipstep3 = False
         else:
             normalize = None
 
@@ -604,7 +583,6 @@
         results = dict.fromkeys(subrepos)
         results['.hg'] = None
 
-        # step 1: find all explicit files
         for ff in files:
             if normalize:
                 nf = normalize(normpath(ff), False, True)
@@ -617,13 +595,12 @@
                 st = lstat(join(nf))
                 kind = getkind(st.st_mode)
                 if kind == dirkind:
-                    skipstep3 = False
                     if nf in dmap:
                         #file deleted on disk but still in dirstate
                         results[nf] = None
-                    match.dir(nf)
-                    if not dirignore(nf):
-                        wadd(nf)
+                    if matchedir:
+                        matchedir(nf)
+                    foundadd(nf)
                 elif kind == regkind or kind == lnkkind:
                     results[nf] = st
                 else:
@@ -637,12 +614,75 @@
                     prefix = nf + "/"
                     for fn in dmap:
                         if fn.startswith(prefix):
-                            match.dir(nf)
-                            skipstep3 = False
+                            if matchedir:
+                                matchedir(nf)
+                            notfoundadd(nf)
                             break
                     else:
                         badfn(ff, inst.strerror)
 
+        return results, dirsfound, dirsnotfound
+
+    def walk(self, match, subrepos, unknown, ignored, full=True):
+        '''
+        Walk recursively through the directory tree, finding all files
+        matched by match.
+
+        If full is False, maybe skip some known-clean files.
+
+        Return a dict mapping filename to stat-like object (either
+        mercurial.osutil.stat instance or return value of os.stat()).
+
+        '''
+        # full is a flag that extensions that hook into walk can use -- this
+        # implementation doesn't use it at all. This satisfies the contract
+        # because we only guarantee a "maybe".
+
+        def fwarn(f, msg):
+            self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
+            return False
+
+        ignore = self._ignore
+        dirignore = self._dirignore
+        if ignored:
+            ignore = util.never
+            dirignore = util.never
+        elif not unknown:
+            # if unknown and ignored are False, skip step 2
+            ignore = util.always
+            dirignore = util.always
+
+        matchfn = match.matchfn
+        matchalways = match.always()
+        matchtdir = match.traversedir
+        dmap = self._map
+        listdir = osutil.listdir
+        lstat = os.lstat
+        dirkind = stat.S_IFDIR
+        regkind = stat.S_IFREG
+        lnkkind = stat.S_IFLNK
+        join = self._join
+
+        exact = skipstep3 = False
+        if matchfn == match.exact: # match.exact
+            exact = True
+            dirignore = util.always # skip step 2
+        elif match.files() and not match.anypats(): # match.match, no patterns
+            skipstep3 = True
+
+        if not exact and self._checkcase:
+            normalize = self._normalize
+            skipstep3 = False
+        else:
+            normalize = None
+
+        # step 1: find all explicit files
+        results, work, dirsnotfound = self._walkexplicit(match, subrepos)
+
+        skipstep3 = skipstep3 and not (work or dirsnotfound)
+        work = [d for d in work if not dirignore(d)]
+        wadd = work.append
+
         # step 2: visit subdirectories
         while work:
             nd = work.pop()
@@ -666,7 +706,8 @@
                 if nf not in results:
                     if kind == dirkind:
                         if not ignore(nf):
-                            match.dir(nf)
+                            if matchtdir:
+                                matchtdir(nf)
                             wadd(nf)
                         if nf in dmap and (matchalways or matchfn(nf)):
                             results[nf] = None
@@ -766,8 +807,13 @@
 
         lnkkind = stat.S_IFLNK
 
-        for fn, st in self.walk(match, subrepos, listunknown,
-                                listignored).iteritems():
+        # We need to do full walks when either
+        # - we're listing all clean files, or
+        # - match.traversedir does something, because match.traversedir should
+        #   be called for every dir in the working dir
+        full = listclean or match.traversedir is not None
+        for fn, st in self.walk(match, subrepos, listunknown, listignored,
+                                full=full).iteritems():
             if fn not in dmap:
                 if (listignored or mexact(fn)) and dirignore(fn):
                     if listignored:
--- a/mercurial/filelog.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/filelog.py	Tue May 14 18:52:52 2013 -0500
@@ -31,7 +31,7 @@
 
 class filelog(revlog.revlog):
     def __init__(self, opener, path):
-        revlog.revlog.__init__(self, opener,
+        super(filelog, self).__init__(opener,
                         "/".join(("data", path + ".i")))
 
     def read(self, node):
@@ -64,7 +64,7 @@
             return len(self.read(node))
 
         # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
-        return revlog.revlog.size(self, rev)
+        return super(filelog, self).size(rev)
 
     def cmp(self, node, text):
         """compare text with a given file revision
@@ -76,7 +76,7 @@
         if text.startswith('\1\n'):
             t = '\1\n\1\n' + text
 
-        samehashes = not revlog.revlog.cmp(self, node, t)
+        samehashes = not super(filelog, self).cmp(node, t)
         if samehashes:
             return False
 
--- a/mercurial/fileset.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/fileset.py	Tue May 14 18:52:52 2013 -0500
@@ -263,23 +263,10 @@
         raise error.ParseError(_('invalid match pattern: %s') % e)
     return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
 
-_units = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
-              M=2**20, MB=2**20, G=2**30, GB=2**30)
-
-def _sizetoint(s):
-    try:
-        s = s.strip()
-        for k, v in _units.items():
-            if s.endswith(k):
-                return int(float(s[:-len(k)]) * v)
-        return int(s)
-    except ValueError:
-        raise error.ParseError(_("couldn't parse size: %s") % s)
-
 def _sizetomax(s):
     try:
         s = s.strip()
-        for k, v in _units.items():
+        for k, v in util._sizeunits:
             if s.endswith(k):
                 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
                 n = s[:-len(k)]
@@ -306,23 +293,23 @@
     expr = getstring(x, _("size requires an expression")).strip()
     if '-' in expr: # do we have a range?
         a, b = expr.split('-', 1)
-        a = _sizetoint(a)
-        b = _sizetoint(b)
+        a = util.sizetoint(a)
+        b = util.sizetoint(b)
         m = lambda x: x >= a and x <= b
     elif expr.startswith("<="):
-        a = _sizetoint(expr[2:])
+        a = util.sizetoint(expr[2:])
         m = lambda x: x <= a
     elif expr.startswith("<"):
-        a = _sizetoint(expr[1:])
+        a = util.sizetoint(expr[1:])
         m = lambda x: x < a
     elif expr.startswith(">="):
-        a = _sizetoint(expr[2:])
+        a = util.sizetoint(expr[2:])
         m = lambda x: x >= a
     elif expr.startswith(">"):
-        a = _sizetoint(expr[1:])
+        a = util.sizetoint(expr[1:])
         m = lambda x: x > a
     elif expr[0].isdigit or expr[0] == '.':
-        a = _sizetoint(expr)
+        a = util.sizetoint(expr)
         b = _sizetomax(expr)
         m = lambda x: x >= a and x <= b
     else:
--- a/mercurial/help/templates.txt	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/help/templates.txt	Tue May 14 18:52:52 2013 -0500
@@ -6,8 +6,8 @@
 You can customize output for any "log-like" command: log,
 outgoing, incoming, tip, parents, heads and glog.
 
-Four styles are packaged with Mercurial: default (the style used
-when no explicit preference is passed), compact, changelog,
+Five styles are packaged with Mercurial: default (the style used
+when no explicit preference is passed), compact, changelog, phases
 and xml.
 Usage::
 
--- a/mercurial/httpclient/__init__.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/httpclient/__init__.py	Tue May 14 18:52:52 2013 -0500
@@ -37,6 +37,9 @@
   * implements ssl inline instead of in a different class
 """
 
+# Many functions in this file have too many arguments.
+# pylint: disable=R0913
+
 import cStringIO
 import errno
 import httplib
@@ -117,6 +120,8 @@
 
     def _close(self):
         if self._reader is not None:
+            # We're a friend of the reader class here.
+            # pylint: disable=W0212
             self._reader._close()
 
     def readline(self):
@@ -137,6 +142,7 @@
         return ''.join(blocks)
 
     def read(self, length=None):
+        """Read data from the response body."""
         # if length is None, unbounded read
         while (not self.complete()  # never select on a finished read
                and (not length  # unbounded, so we wait for complete()
@@ -150,7 +156,8 @@
         return r
 
     def _select(self):
-        r, _, _ = select.select([self.sock], [], [], self._timeout)
+        r, unused_write, unused_err = select.select(
+            [self.sock], [], [], self._timeout)
         if not r:
             # socket was not readable. If the response is not
             # complete, raise a timeout.
@@ -170,13 +177,16 @@
         # raise an exception if this is an invalid situation.
         if not data:
             if self._reader:
+                # We're a friend of the reader class here.
+                # pylint: disable=W0212
                 self._reader._close()
             return False
         else:
             self._load_response(data)
             return True
 
-    def _load_response(self, data):
+    # This method gets replaced by _load later, which confuses pylint.
+    def _load_response(self, data): # pylint: disable=E0202
         # Being here implies we're not at the end of the headers yet,
         # since at the end of this method if headers were completely
         # loaded we replace this method with the load() method of the
@@ -201,7 +211,7 @@
 
         # handle 100-continue response
         hdrs, body = self.raw_response.split(self._end_headers, 1)
-        http_ver, status = hdrs.split(' ', 1)
+        unused_http_ver, status = hdrs.split(' ', 1)
         if status.startswith('100'):
             self.raw_response = body
             self.continued = True
@@ -260,9 +270,13 @@
                 self.will_close = True
 
         if body:
+            # We're a friend of the reader class here.
+            # pylint: disable=W0212
             self._reader._load(body)
         logger.debug('headers complete')
         self.headers = headers
+        # We're a friend of the reader class here.
+        # pylint: disable=W0212
         self._load_response = self._reader._load
 
 
@@ -335,9 +349,9 @@
                                                  self._proxy_port))
             if self.ssl:
                 # TODO proxy header support
-                data = self.buildheaders('CONNECT', '%s:%d' % (self.host,
-                                                               self.port),
-                                         {}, HTTP_VER_1_0)
+                data = self._buildheaders('CONNECT', '%s:%d' % (self.host,
+                                                                self.port),
+                                          {}, HTTP_VER_1_0)
                 sock.send(data)
                 sock.setblocking(0)
                 r = self.response_class(sock, self.timeout, 'CONNECT')
@@ -345,6 +359,9 @@
                     'Timed out waiting for CONNECT response from proxy')
                 while not r.complete():
                     try:
+                        # We're a friend of the response class, so let
+                        # us use the private attribute.
+                        # pylint: disable=W0212
                         if not r._select():
                             if not r.complete():
                                 raise timeout_exc
@@ -376,7 +393,7 @@
         sock.setblocking(0)
         self.sock = sock
 
-    def buildheaders(self, method, path, headers, http_ver):
+    def _buildheaders(self, method, path, headers, http_ver):
         if self.ssl and self.port == 443 or self.port == 80:
             # default port for protocol, so leave it out
             hdrhost = self.host
@@ -437,6 +454,11 @@
             return True
         return False
 
+    def _reconnect(self, where):
+        logger.info('reconnecting during %s', where)
+        self.close()
+        self._connect()
+
     def request(self, method, path, body=None, headers={},
                 expect_continue=False):
         """Send a request to the server.
@@ -474,16 +496,11 @@
                 raise BadRequestData('body has no __len__() nor read()')
 
         self._connect()
-        outgoing_headers = self.buildheaders(
+        outgoing_headers = self._buildheaders(
             method, path, hdrs, self.http_version)
         response = None
         first = True
 
-        def reconnect(where):
-            logger.info('reconnecting during %s', where)
-            self.close()
-            self._connect()
-
         while ((outgoing_headers or body)
                and not (response and response.complete())):
             select_timeout = self.timeout
@@ -523,14 +540,17 @@
                     except socket.sslerror, e:
                         if e.args[0] != socket.SSL_ERROR_WANT_READ:
                             raise
-                        logger.debug(
-                            'SSL_ERROR_WANT_READ while sending data, retrying...')
+                        logger.debug('SSL_ERROR_WANT_READ while sending '
+                                     'data, retrying...')
                         continue
                     if not data:
                         logger.info('socket appears closed in read')
                         self.sock = None
                         self._current_response = None
                         if response is not None:
+                            # We're a friend of the response class, so let
+                            # us use the private attribute.
+                            # pylint: disable=W0212
                             response._close()
                         # This if/elif ladder is a bit subtle,
                         # comments in each branch should help.
@@ -550,7 +570,7 @@
                             logger.info(
                                 'Connection appeared closed in read on first'
                                 ' request loop iteration, will retry.')
-                            reconnect('read')
+                            self._reconnect('read')
                             continue
                         else:
                             # We didn't just send the first data hunk,
@@ -563,7 +583,11 @@
                                 'response was missing or incomplete!')
                     logger.debug('read %d bytes in request()', len(data))
                     if response is None:
-                        response = self.response_class(r[0], self.timeout, method)
+                        response = self.response_class(
+                            r[0], self.timeout, method)
+                    # We're a friend of the response class, so let us
+                    # use the private attribute.
+                    # pylint: disable=W0212
                     response._load_response(data)
                     # Jump to the next select() call so we load more
                     # data if the server is still sending us content.
@@ -576,6 +600,8 @@
             if w and out:
                 try:
                     if getattr(out, 'read', False):
+                        # pylint guesses the type of out incorrectly here
+                        # pylint: disable=E1103
                         data = out.read(OUTGOING_BUFFER_SIZE)
                         if not data:
                             continue
@@ -599,14 +625,10 @@
                     elif (e[0] not in (errno.ECONNRESET, errno.EPIPE)
                           and not first):
                         raise
-                    reconnect('write')
+                    self._reconnect('write')
                     amt = self.sock.send(out)
                 logger.debug('sent %d', amt)
                 first = False
-                # stash data we think we sent in case the socket breaks
-                # when we read from it
-                if was_first:
-                    sent_data = out[:amt]
                 if out is body:
                     body = out[amt:]
                 else:
@@ -616,7 +638,6 @@
         # the whole request
         if response is None:
             response = self.response_class(self.sock, self.timeout, method)
-        complete = response.complete()
         data_left = bool(outgoing_headers or body)
         if data_left:
             logger.info('stopped sending request early, '
@@ -629,10 +650,14 @@
         self._current_response = response
 
     def getresponse(self):
+        """Returns the response to the most recent request."""
         if self._current_response is None:
             raise httplib.ResponseNotReady()
         r = self._current_response
         while r.headers is None:
+            # We're a friend of the response class, so let us use the
+            # private attribute.
+            # pylint: disable=W0212
             if not r._select() and not r.complete():
                 raise _readers.HTTPRemoteClosedError()
         if r.will_close:
--- a/mercurial/httpclient/_readers.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/httpclient/_readers.py	Tue May 14 18:52:52 2013 -0500
@@ -33,7 +33,6 @@
 """
 
 import httplib
-import itertools
 import logging
 
 logger = logging.getLogger(__name__)
@@ -59,33 +58,35 @@
         self._done_chunks = []
         self.available_data = 0
 
-    def addchunk(self, data):
+    def _addchunk(self, data):
         self._done_chunks.append(data)
         self.available_data += len(data)
 
-    def pushchunk(self, data):
+    def _pushchunk(self, data):
         self._done_chunks.insert(0, data)
         self.available_data += len(data)
 
-    def popchunk(self):
+    def _popchunk(self):
         b = self._done_chunks.pop(0)
         self.available_data -= len(b)
 
         return b
 
     def done(self):
+        """Returns true if the response body is entirely read."""
         return self._finished
 
     def read(self, amt):
+        """Read amt bytes from the response body."""
         if self.available_data < amt and not self._finished:
             raise ReadNotReady()
         blocks = []
         need = amt
         while self._done_chunks:
-            b = self.popchunk()
+            b = self._popchunk()
             if len(b) > need:
                 nb = b[:need]
-                self.pushchunk(b[need:])
+                self._pushchunk(b[need:])
                 b = nb
             blocks.append(b)
             need -= len(b)
@@ -107,11 +108,11 @@
             blocks = []
 
         while self._done_chunks:
-            b = self.popchunk()
+            b = self._popchunk()
             i = b.find(delimstr) + len(delimstr)
             if i:
                 if i < len(b):
-                    self.pushchunk(b[i:])
+                    self._pushchunk(b[i:])
                 blocks.append(b[:i])
                 break
             else:
@@ -154,8 +155,9 @@
         if data:
             assert not self._finished, (
                 'tried to add data (%r) to a closed reader!' % data)
-        logger.debug('%s read an additional %d data', self.name, len(data))
-        self.addchunk(data)
+        logger.debug('%s read an additional %d data',
+                     self.name, len(data)) # pylint: disable=E1101
+        self._addchunk(data)
 
 
 class CloseIsEndReader(AbstractSimpleReader):
@@ -172,7 +174,7 @@
     name = 'content-length'
 
     def __init__(self, amount):
-        AbstractReader.__init__(self)
+        AbstractSimpleReader.__init__(self)
         self._amount = amount
         if amount == 0:
             self._finished = True
@@ -199,7 +201,8 @@
         logger.debug('chunked read an additional %d data', len(data))
         position = 0
         if self._leftover_data:
-            logger.debug('chunked reader trying to finish block from leftover data')
+            logger.debug(
+                'chunked reader trying to finish block from leftover data')
             # TODO: avoid this string concatenation if possible
             data = self._leftover_data + data
             position = self._leftover_skip_amt
@@ -224,6 +227,6 @@
                 self._finished = True
                 logger.debug('closing chunked reader due to chunk of length 0')
                 return
-            self.addchunk(data[block_start:block_start + amt])
+            self._addchunk(data[block_start:block_start + amt])
             position = block_start + amt + len(self._eol)
 # no-check-code
--- a/mercurial/httpclient/socketutil.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/httpclient/socketutil.py	Tue May 14 18:52:52 2013 -0500
@@ -39,7 +39,8 @@
 
 try:
     import ssl
-    ssl.wrap_socket  # make demandimporters load the module
+    # make demandimporters load the module
+    ssl.wrap_socket # pylint: disable=W0104
     have_ssl = True
 except ImportError:
     import httplib
@@ -52,12 +53,13 @@
     create_connection = socket.create_connection
 except AttributeError:
     def create_connection(address):
+        """Backport of socket.create_connection from Python 2.6."""
         host, port = address
         msg = "getaddrinfo returns an empty list"
         sock = None
         for res in socket.getaddrinfo(host, port, 0,
                                       socket.SOCK_STREAM):
-            af, socktype, proto, _canonname, sa = res
+            af, socktype, proto, unused_canonname, sa = res
             try:
                 sock = socket.socket(af, socktype, proto)
                 logger.info("connect: (%s, %s)", host, port)
@@ -80,8 +82,11 @@
     CERT_REQUIRED = ssl.CERT_REQUIRED
 else:
     class FakeSocket(httplib.FakeSocket):
-        """Socket wrapper that supports SSL.
-        """
+        """Socket wrapper that supports SSL."""
+
+        # Silence lint about this goofy backport class
+        # pylint: disable=W0232,E1101,R0903,R0913,C0111
+
         # backport the behavior from Python 2.6, which is to busy wait
         # on the socket instead of anything nice. Sigh.
         # See http://bugs.python.org/issue3890 for more info.
@@ -107,11 +112,16 @@
     CERT_OPTIONAL = 1
     CERT_REQUIRED = 2
 
+    # Disable unused-argument because we're making a dumb wrapper
+    # that's like an upstream method.
+    #
+    # pylint: disable=W0613,R0913
     def wrap_socket(sock, keyfile=None, certfile=None,
                 server_side=False, cert_reqs=CERT_NONE,
                 ssl_version=_PROTOCOL_SSLv23, ca_certs=None,
                 do_handshake_on_connect=True,
                 suppress_ragged_eofs=True):
+        """Backport of ssl.wrap_socket from Python 2.6."""
         if cert_reqs != CERT_NONE and ca_certs:
             raise CertificateValidationUnsupported(
                 'SSL certificate validation requires the ssl module'
@@ -120,6 +130,7 @@
         # borrow httplib's workaround for no ssl.wrap_socket
         sock = FakeSocket(sock, sslob)
         return sock
+    # pylint: enable=W0613,R0913
 
 
 class CertificateValidationUnsupported(Exception):
--- a/mercurial/localrepo.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/localrepo.py	Tue May 14 18:52:52 2013 -0500
@@ -1145,7 +1145,7 @@
 
         if not force:
             vdirs = []
-            match.dir = vdirs.append
+            match.explicitdir = vdirs.append
             match.bad = fail
 
         wlock = self.wlock()
--- a/mercurial/match.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/match.py	Tue May 14 18:52:52 2013 -0500
@@ -119,8 +119,12 @@
         found/accessed, with an error message
         '''
         pass
-    def dir(self, f):
-        pass
+    # If this is set, it will be called when an explicitly listed directory is
+    # visited.
+    explicitdir = None
+    # If this is set, it will be called when a directory discovered by recursive
+    # traversal is visited.
+    traversedir = None
     def missing(self, f):
         pass
     def exact(self, f):
--- a/mercurial/merge.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/merge.py	Tue May 14 18:52:52 2013 -0500
@@ -95,6 +95,7 @@
 def _checkunknownfile(repo, wctx, mctx, f):
     return (not repo.dirstate._ignore(f)
         and os.path.isfile(repo.wjoin(f))
+        and repo.wopener.audit.check(f)
         and repo.dirstate.normalize(f) not in repo.dirstate
         and mctx[f].cmp(wctx[f]))
 
--- a/mercurial/patch.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/patch.py	Tue May 14 18:52:52 2013 -0500
@@ -481,7 +481,7 @@
 
     def close(self):
         wctx = self.repo[None]
-        addremoved = set(self.changed)
+        changed = set(self.changed)
         for src, dst in self.copied:
             scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
         if self.removed:
@@ -491,14 +491,10 @@
                     # File was deleted and no longer belongs to the
                     # dirstate, it was probably marked added then
                     # deleted, and should not be considered by
-                    # addremove().
-                    addremoved.discard(f)
-        if addremoved:
-            cwd = self.repo.getcwd()
-            if cwd:
-                addremoved = [util.pathto(self.repo.root, cwd, f)
-                              for f in addremoved]
-            scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
+                    # marktouched().
+                    changed.discard(f)
+        if changed:
+            scmutil.marktouched(self.repo, changed, self.similarity)
         return sorted(self.changed)
 
 class filestore(object):
@@ -1397,12 +1393,7 @@
                 ui.warn(line + '\n')
     finally:
         if files:
-            cfiles = list(files)
-            cwd = repo.getcwd()
-            if cwd:
-                cfiles = [util.pathto(repo.root, cwd, f)
-                          for f in cfiles]
-            scmutil.addremove(repo, cfiles, similarity=similarity)
+            scmutil.marktouched(repo, files, similarity)
     code = fp.close()
     if code:
         raise PatchError(_("patch command failed: %s") %
--- a/mercurial/scmutil.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/scmutil.py	Tue May 14 18:52:52 2013 -0500
@@ -685,26 +685,11 @@
     if similarity is None:
         similarity = float(opts.get('similarity') or 0)
     # we'd use status here, except handling of symlinks and ignore is tricky
-    added, unknown, deleted, removed = [], [], [], []
-    audit_path = pathauditor(repo.root)
     m = match(repo[None], pats, opts)
     rejected = []
     m.bad = lambda x, y: rejected.append(x)
 
-    ctx = repo[None]
-    dirstate = repo.dirstate
-    walkresults = dirstate.walk(m, sorted(ctx.substate), True, False)
-    for abs, st in walkresults.iteritems():
-        dstate = dirstate[abs]
-        if dstate == '?' and audit_path.check(abs):
-            unknown.append(abs)
-        elif dstate != 'r' and not st:
-            deleted.append(abs)
-        # for finding renames
-        elif dstate == 'r':
-            removed.append(abs)
-        elif dstate == 'a':
-            added.append(abs)
+    added, unknown, deleted, removed = _interestingfiles(repo, m)
 
     unknownset = set(unknown)
     toprint = unknownset.copy()
@@ -718,32 +703,101 @@
                 status = _('removing %s\n') % ((pats and rel) or abs)
             repo.ui.status(status)
 
-    copies = {}
-    if similarity > 0:
-        for old, new, score in similar.findrenames(repo,
-                added + unknown, removed + deleted, similarity):
-            if repo.ui.verbose or not m.exact(old) or not m.exact(new):
-                repo.ui.status(_('recording removal of %s as rename to %s '
-                                 '(%d%% similar)\n') %
-                               (m.rel(old), m.rel(new), score * 100))
-            copies[new] = old
+    renames = _findrenames(repo, m, added + unknown, removed + deleted,
+                           similarity)
 
     if not dry_run:
-        wctx = repo[None]
-        wlock = repo.wlock()
-        try:
-            wctx.forget(deleted)
-            wctx.add(unknown)
-            for new, old in copies.iteritems():
-                wctx.copy(old, new)
-        finally:
-            wlock.release()
+        _markchanges(repo, unknown, deleted, renames)
+
+    for f in rejected:
+        if f in m.files():
+            return 1
+    return 0
+
+def marktouched(repo, files, similarity=0.0):
+    '''Assert that files have somehow been operated upon. files are relative to
+    the repo root.'''
+    m = matchfiles(repo, files)
+    rejected = []
+    m.bad = lambda x, y: rejected.append(x)
+
+    added, unknown, deleted, removed = _interestingfiles(repo, m)
+
+    if repo.ui.verbose:
+        unknownset = set(unknown)
+        toprint = unknownset.copy()
+        toprint.update(deleted)
+        for abs in sorted(toprint):
+            if abs in unknownset:
+                status = _('adding %s\n') % abs
+            else:
+                status = _('removing %s\n') % abs
+            repo.ui.status(status)
+
+    renames = _findrenames(repo, m, added + unknown, removed + deleted,
+                           similarity)
+
+    _markchanges(repo, unknown, deleted, renames)
 
     for f in rejected:
         if f in m.files():
             return 1
     return 0
 
+def _interestingfiles(repo, matcher):
+    '''Walk dirstate with matcher, looking for files that addremove would care
+    about.
+
+    This is different from dirstate.status because it doesn't care about
+    whether files are modified or clean.'''
+    added, unknown, deleted, removed = [], [], [], []
+    audit_path = pathauditor(repo.root)
+
+    ctx = repo[None]
+    dirstate = repo.dirstate
+    walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False)
+    for abs, st in walkresults.iteritems():
+        dstate = dirstate[abs]
+        if dstate == '?' and audit_path.check(abs):
+            unknown.append(abs)
+        elif dstate != 'r' and not st:
+            deleted.append(abs)
+        # for finding renames
+        elif dstate == 'r':
+            removed.append(abs)
+        elif dstate == 'a':
+            added.append(abs)
+
+    return added, unknown, deleted, removed
+
+def _findrenames(repo, matcher, added, removed, similarity):
+    '''Find renames from removed files to added ones.'''
+    renames = {}
+    if similarity > 0:
+        for old, new, score in similar.findrenames(repo, added, removed,
+                                                   similarity):
+            if (repo.ui.verbose or not matcher.exact(old)
+                or not matcher.exact(new)):
+                repo.ui.status(_('recording removal of %s as rename to %s '
+                                 '(%d%% similar)\n') %
+                               (matcher.rel(old), matcher.rel(new),
+                                score * 100))
+            renames[new] = old
+    return renames
+
+def _markchanges(repo, unknown, deleted, renames):
+    '''Marks the files in unknown as added, the files in deleted as removed,
+    and the files in renames as copied.'''
+    wctx = repo[None]
+    wlock = repo.wlock()
+    try:
+        wctx.forget(deleted)
+        wctx.add(unknown)
+        for new, old in renames.iteritems():
+            wctx.copy(old, new)
+    finally:
+        wlock.release()
+
 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
     """Update the dirstate to reflect the intent of copying src to dst. For
     different reasons it might not end with dst being marked as copied from src.
--- a/mercurial/store.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/store.py	Tue May 14 18:52:52 2013 -0500
@@ -322,13 +322,16 @@
     def datafiles(self):
         return self._walk('data', True)
 
+    def topfiles(self):
+        # yield manifest before changelog
+        return reversed(self._walk('', False))
+
     def walk(self):
         '''yields (unencoded, encoded, size)'''
         # yield data files first
         for x in self.datafiles():
             yield x
-        # yield manifest before changelog
-        for x in reversed(self._walk('', False)):
+        for x in self.topfiles():
             yield x
 
     def copylist(self):
--- a/mercurial/templater.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/templater.py	Tue May 14 18:52:52 2013 -0500
@@ -394,6 +394,16 @@
 
 engines = {'default': engine}
 
+def stylelist():
+    path = templatepath()[0]
+    dirlist =  os.listdir(path)
+    stylelist = []
+    for file in dirlist:
+        split = file.split(".")
+        if split[0] == "map-cmdline":
+            stylelist.append(split[1])
+    return ", ".join(sorted(stylelist))
+
 class templater(object):
 
     def __init__(self, mapfile, filters={}, defaults={}, cache={},
@@ -415,7 +425,8 @@
         if not mapfile:
             return
         if not os.path.exists(mapfile):
-            raise util.Abort(_('style not found: %s') % mapfile)
+            raise util.Abort(_("style '%s' not found") % mapfile,
+                             hint=_("available styles: %s") % stylelist())
 
         conf = config.config()
         conf.read(mapfile)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/templates/map-cmdline.phases	Tue May 14 18:52:52 2013 -0500
@@ -0,0 +1,25 @@
+changeset = 'changeset:   {rev}:{node|short}\n{branches}{bookmarks}{tags}phase:       {phase}\n{parents}user:        {author}\ndate:        {date|date}\nsummary:     {desc|firstline}\n\n'
+changeset_quiet = '{rev}:{node|short}\n'
+changeset_verbose = 'changeset:   {rev}:{node|short}\n{branches}{bookmarks}{tags}{parents}user:        {author}\ndate:        {date|date}\n{files}{file_copies_switch}description:\n{desc|strip}\n\n\n'
+changeset_debug = 'changeset:   {rev}:{node}\n{branches}{bookmarks}{tags}phase:       {phase}\n{parents}{manifest}user:        {author}\ndate:        {date|date}\n{file_mods}{file_adds}{file_dels}{file_copies_switch}{extras}description:\n{desc|strip}\n\n\n'
+start_files = 'files:      '
+file = ' {file}'
+end_files = '\n'
+start_file_mods = 'files:      '
+file_mod = ' {file_mod}'
+end_file_mods = '\n'
+start_file_adds = 'files+:     '
+file_add = ' {file_add}'
+end_file_adds = '\n'
+start_file_dels = 'files-:     '
+file_del = ' {file_del}'
+end_file_dels = '\n'
+start_file_copies = 'copies:     '
+file_copy = ' {name} ({source})'
+end_file_copies = '\n'
+parent = 'parent:      {rev}:{node|formatnode}\n'
+manifest = 'manifest:    {rev}:{node}\n'
+branch = 'branch:      {branch}\n'
+tag = 'tag:         {tag}\n'
+bookmark = 'bookmark:    {bookmark}\n'
+extra = 'extra:       {key}={value|stringescape}\n'
--- a/mercurial/ui.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/ui.py	Tue May 14 18:52:52 2013 -0500
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import _
-import errno, getpass, os, re, socket, sys, tempfile, traceback
+import errno, getpass, os, socket, sys, tempfile, traceback
 import config, scmutil, util, error, formatter
 
 class ui(object):
@@ -284,22 +284,16 @@
         ConfigError: foo.invalid is not a byte quantity ('somevalue')
         """
 
-        orig = string = self.config(section, name)
-        if orig is None:
+        value = self.config(section, name)
+        if value is None:
             if not isinstance(default, str):
                 return default
-            orig = string = default
-        multiple = 1
-        m = re.match(r'([^kmbg]+?)\s*([kmg]?)b?$', string, re.I)
-        if m:
-            string, key = m.groups()
-            key = key.lower()
-            multiple = dict(k=1024, m=1048576, g=1073741824).get(key, 1)
+            value = default
         try:
-            return int(float(string) * multiple)
-        except ValueError:
+            return util.sizetoint(value)
+        except error.ParseError:
             raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
-                                    % (section, name, orig))
+                                    % (section, name, value))
 
     def configlist(self, section, name, default=None, untrusted=False):
         """parse a configuration element as a list of comma/space separated
--- a/mercurial/util.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/util.py	Tue May 14 18:52:52 2013 -0500
@@ -1924,3 +1924,25 @@
                              (' ' * _timenesting[0], func.__name__,
                               timecount(elapsed)))
     return wrapper
+
+_sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
+              ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
+
+def sizetoint(s):
+    '''Convert a space specifier to a byte count.
+
+    >>> sizetoint('30')
+    30
+    >>> sizetoint('2.2kb')
+    2252
+    >>> sizetoint('6M')
+    6291456
+    '''
+    t = s.strip().lower()
+    try:
+        for k, u in _sizeunits:
+            if t.endswith(k):
+                return int(float(t[:-len(k)]) * u)
+        return int(t)
+    except ValueError:
+        raise error.ParseError(_("couldn't parse size: %s") % s)
--- a/mercurial/wireproto.py	Tue May 14 18:43:53 2013 -0500
+++ b/mercurial/wireproto.py	Tue May 14 18:52:52 2013 -0500
@@ -523,6 +523,10 @@
 def _allowstream(ui):
     return ui.configbool('server', 'uncompressed', True, untrusted=True)
 
+def _walkstreamfiles(repo):
+    # this is it's own function so extensions can override it
+    return repo.store.walk()
+
 def stream(repo, proto):
     '''If the server supports streaming clone, it advertises the "stream"
     capability with a value representing the version and flags of the repo
@@ -544,7 +548,7 @@
         lock = repo.lock()
         try:
             repo.ui.debug('scanning\n')
-            for name, ename, size in repo.store.walk():
+            for name, ename, size in _walkstreamfiles(repo):
                 if size:
                     entries.append((name, size))
                     total_bytes += size
--- a/tests/run-tests.py	Tue May 14 18:43:53 2013 -0500
+++ b/tests/run-tests.py	Tue May 14 18:52:52 2013 -0500
@@ -1120,6 +1120,8 @@
         childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
         childtmp = os.path.join(HGTMP, 'child%d' % j)
         childopts += ['--tmpdir', childtmp]
+        if options.keep_tmpdir:
+            childopts.append('--keep-tmpdir')
         cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
         vlog(' '.join(cmdline))
         proc = subprocess.Popen(cmdline, executable=cmdline[0])
@@ -1288,7 +1290,8 @@
     global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
     TESTDIR = os.environ["TESTDIR"] = os.getcwd()
     if options.tmpdir:
-        options.keep_tmpdir = True
+        if not options.child:
+            options.keep_tmpdir = True
         tmpdir = options.tmpdir
         if os.path.exists(tmpdir):
             # Meaning of tmpdir has changed since 1.3: we used to create
--- a/tests/test-bookmarks-current.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-bookmarks-current.t	Tue May 14 18:52:52 2013 -0500
@@ -43,16 +43,19 @@
   $ hg bookmarks
    * Z                         -1:000000000000
 
-new bookmark Y
+new bookmarks X and Y, first one made active
 
-  $ hg bookmark Y
+  $ hg bookmark Y X
 
 list bookmarks
 
   $ hg bookmark
+     X                         -1:000000000000
    * Y                         -1:000000000000
      Z                         -1:000000000000
 
+  $ hg bookmark -d X
+
 commit
 
   $ echo 'b' > b
--- a/tests/test-bookmarks.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-bookmarks.t	Tue May 14 18:52:52 2013 -0500
@@ -168,11 +168,14 @@
   $ hg bookmark -d REVSET
   $ hg bookmark -d TIP
 
-rename without new name
+rename without new name or multiple names
 
   $ hg bookmark -m Y
   abort: new bookmark name required
   [255]
+  $ hg bookmark -m Y Y2 Y3
+  abort: only one new bookmark name allowed
+  [255]
 
 delete without name
 
@@ -417,8 +420,9 @@
      a@                        2:db815d6d32e6
      x  y                      2:db815d6d32e6
 
-  $ hg bookmark -d @
-  $ hg bookmark -d a@
+delete multiple bookmarks at once
+
+  $ hg bookmark -d @ a@
 
 test clone with a bookmark named "default" (issue3677)
 
--- a/tests/test-command-template.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-command-template.t	Tue May 14 18:52:52 2013 -0500
@@ -458,7 +458,8 @@
 Error if no style:
 
   $ hg log --style notexist
-  abort: style not found: notexist
+  abort: style 'notexist' not found
+  (available styles: bisect, changelog, compact, default, phases, xml)
   [255]
 
 Error if style missing key:
--- a/tests/test-commandserver.py	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-commandserver.py	Tue May 14 18:52:52 2013 -0500
@@ -25,7 +25,11 @@
     else:
         return channel, server.stdout.read(length)
 
-def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None):
+def sep(text):
+    return text.replace('\\', '/')
+
+def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None,
+               outfilter=lambda x: x):
     print ' runcommand', ' '.join(args)
     sys.stdout.flush()
     server.stdin.write('runcommand\n')
@@ -37,7 +41,7 @@
     while True:
         ch, data = readchannel(server)
         if ch == 'o':
-            output.write(data)
+            output.write(outfilter(data))
             output.flush()
         elif ch == 'e':
             error.write(data)
@@ -249,7 +253,8 @@
 
     # make it public; draft marker moves to 4:7966c8e3734d
     runcommand(server, ['phase', '-p', '.'])
-    runcommand(server, ['phase', '.'])  # load _phasecache.phaseroots
+    # load _phasecache.phaseroots
+    runcommand(server, ['phase', '.'], outfilter=sep)
 
     # strip 1::4 outside server
     os.system('hg -q --config extensions.mq= strip 1')
--- a/tests/test-convert-git.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-convert-git.t	Tue May 14 18:52:52 2013 -0500
@@ -13,6 +13,10 @@
   $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
   $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
   $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
+  $ INVALIDID1=afd12345af
+  $ INVALIDID2=28173x36ddd1e67bf7098d541130558ef5534a86
+  $ VALIDID1=39b3d83f9a69a9ba4ebb111461071a0af0027357
+  $ VALIDID2=8dd6476bd09d9c7776355dc454dafe38efaec5da
   $ count=10
   $ commit()
   > {
@@ -298,6 +302,36 @@
   $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
   $ cd ..
 
+test invalid splicemap1
+
+  $ cat > splicemap <<EOF
+  > $VALIDID1
+  > EOF
+  $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
+  initializing destination git-repo2-splicemap1-hg repository
+  abort: syntax error in splicemap(1): child parent1[,parent2] expected
+  [255]
+
+test invalid splicemap2
+
+  $ cat > splicemap <<EOF
+  > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
+  > EOF
+  $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
+  initializing destination git-repo2-splicemap2-hg repository
+  abort: syntax error in splicemap(1): child parent1[,parent2] expected
+  [255]
+
+test invalid splicemap3
+
+  $ cat > splicemap <<EOF
+  > $INVALIDID1 $INVALIDID2
+  > EOF
+  $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
+  initializing destination git-repo2-splicemap3-hg repository
+  abort: splicemap entry afd12345af is not a valid revision identifier
+  [255]
+
 convert sub modules
   $ hg convert git-repo6 git-repo6-hg
   initializing destination git-repo6-hg repository
--- a/tests/test-convert-splicemap.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-convert-splicemap.t	Tue May 14 18:52:52 2013 -0500
@@ -37,6 +37,8 @@
   $ hg ci -Am addaandd
   adding a
   adding d
+  $ INVALIDID1=afd12345af
+  $ INVALIDID2=28173x36ddd1e67bf7098d541130558ef5534a86
   $ CHILDID1=`hg id --debug -i`
   $ echo d >> d
   $ hg ci -Am changed
@@ -53,7 +55,7 @@
   o  0:527cdedf31fb "addaandd" files: a d
   
 
-test invalid splicemap
+test invalid splicemap1
 
   $ cat > splicemap <<EOF
   > $CHILDID2
@@ -62,6 +64,24 @@
   abort: syntax error in splicemap(1): child parent1[,parent2] expected
   [255]
 
+test invalid splicemap2
+
+  $ cat > splicemap <<EOF
+  > $CHILDID2 $PARENTID1, $PARENTID2, $PARENTID2
+  > EOF
+  $ hg convert --splicemap splicemap repo2 repo1
+  abort: syntax error in splicemap(1): child parent1[,parent2] expected
+  [255]
+
+test invalid splicemap3
+
+  $ cat > splicemap <<EOF
+  > $INVALIDID1 $INVALIDID2
+  > EOF
+  $ hg convert --splicemap splicemap repo2 repo1
+  abort: splicemap entry afd12345af is not a valid revision identifier
+  [255]
+
 splice repo2 on repo1
 
   $ cat > splicemap <<EOF
--- a/tests/test-convert-svn-source.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-convert-svn-source.t	Tue May 14 18:52:52 2013 -0500
@@ -16,6 +16,8 @@
 #else
   $ SVNREPOURL=file://`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
+  $ INVALIDREVISIONID=svn:x2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk@1
+  $ VALIDREVISIONID=svn:a2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk/mytrunk@1
 
 Now test that it works with trunk/tags layout, but no branches yet.
 
@@ -168,6 +170,15 @@
   |
   o  0 second letter files: letter2.txt
   
+test invalid splicemap1
+
+  $ cat > splicemap <<EOF
+  > $INVALIDREVISIONID $VALIDREVISIONID
+  > EOF
+  $ hg convert --splicemap splicemap "$SVNREPOURL/proj%20B/mytrunk" smap
+  initializing destination smap repository
+  abort: splicemap entry svn:x2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk@1 is not a valid revision identifier
+  [255]
 
 Test stop revision
   $ hg convert --rev 1 "$SVNREPOURL/proj%20B/mytrunk" stoprev
--- a/tests/test-log.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-log.t	Tue May 14 18:52:52 2013 -0500
@@ -84,6 +84,25 @@
   abort: cannot follow file not in parent revision: "dir"
   [255]
 
+-f, a wrong style
+
+  $ hg log -f -l1 --style something
+  abort: style 'something' not found
+  (available styles: bisect, changelog, compact, default, phases, xml)
+  [255]
+
+-f, phases style
+
+
+  $ hg log -f -l1 --style phases
+  changeset:   4:7e4639b4691b
+  tag:         tip
+  phase:       draft
+  user:        test
+  date:        Thu Jan 01 00:00:05 1970 +0000
+  summary:     e
+  
+
 -f, but no args
 
   $ hg log -f
--- a/tests/test-nested-repo.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-nested-repo.t	Tue May 14 18:52:52 2013 -0500
@@ -8,6 +8,9 @@
   $ hg add b
   $ hg st
 
+  $ echo y > b/y
+  $ hg st
+
 Should fail:
 
   $ hg st b/x
--- a/tests/test-rebase-rename.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-rebase-rename.t	Tue May 14 18:52:52 2013 -0500
@@ -22,7 +22,7 @@
   adding d/b
 
   $ hg mv d d-renamed
-  moving d/b to d-renamed/b
+  moving d/b to d-renamed/b (glob)
   $ hg ci -m 'rename B'
 
   $ hg up -q -C 1
--- a/tests/test-rename-dir-merge.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-rename-dir-merge.t	Tue May 14 18:52:52 2013 -0500
@@ -51,7 +51,7 @@
   getting b/b
   updating: b/b 4/5 files (80.00%)
   updating: a/c 5/5 files (100.00%)
-  moving a/c to b/c
+  moving a/c to b/c (glob)
   3 files updated, 0 files merged, 2 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
--- a/tests/test-subrepo.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-subrepo.t	Tue May 14 18:52:52 2013 -0500
@@ -391,7 +391,7 @@
   $ hg -R s update '.^'
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg push
-  pushing to $TESTTMP/t
+  pushing to $TESTTMP/t (glob)
   no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
   no changes made to subrepo s since last push to $TESTTMP/t/s
   no changes made to subrepo t since last push to $TESTTMP/t/t
@@ -400,7 +400,7 @@
   [1]
   $ echo foo >> s/a
   $ hg push
-  pushing to $TESTTMP/t
+  pushing to $TESTTMP/t (glob)
   no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
   no changes made to subrepo s since last push to $TESTTMP/t/s
   no changes made to subrepo t since last push to $TESTTMP/t/t
@@ -415,7 +415,7 @@
   $ echo foo >> s/ss/a
   $ hg -R s/ss commit -m 'test dirty store detection'
   $ hg push
-  pushing to $TESTTMP/t
+  pushing to $TESTTMP/t (glob)
   pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
   searching for changes
   adding changesets
@@ -431,7 +431,7 @@
 a subrepo store may be clean versus one repo but not versus another
 
   $ hg push
-  pushing to $TESTTMP/t
+  pushing to $TESTTMP/t (glob)
   no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
   no changes made to subrepo s since last push to $TESTTMP/t/s
   no changes made to subrepo t since last push to $TESTTMP/t/t
@@ -798,7 +798,7 @@
 Try to push from the other side
 
   $ hg -R issue1852a push `pwd`/issue1852c
-  pushing to $TESTTMP/issue1852c
+  pushing to $TESTTMP/issue1852c (glob)
   pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
   searching for changes
   no changes found
--- a/tests/test-symlinks.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-symlinks.t	Tue May 14 18:52:52 2013 -0500
@@ -160,6 +160,15 @@
   adding bar/a
   adding foo
   removing foo/a
+
+commit and update back
+
+  $ hg ci -mb
+  $ hg up '.^'
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg up tip
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
   $ cd ..
 
 == root of repository is symlinked ==
--- a/tests/test-tag.t	Tue May 14 18:43:53 2013 -0500
+++ b/tests/test-tag.t	Tue May 14 18:52:52 2013 -0500
@@ -316,7 +316,7 @@
   adding test
   $ hg init repo-tag-target
   $ hg -R repo-tag --config hooks.commit="\"hg\" push \"`pwd`/repo-tag-target\"" tag tag
-  pushing to $TESTTMP/repo-tag-target
+  pushing to $TESTTMP/repo-tag-target (glob)
   searching for changes
   adding changesets
   adding manifests