changeset 13129:873c032c81b5

Merge with stable
author Steve Borho <steve@borho.org>
date Mon, 13 Dec 2010 12:19:26 -0600
parents ece1c069ddd6 (diff) dbc546811dd6 (current diff)
children f139f34ba330
files mercurial/util.py
diffstat 69 files changed, 2782 insertions(+), 1379 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Mon Dec 13 11:51:01 2010 -0600
+++ b/Makefile	Mon Dec 13 12:19:26 2010 -0600
@@ -45,7 +45,7 @@
 clean:
 	-$(PYTHON) setup.py clean --all # ignore errors from this command
 	find . \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
-	rm -f MANIFEST mercurial/__version__.py tests/*.err
+	rm -f MANIFEST tests/*.err
 	rm -rf build mercurial/locale
 	$(MAKE) -C doc clean
 
--- a/contrib/check-code.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/contrib/check-code.py	Mon Dec 13 12:19:26 2010 -0600
@@ -8,6 +8,7 @@
 # GNU General Public License version 2 or any later version.
 
 import re, glob, os, sys
+import keyword
 import optparse
 
 def repquote(m):
@@ -64,6 +65,7 @@
     ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
     (r'^source\b', "don't use 'source', use '.'"),
     (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
+    (r'ls\s+[^-]+\s+-', "options to 'ls' must come before filenames"),
 ]
 
 testfilters = [
@@ -117,8 +119,8 @@
     (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
      "linebreak after :"),
     (r'class\s[^(]:', "old-style class, use class foo(object)"),
-    (r'^\s+del\(', "del isn't a function"),
-    (r'^\s+except\(', "except isn't a function"),
+    (r'\b(%s)\(' % '|'.join(keyword.kwlist),
+     "Python keyword is not a function"),
     (r',]', "unneeded trailing ',' in list"),
 #    (r'class\s[A-Z][^\(]*\((?!Exception)',
 #     "don't capitalize non-exception classes"),
@@ -132,6 +134,8 @@
     (r'(?<!def)\s+(callable)\(',
      "callable not available in Python 3, use hasattr(f, '__call__')"),
     (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
+    (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
+     "gratuitous whitespace after Python keyword"),
     (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
 #    (r'\s\s=', "gratuitous whitespace before ="),
     (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
@@ -145,6 +149,9 @@
     (r'raise Exception', "don't raise generic exceptions"),
     (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
      "warning: unwrapped ui message"),
+    (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
+    (r' [=!]=\s+(True|False|None)',
+     "comparison with singleton, use 'is' or 'is not' instead"),
 ]
 
 pyfilters = [
--- a/doc/Makefile	Mon Dec 13 11:51:01 2010 -0600
+++ b/doc/Makefile	Mon Dec 13 12:19:26 2010 -0600
@@ -1,11 +1,13 @@
 SOURCES=$(wildcard *.[0-9].txt)
 MAN=$(SOURCES:%.txt=%)
 HTML=$(SOURCES:%.txt=%.html)
-GENDOC=gendoc.py ../mercurial/commands.py ../mercurial/help.py ../mercurial/help/*.txt
+GENDOC=gendoc.py ../mercurial/commands.py ../mercurial/help.py \
+	../mercurial/help/*.txt ../hgext/*.py ../hgext/*/__init__.py
 PREFIX=/usr/local
 MANDIR=$(PREFIX)/share/man
 INSTALL=install -c -m 644
 PYTHON=python
+RSTARGS=
 
 export LANGUAGE=C
 export LC_ALL=C
@@ -24,11 +26,11 @@
 	mv $@.tmp $@
 
 %: %.txt common.txt
-	$(PYTHON) runrst hgmanpage --halt warning \
+	$(PYTHON) runrst hgmanpage $(RSTARGS) --halt warning \
 	  --strip-elements-with-class htmlonly $*.txt $*
 
 %.html: %.txt common.txt
-	$(PYTHON) runrst html --halt warning \
+	$(PYTHON) runrst html $(RSTARGS) --halt warning \
 	  --link-stylesheet --stylesheet-path style.css $*.txt $*.html
 
 MANIFEST: man html
--- a/doc/gendoc.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/doc/gendoc.py	Mon Dec 13 12:19:26 2010 -0600
@@ -40,7 +40,7 @@
         if longopt:
             allopts.append("--%s" % longopt)
         desc += default and _(" (default: %s)") % default or ""
-        yield(", ".join(allopts), desc)
+        yield (", ".join(allopts), desc)
 
 def get_cmd(cmd, cmdtable):
     d = {}
--- a/hgext/bookmarks.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/hgext/bookmarks.py	Mon Dec 13 12:19:26 2010 -0600
@@ -31,7 +31,7 @@
 from mercurial.i18n import _
 from mercurial.node import nullid, nullrev, bin, hex, short
 from mercurial import util, commands, repair, extensions, pushkey, hg, url
-from mercurial import revset
+from mercurial import revset, encoding
 import os
 
 def write(repo):
@@ -58,7 +58,7 @@
     try:
         file = repo.opener('bookmarks', 'w', atomictemp=True)
         for refspec, node in refs.iteritems():
-            file.write("%s %s\n" % (hex(node), refspec))
+            file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
         file.rename()
 
         # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
@@ -143,7 +143,7 @@
         write(repo)
         return
 
-    if mark != None:
+    if mark is not None:
         if "\n" in mark:
             raise util.Abort(_("bookmark name cannot contain newlines"))
         mark = mark.strip()
@@ -239,6 +239,7 @@
                 bookmarks = {}
                 for line in self.opener('bookmarks'):
                     sha, refspec = line.strip().split(' ', 1)
+                    refspec = encoding.tolocal(refspec)
                     bookmarks[refspec] = self.changelog.lookup(sha)
             except:
                 pass
@@ -339,7 +340,7 @@
             rb = remote.listkeys('bookmarks')
             for k in rb.keys():
                 if k in self._bookmarks:
-                    nr, nl = rb[k], self._bookmarks[k]
+                    nr, nl = rb[k], hex(self._bookmarks[k])
                     if nr in self:
                         cr = self[nr]
                         cl = self[nl]
@@ -354,14 +355,12 @@
             return result
 
         def addchangegroup(self, *args, **kwargs):
-            parents = self.dirstate.parents()
-
             result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
             if result > 1:
                 # We have more heads than before
                 return result
             node = self.changelog.tip()
-
+            parents = self.dirstate.parents()
             self._bookmarksupdate(parents, node)
             return result
 
--- a/hgext/convert/__init__.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/hgext/convert/__init__.py	Mon Dec 13 12:19:26 2010 -0600
@@ -59,10 +59,10 @@
     --sourcesort  try to preserve source revisions order, only
                   supported by Mercurial sources.
 
-    If <REVMAP> isn't given, it will be put in a default location
-    (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
-    that maps each source commit ID to the destination ID for that
-    revision, like so::
+    If ``REVMAP`` isn't given, it will be put in a default location
+    (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
+    text file that maps each source commit ID to the destination ID
+    for that revision, like so::
 
       <source ID> <destination ID>
 
@@ -138,15 +138,19 @@
     Mercurial Source
     ''''''''''''''''
 
-    --config convert.hg.ignoreerrors=False    (boolean)
-        ignore integrity errors when reading. Use it to fix Mercurial
-        repositories with missing revlogs, by converting from and to
-        Mercurial.
-    --config convert.hg.saverev=False         (boolean)
-        store original revision ID in changeset (forces target IDs to
-        change)
-    --config convert.hg.startrev=0            (hg revision identifier)
-        convert start revision and its descendants
+    The Mercurial source recognizes the following configuration
+    options, which you can set on the command line with ``--config``:
+
+    :convert.hg.ignoreerrors: ignore integrity errors when reading.
+        Use it to fix Mercurial repositories with missing revlogs, by
+        converting from and to Mercurial. Default is False.
+
+    :convert.hg.saverev: store original. revision ID in changeset
+        (forces target IDs to change). It takes and boolean argument
+        and defaults to False.
+
+    :convert.hg.startrev: convert start revision and its descendants.
+        It takes a hg revision identifier and defaults to 0.
 
     CVS Source
     ''''''''''
@@ -154,42 +158,46 @@
     CVS source will use a sandbox (i.e. a checked-out copy) from CVS
     to indicate the starting point of what will be converted. Direct
     access to the repository files is not needed, unless of course the
-    repository is :local:. The conversion uses the top level directory
-    in the sandbox to find the CVS repository, and then uses CVS rlog
-    commands to find files to convert. This means that unless a
-    filemap is given, all files under the starting directory will be
+    repository is ``:local:``. The conversion uses the top level
+    directory in the sandbox to find the CVS repository, and then uses
+    CVS rlog commands to find files to convert. This means that unless
+    a filemap is given, all files under the starting directory will be
     converted, and that any directory reorganization in the CVS
     sandbox is ignored.
 
-    The options shown are the defaults.
+    The following options can be used with ``--config``:
+
+    :convert.cvsps.cache: Set to False to disable remote log caching,
+        for testing and debugging purposes. Default is True.
+
+    :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
+        allowed between commits with identical user and log message in
+        a single changeset. When very large files were checked in as
+        part of a changeset then the default may not be long enough.
+        The default is 60.
 
-    --config convert.cvsps.cache=True         (boolean)
-        Set to False to disable remote log caching, for testing and
-        debugging purposes.
-    --config convert.cvsps.fuzz=60            (integer)
-        Specify the maximum time (in seconds) that is allowed between
-        commits with identical user and log message in a single
-        changeset. When very large files were checked in as part of a
-        changeset then the default may not be long enough.
-    --config convert.cvsps.mergeto='{{mergetobranch ([-\\w]+)}}'
-        Specify a regular expression to which commit log messages are
-        matched. If a match occurs, then the conversion process will
-        insert a dummy revision merging the branch on which this log
-        message occurs to the branch indicated in the regex.
-    --config convert.cvsps.mergefrom='{{mergefrombranch ([-\\w]+)}}'
-        Specify a regular expression to which commit log messages are
-        matched. If a match occurs, then the conversion process will
-        add the most recent revision on the branch indicated in the
-        regex as the second parent of the changeset.
-    --config hook.cvslog
-        Specify a Python function to be called at the end of gathering
-        the CVS log. The function is passed a list with the log entries,
-        and can modify the entries in-place, or add or delete them.
-    --config hook.cvschangesets
-        Specify a Python function to be called after the changesets
-        are calculated from the the CVS log. The function is passed
-        a list with the changeset entries, and can modify the changesets
-        in-place, or add or delete them.
+    :convert.cvsps.mergeto: Specify a regular expression to which
+        commit log messages are matched. If a match occurs, then the
+        conversion process will insert a dummy revision merging the
+        branch on which this log message occurs to the branch
+        indicated in the regex. Default is ``{{mergetobranch
+        ([-\\w]+)}}``
+
+    :convert.cvsps.mergefrom: Specify a regular expression to which
+        commit log messages are matched. If a match occurs, then the
+        conversion process will add the most recent revision on the
+        branch indicated in the regex as the second parent of the
+        changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
+
+    :hook.cvslog: Specify a Python function to be called at the end of
+        gathering the CVS log. The function is passed a list with the
+        log entries, and can modify the entries in-place, or add or
+        delete them.
+
+    :hook.cvschangesets: Specify a Python function to be called after
+        the changesets are calculated from the the CVS log. The
+        function is passed a list with the changeset entries, and can
+        modify the changesets in-place, or add or delete them.
 
     An additional "debugcvsps" Mercurial command allows the builtin
     changeset merging code to be run without doing a conversion. Its
@@ -200,29 +208,33 @@
     '''''''''''''''''
 
     Subversion source detects classical trunk/branches/tags layouts.
-    By default, the supplied "svn://repo/path/" source URL is
-    converted as a single branch. If "svn://repo/path/trunk" exists it
-    replaces the default branch. If "svn://repo/path/branches" exists,
-    its subdirectories are listed as possible branches. If
-    "svn://repo/path/tags" exists, it is looked for tags referencing
-    converted branches. Default "trunk", "branches" and "tags" values
-    can be overridden with following options. Set them to paths
+    By default, the supplied ``svn://repo/path/`` source URL is
+    converted as a single branch. If ``svn://repo/path/trunk`` exists
+    it replaces the default branch. If ``svn://repo/path/branches``
+    exists, its subdirectories are listed as possible branches. If
+    ``svn://repo/path/tags`` exists, it is looked for tags referencing
+    converted branches. Default ``trunk``, ``branches`` and ``tags``
+    values can be overridden with following options. Set them to paths
     relative to the source URL, or leave them blank to disable auto
     detection.
 
-    --config convert.svn.branches=branches    (directory name)
-        specify the directory containing branches
-    --config convert.svn.tags=tags            (directory name)
-        specify the directory containing tags
-    --config convert.svn.trunk=trunk          (directory name)
-        specify the name of the trunk branch
+    The following options can be set with ``--config``:
+
+    :convert.svn.branches: specify the directory containing branches.
+        The defaults is ``branches``.
+
+    :convert.svn.tags: specify the directory containing tags. The
+        default is ``tags``.
+
+    :convert.svn.trunk: specify the name of the trunk branch The
+        defauls is ``trunk``.
 
     Source history can be retrieved starting at a specific revision,
     instead of being integrally converted. Only single branch
     conversions are supported.
 
-    --config convert.svn.startrev=0           (svn revision number)
-        specify start Subversion revision.
+    :convert.svn.startrev: specify start Subversion revision number.
+        The default is 0.
 
     Perforce Source
     '''''''''''''''
@@ -232,24 +244,27 @@
     source to a flat Mercurial repository, ignoring labels, branches
     and integrations. Note that when a depot path is given you then
     usually should specify a target directory, because otherwise the
-    target may be named ...-hg.
+    target may be named ``...-hg``.
 
     It is possible to limit the amount of source history to be
-    converted by specifying an initial Perforce revision.
+    converted by specifying an initial Perforce revision:
 
-    --config convert.p4.startrev=0            (perforce changelist number)
-        specify initial Perforce revision.
+    :convert.p4.startrev: specify initial Perforce revision, a
+        Perforce changelist number).
 
     Mercurial Destination
     '''''''''''''''''''''
 
-    --config convert.hg.clonebranches=False   (boolean)
-        dispatch source branches in separate clones.
-    --config convert.hg.tagsbranch=default    (branch name)
-        tag revisions branch name
-    --config convert.hg.usebranchnames=True   (boolean)
-        preserve branch names
+    The following options are supported:
+
+    :convert.hg.clonebranches: dispatch source branches in separate
+        clones. The default is False.
 
+    :convert.hg.tagsbranch: branch name for tag revisions, defaults to
+        ``default``.
+
+    :convert.hg.usebranchnames: preserve branch names. The default is
+        True
     """
     return convcmd.convert(ui, src, dest, revmapfile, **opts)
 
--- a/hgext/eol.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/hgext/eol.py	Mon Dec 13 12:19:26 2010 -0600
@@ -67,6 +67,11 @@
   Such files are normally not touched under the assumption that they
   have mixed EOLs on purpose.
 
+The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
+like the deprecated win32text extension does. This means that you can
+disable win32text and enable eol and your filters will still work. You
+only need to these filters until you have prepared a ``.hgeol`` file.
+
 The ``win32text.forbid*`` hooks provided by the win32text extension
 have been unified into a single hook named ``eol.hook``. The hook will
 lookup the expected line endings from the ``.hgeol`` file, which means
@@ -115,6 +120,9 @@
     'to-lf': tolf,
     'to-crlf': tocrlf,
     'is-binary': isbinary,
+    # The following provide backwards compatibility with win32text
+    'cleverencode:': tolf,
+    'cleverdecode:': tocrlf
 }
 
 
--- a/hgext/hgk.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/hgext/hgk.py	Mon Dec 13 12:19:26 2010 -0600
@@ -181,14 +181,14 @@
                 if i + x >= count:
                     l[chunk - x:] = [0] * (chunk - x)
                     break
-                if full != None:
+                if full is not None:
                     l[x] = repo[i + x]
                     l[x].changeset() # force reading
                 else:
                     l[x] = 1
             for x in xrange(chunk - 1, -1, -1):
                 if l[x] != 0:
-                    yield (i + x, full != None and l[x] or None)
+                    yield (i + x, full is not None and l[x] or None)
             if i == 0:
                 break
 
--- a/hgext/keyword.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/hgext/keyword.py	Mon Dec 13 12:19:26 2010 -0600
@@ -101,6 +101,14 @@
 # names of extensions using dorecord
 recordextensions = 'record'
 
+colortable = {
+    'kwfiles.enabled': 'green bold',
+    'kwfiles.deleted': 'cyan bold underline',
+    'kwfiles.enabledunknown': 'green',
+    'kwfiles.ignored': 'bold',
+    'kwfiles.ignoredunknown': 'none'
+}
+
 # date like in cvs' $Date
 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
 # date like in svn's $Date
@@ -111,7 +119,6 @@
 # make keyword tools accessible
 kwtools = {'templater': None, 'hgcmd': ''}
 
-
 def _defaultkwmaps(ui):
     '''Returns default keywordmaps according to keywordset configuration.'''
     templates = {
@@ -170,14 +177,25 @@
                                   for k, v in kwmaps)
         else:
             self.templates = _defaultkwmaps(self.ui)
-        escaped = '|'.join(map(re.escape, self.templates.keys()))
-        self.re_kw = re.compile(r'\$(%s)\$' % escaped)
-        self.re_kwexp = re.compile(r'\$(%s): [^$\n\r]*? \$' % escaped)
-
         templatefilters.filters.update({'utcdate': utcdate,
                                         'svnisodate': svnisodate,
                                         'svnutcdate': svnutcdate})
 
+    @util.propertycache
+    def escape(self):
+        '''Returns bar-separated and escaped keywords.'''
+        return '|'.join(map(re.escape, self.templates.keys()))
+
+    @util.propertycache
+    def rekw(self):
+        '''Returns regex for unexpanded keywords.'''
+        return re.compile(r'\$(%s)\$' % self.escape)
+
+    @util.propertycache
+    def rekwexp(self):
+        '''Returns regex for expanded keywords.'''
+        return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
+
     def substitute(self, data, path, ctx, subfunc):
         '''Replaces keywords in data with expanded template.'''
         def kwsub(mobj):
@@ -191,11 +209,15 @@
             return '$%s: %s $' % (kw, ekw)
         return subfunc(kwsub, data)
 
+    def linkctx(self, path, fileid):
+        '''Similar to filelog.linkrev, but returns a changectx.'''
+        return self.repo.filectx(path, fileid=fileid).changectx()
+
     def expand(self, path, node, data):
         '''Returns data with keywords expanded.'''
         if not self.restrict and self.match(path) and not util.binary(data):
-            ctx = self.repo.filectx(path, fileid=node).changectx()
-            return self.substitute(data, path, ctx, self.re_kw.sub)
+            ctx = self.linkctx(path, node)
+            return self.substitute(data, path, ctx, self.rekw.sub)
         return data
 
     def iskwfile(self, cand, ctx):
@@ -212,8 +234,8 @@
         kwcmd = self.restrict and lookup # kwexpand/kwshrink
         if self.restrict or expand and lookup:
             mf = ctx.manifest()
-        fctx = ctx
-        subn = (self.restrict or rekw) and self.re_kw.subn or self.re_kwexp.subn
+        lctx = ctx
+        re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp
         msg = (expand and _('overwriting %s expanding keywords\n')
                or _('overwriting %s shrinking keywords\n'))
         for f in candidates:
@@ -225,12 +247,12 @@
                 continue
             if expand:
                 if lookup:
-                    fctx = self.repo.filectx(f, fileid=mf[f]).changectx()
-                data, found = self.substitute(data, f, fctx, subn)
+                    lctx = self.linkctx(f, mf[f])
+                data, found = self.substitute(data, f, lctx, re_kw.subn)
             elif self.restrict:
-                found = self.re_kw.search(data)
+                found = re_kw.search(data)
             else:
-                data, found = _shrinktext(data, subn)
+                data, found = _shrinktext(data, re_kw.subn)
             if found:
                 self.ui.note(msg % f)
                 self.repo.wwrite(f, data, ctx.flags(f))
@@ -242,7 +264,7 @@
     def shrink(self, fname, text):
         '''Returns text with all keyword substitutions removed.'''
         if self.match(fname) and not util.binary(text):
-            return _shrinktext(text, self.re_kwexp.sub)
+            return _shrinktext(text, self.rekwexp.sub)
         return text
 
     def shrinklines(self, fname, lines):
@@ -250,7 +272,7 @@
         if self.match(fname):
             text = ''.join(lines)
             if not util.binary(text):
-                return _shrinktext(text, self.re_kwexp.sub).splitlines(True)
+                return _shrinktext(text, self.rekwexp.sub).splitlines(True)
         return lines
 
     def wread(self, fname, data):
@@ -424,18 +446,21 @@
         files = sorted(modified + added + clean)
     wctx = repo[None]
     kwfiles = kwt.iskwfile(files, wctx)
+    kwdeleted = kwt.iskwfile(deleted, wctx)
     kwunknown = kwt.iskwfile(unknown, wctx)
     if not opts.get('ignore') or opts.get('all'):
-        showfiles = kwfiles, kwunknown
+        showfiles = kwfiles, kwdeleted, kwunknown
     else:
-        showfiles = [], []
+        showfiles = [], [], []
     if opts.get('all') or opts.get('ignore'):
         showfiles += ([f for f in files if f not in kwfiles],
                       [f for f in unknown if f not in kwunknown])
-    for char, filenames in zip('KkIi', showfiles):
+    kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
+    kwstates = zip('K!kIi', showfiles, kwlabels)
+    for char, filenames, kwstate in kwstates:
         fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
         for f in filenames:
-            ui.write(fmt % repo.pathto(f, cwd))
+            ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate)
 
 def shrink(ui, repo, *pats, **opts):
     '''revert expanded keywords in the working directory
--- a/hgext/mq.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/hgext/mq.py	Mon Dec 13 12:19:26 2010 -0600
@@ -1006,7 +1006,7 @@
         raise util.Abort(_("patch %s not in series") % patch)
 
     def push(self, repo, patch=None, force=False, list=False,
-             mergeq=None, all=False, move=False):
+             mergeq=None, all=False, move=False, exact=False):
         diffopts = self.diffopts()
         wlock = repo.wlock()
         try:
@@ -1015,7 +1015,7 @@
                 heads += ls
             if not heads:
                 heads = [nullid]
-            if repo.dirstate.parents()[0] not in heads:
+            if repo.dirstate.parents()[0] not in heads and not exact:
                 self.ui.status(_("(working directory not at a head)\n"))
 
             if not self.series:
@@ -1062,9 +1062,21 @@
             if not force:
                 self.check_localchanges(repo)
 
+            if exact:
+                if move:
+                    raise util.Abort(_("cannot use --exact and --move together"))
+                if self.applied:
+                    raise util.Abort(_("cannot push --exact with applied patches"))
+                root = self.series[start]
+                target = patchheader(self.join(root), self.plainmode).parent
+                if not target:
+                    raise util.Abort(_("%s does not have a parent recorded" % root))
+                if not repo[target] == repo['.']:
+                    hg.update(repo, target)
+
             if move:
                 if not patch:
-                    raise  util.Abort(_("please specify the patch to move"))
+                    raise util.Abort(_("please specify the patch to move"))
                 for i, rpn in enumerate(self.full_series[start:]):
                     # strip markers for patch guards
                     if self.guard_re.split(rpn, 1)[0] == patch:
@@ -1270,10 +1282,10 @@
             # and then commit.
             #
             # this should really read:
-            #   mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
+            #   mm, dd, aa = repo.status(top, patchparent)[:3]
             # but we do it backwards to take advantage of manifest/chlog
             # caching against the next repo.status call
-            mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
+            mm, aa, dd = repo.status(patchparent, top)[:3]
             changes = repo.changelog.read(top)
             man = repo.manifest.read(changes[0])
             aaa = aa[:]
@@ -1289,6 +1301,9 @@
             else:
                 match = cmdutil.matchall(repo)
             m, a, r, d = repo.status(match=match)[:4]
+            mm = set(mm)
+            aa = set(aa)
+            dd = set(dd)
 
             # we might end up with files that were added between
             # qtip and the dirstate parent, but then changed in the
@@ -1299,7 +1314,7 @@
                     self.ui.warn(_('warning: not refreshing %s\n') % x)
                     continue
                 if x not in aa:
-                    mm.append(x)
+                    mm.add(x)
             # we might end up with files added by the local dirstate that
             # were deleted by the patch.  In this case, they should only
             # show up in the changed section.
@@ -1308,10 +1323,10 @@
                     self.ui.warn(_('warning: not adding %s\n') % x)
                     continue
                 if x in dd:
-                    del dd[dd.index(x)]
-                    mm.append(x)
+                    dd.remove(x)
+                    mm.add(x)
                 else:
-                    aa.append(x)
+                    aa.add(x)
             # make sure any files deleted in the local dirstate
             # are not in the add or change column of the patch
             forget = []
@@ -1320,16 +1335,16 @@
                     self.ui.warn(_('warning: not removing %s\n') % x)
                     continue
                 if x in aa:
-                    del aa[aa.index(x)]
+                    aa.remove(x)
                     forget.append(x)
                     continue
-                elif x in mm:
-                    del mm[mm.index(x)]
-                dd.append(x)
-
-            m = list(set(mm))
-            r = list(set(dd))
-            a = list(set(aa))
+                else:
+                    mm.discard(x)
+                dd.add(x)
+
+            m = list(mm)
+            r = list(dd)
+            a = list(aa)
             c = [filter(matchfn, l) for l in (m, a, r)]
             match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
             chunks = patch.diff(repo, patchparent, match=match,
@@ -1529,7 +1544,7 @@
                 l = line.rstrip()
                 l = l[10:].split(' ')
                 qpp = [bin(x) for x in l]
-            elif datastart != None:
+            elif datastart is not None:
                 l = line.rstrip()
                 n, name = l.split(':', 1)
                 if n:
@@ -2344,7 +2359,8 @@
         mergeq = queue(ui, repo.join(""), newpath)
         ui.warn(_("merging with queue at: %s\n") % mergeq.path)
     ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
-                 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'))
+                 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
+                 exact=opts.get('exact'))
     return ret
 
 def pop(ui, repo, patch=None, **opts):
@@ -3120,6 +3136,7 @@
     "^qpush":
         (push,
          [('f', 'force', None, _('apply on top of local changes')),
+          ('e', 'exact', None, _('apply the target patch to its recorded parent')),
           ('l', 'list', None, _('list patch name in commit text')),
           ('a', 'all', None, _('apply all patches')),
           ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
--- a/hgext/record.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/hgext/record.py	Mon Dec 13 12:19:26 2010 -0600
@@ -10,7 +10,7 @@
 from mercurial.i18n import gettext, _
 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
 from mercurial import util
-import copy, cStringIO, errno, os, re, tempfile
+import copy, cStringIO, errno, os, re, shutil, tempfile
 
 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
 
@@ -344,10 +344,10 @@
             # new hunk
             if resp_file[0] is None and resp_all[0] is None:
                 chunk.pretty(ui)
-            r = total == 1 and prompt(_('record this change to %r?') %
-                                      chunk.filename()) \
-                           or  prompt(_('record change %d/%d to %r?') %
-                                      (pos, total, chunk.filename()))
+            r = (total == 1
+                 and prompt(_('record this change to %r?') % chunk.filename())
+                 or prompt(_('record change %d/%d to %r?') %
+                           (pos, total, chunk.filename())))
             if r:
                 if fixoffset:
                     chunk = copy.copy(chunk)
@@ -429,7 +429,7 @@
         merge = len(repo[None].parents()) > 1
         if merge:
             raise util.Abort(_('cannot partially commit a merge '
-                               '(use hg commit instead)'))
+                               '(use "hg commit" instead)'))
 
         changes = repo.status(match=match)[:3]
         diffopts = mdiff.diffopts(git=True, nodates=True)
@@ -475,6 +475,7 @@
                 os.close(fd)
                 ui.debug('backup %r as %r\n' % (f, tmpname))
                 util.copyfile(repo.wjoin(f), tmpname)
+                shutil.copystat(repo.wjoin(f), tmpname)
                 backups[f] = tmpname
 
             fp = cStringIO.StringIO()
@@ -521,6 +522,14 @@
                 for realname, tmpname in backups.iteritems():
                     ui.debug('restoring %r to %r\n' % (tmpname, realname))
                     util.copyfile(tmpname, repo.wjoin(realname))
+                    # Our calls to copystat() here and above are a
+                    # hack to trick any editors that have f open that
+                    # we haven't modified them.
+                    #
+                    # Also note that this racy as an editor could
+                    # notice the file's mtime before we've finished
+                    # writing it.
+                    shutil.copystat(tmpname, repo.wjoin(realname))
                     os.unlink(tmpname)
                 os.rmdir(backupdir)
             except OSError:
--- a/hgext/transplant.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/hgext/transplant.py	Mon Dec 13 12:19:26 2010 -0600
@@ -401,7 +401,7 @@
 
 def hasnode(repo, node):
     try:
-        return repo.changelog.rev(node) != None
+        return repo.changelog.rev(node) is not None
     except error.RevlogError:
         return False
 
--- a/i18n/da.po	Mon Dec 13 11:51:01 2010 -0600
+++ b/i18n/da.po	Mon Dec 13 12:19:26 2010 -0600
@@ -17,8 +17,8 @@
 msgstr ""
 "Project-Id-Version: Mercurial\n"
 "Report-Msgid-Bugs-To: <mercurial-devel@selenic.com>\n"
-"POT-Creation-Date: 2010-10-28 09:50+0200\n"
-"PO-Revision-Date: 2010-10-28 10:01+0200\n"
+"POT-Creation-Date: 2010-11-01 11:03+0100\n"
+"PO-Revision-Date: 2010-11-01 11:11+0100\n"
 "Last-Translator: <mg@lazybytes.net>\n"
 "Language-Team: Danish\n"
 "MIME-Version: 1.0\n"
@@ -3396,6 +3396,10 @@
 msgstr "\"%s\" kan ikke bruges som navnet på en rettelse"
 
 #, python-format
+msgid "\"%s\" already exists as a directory"
+msgstr "\"%s\" eksisterer allerede som et katalog"
+
+#, python-format
 msgid "patch \"%s\" already exists"
 msgstr "rettelsen \"%s\" findes allerede"
 
@@ -3403,6 +3407,10 @@
 msgstr "kan ikke håndtere sammenføjninger"
 
 #, python-format
+msgid "cannot write patch \"%s\": %s"
+msgstr "kan ikke skrive patch \"%s\": %s"
+
+#, python-format
 msgid "error unlinking %s\n"
 msgstr "fejl ved sletning af %s\n"
 
@@ -4723,10 +4731,10 @@
 
 msgid ""
 "  [pager]\n"
-"  pager = LESS='FSRX' less"
+"  pager = less -FRSX"
 msgstr ""
 "  [pager]\n"
-"  pager = LESS='FSRX' less"
+"  pager = less -FRSX"
 
 msgid ""
 "If no pager is set, the pager extensions uses the environment variable\n"
@@ -4734,18 +4742,6 @@
 msgstr ""
 
 msgid ""
-"By default, the pager is only executed if a command has output. To\n"
-"force the pager to run even if a command prints nothing, set::"
-msgstr ""
-
-msgid ""
-"  [pager]\n"
-"  force = True"
-msgstr ""
-"  [pager]\n"
-"  force = True"
-
-msgid ""
 "If you notice \"BROKEN PIPE\" error messages, you can disable them by\n"
 "setting::"
 msgstr ""
@@ -6225,6 +6221,12 @@
 msgstr "noterer fjernelse af %s som en omdøbning til %s (%d%% lighed)\n"
 
 #, python-format
+msgid "%s has not been committed yet, so no copy data will be stored for %s.\n"
+msgstr ""
+"%s er endnu ikke comitted, så der vil ikke blive gemt kopieringsdata for %"
+"s.\n"
+
+#, python-format
 msgid "%s: not copying - file is not managed\n"
 msgstr "%s: kopierer ikke - filen er ikke versionsstyret\n"
 
@@ -6264,12 +6266,6 @@
 msgid "copying %s to %s\n"
 msgstr "kopierer %s til %s\n"
 
-#, python-format
-msgid "%s has not been committed yet, so no copy data will be stored for %s.\n"
-msgstr ""
-"%s er endnu ikke comitted, så der vil ikke blive gemt kopieringsdata for %"
-"s.\n"
-
 msgid "no source or destination specified"
 msgstr "ingen kilde eller destination angivet"
 
@@ -9355,6 +9351,9 @@
 msgid "[PATH]"
 msgstr "[STI]"
 
+msgid "revlog format"
+msgstr ""
+
 msgid "REPO NAMESPACE [KEY OLD NEW]"
 msgstr ""
 
@@ -12984,10 +12983,10 @@
 
 #, python-format
 msgid "can't use %s here"
-msgstr ""
+msgstr "kan ikke bruge %s her"
 
 msgid "can't use a list in this context"
-msgstr ""
+msgstr "en liste kan ikke bruges i denne konteks"
 
 #, python-format
 msgid "not a function: %s"
@@ -13004,7 +13003,7 @@
 
 #. i18n: "id" is a keyword
 msgid "id requires a string"
-msgstr ""
+msgstr "id kræver en streng"
 
 msgid ""
 "``rev(number)``\n"
@@ -13013,11 +13012,11 @@
 
 #. i18n: "rev" is a keyword
 msgid "rev requires one argument"
-msgstr ""
+msgstr "rev kræver et argument"
 
 #. i18n: "rev" is a keyword
 msgid "rev requires a number"
-msgstr ""
+msgstr "rev kræver et tal"
 
 #. i18n: "rev" is a keyword
 msgid "rev expects a number"
@@ -13298,20 +13297,23 @@
 "``tag(name)``\n"
 "    The specified tag by name, or all tagged revisions if no name is given."
 msgstr ""
+"``tag(navn)``\n"
+"    Den navngivne mærkat eller alle revisioner med en mærkat hvis der\n"
+"    ikke angives noget navn."
 
 #. i18n: "tag" is a keyword
 msgid "tag takes one or no arguments"
-msgstr ""
+msgstr "tag tager et eller to argumenter"
 
 #. i18n: "tag" is a keyword
 msgid "the argument to tag must be a string"
-msgstr ""
+msgstr "argumentet til tag skal være en streng"
 
 msgid "can't negate that"
 msgstr ""
 
 msgid "not a symbol"
-msgstr ""
+msgstr "ikke et symbol"
 
 msgid "empty query"
 msgstr "tomt forespørgsel"
@@ -13560,11 +13562,11 @@
 msgstr "certifikatet er for %s"
 
 msgid "no commonName found in certificate"
-msgstr ""
+msgstr "fandt ikke noget commonName i certifikatet"
 
 #, python-format
 msgid "%s certificate error: %s"
-msgstr ""
+msgstr "%s certifikatfejl: %s"
 
 #, python-format
 msgid "command '%s' failed: %s"
@@ -13647,7 +13649,7 @@
 
 #, python-format
 msgid "no port number associated with service '%s'"
-msgstr ""
+msgstr "der er ikke knyttet noget portnummer til servicen '%s'"
 
 msgid "cannot verify bundle or remote repos"
 msgstr "kan ikke verificere bundt eller fjerndepoter"
@@ -13704,7 +13706,7 @@
 msgstr "duplikeret revision %d (%d)"
 
 msgid "abandoned transaction found - run hg recover\n"
-msgstr ""
+msgstr "fandt efterladt transaktion - kør hg recover\n"
 
 #, python-format
 msgid "repository uses revlog format %d\n"
@@ -13738,7 +13740,7 @@
 msgstr "krydstjekker filer i ændringer og manifester\n"
 
 msgid "crosschecking"
-msgstr ""
+msgstr "krydstjekker"
 
 #, python-format
 msgid "changeset refers to unknown manifest %s"
@@ -13766,7 +13768,7 @@
 
 #, python-format
 msgid "%s not in manifests"
-msgstr ""
+msgstr "%s findes ikke i manifestet"
 
 #, python-format
 msgid "unpacked size is %s, %s expected"
--- a/i18n/polib.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/i18n/polib.py	Mon Dec 13 12:19:26 2010 -0600
@@ -105,7 +105,7 @@
     ... finally:
     ...     os.unlink(tmpf)
     """
-    if kwargs.get('autodetect_encoding', True) == True:
+    if kwargs.get('autodetect_encoding', True):
         enc = detect_encoding(fpath)
     else:
         enc = kwargs.get('encoding', default_encoding)
@@ -159,7 +159,7 @@
     ...     finally:
     ...         os.unlink(tmpf)
     """
-    if kwargs.get('autodetect_encoding', True) == True:
+    if kwargs.get('autodetect_encoding', True):
         enc = detect_encoding(fpath, True)
     else:
         enc = kwargs.get('encoding', default_encoding)
--- a/mercurial/archival.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/archival.py	Mon Dec 13 12:19:26 2010 -0600
@@ -8,7 +8,7 @@
 from i18n import _
 from node import hex
 import cmdutil
-import util
+import util, encoding
 import cStringIO, os, stat, tarfile, time, zipfile
 import zlib, gzip
 
@@ -245,7 +245,7 @@
     if repo.ui.configbool("ui", "archivemeta", True):
         def metadata():
             base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
-                repo[0].hex(), hex(node), ctx.branch())
+                repo[0].hex(), hex(node), encoding.fromlocal(ctx.branch()))
 
             tags = ''.join('tag: %s\n' % t for t in ctx.tags()
                            if repo.tagtype(t) == 'global')
--- a/mercurial/bdiff.c	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/bdiff.c	Mon Dec 13 12:19:26 2010 -0600
@@ -57,12 +57,10 @@
 	int pos, len;
 };
 
+struct hunk;
 struct hunk {
 	int a1, a2, b1, b2;
-};
-
-struct hunklist {
-	struct hunk *base, *head;
+	struct hunk *next;
 };
 
 int splitlines(const char *a, int len, struct line **lr)
@@ -223,8 +221,8 @@
 	return mk + mb;
 }
 
-static void recurse(struct line *a, struct line *b, struct pos *pos,
-		    int a1, int a2, int b1, int b2, struct hunklist *l)
+static struct hunk *recurse(struct line *a, struct line *b, struct pos *pos,
+			    int a1, int a2, int b1, int b2, struct hunk *l)
 {
 	int i, j, k;
 
@@ -232,51 +230,66 @@
 		/* find the longest match in this chunk */
 		k = longest_match(a, b, pos, a1, a2, b1, b2, &i, &j);
 		if (!k)
-			return;
+			return l;
 
 		/* and recurse on the remaining chunks on either side */
-		recurse(a, b, pos, a1, i, b1, j, l);
-		l->head->a1 = i;
-		l->head->a2 = i + k;
-		l->head->b1 = j;
-		l->head->b2 = j + k;
-		l->head++;
-		/* tail-recursion didn't happen, so doing equivalent iteration */
+		l = recurse(a, b, pos, a1, i, b1, j, l);
+		if (!l)
+			return NULL;
+
+		l->next = (struct hunk *)malloc(sizeof(struct hunk));
+		if (!l->next)
+			return NULL;
+
+		l = l->next;
+		l->a1 = i;
+		l->a2 = i + k;
+		l->b1 = j;
+		l->b2 = j + k;
+		l->next = NULL;
+
+		/* tail-recursion didn't happen, so do equivalent iteration */
 		a1 = i + k;
 		b1 = j + k;
 	}
 }
 
-static struct hunklist diff(struct line *a, int an, struct line *b, int bn)
+static int diff(struct line *a, int an, struct line *b, int bn,
+		 struct hunk *base)
 {
-	struct hunklist l;
 	struct hunk *curr;
 	struct pos *pos;
-	int t;
+	int t, count = 0;
 
 	/* allocate and fill arrays */
 	t = equatelines(a, an, b, bn);
 	pos = (struct pos *)calloc(bn ? bn : 1, sizeof(struct pos));
-	/* we can't have more matches than lines in the shorter file */
-	l.head = l.base = (struct hunk *)malloc(sizeof(struct hunk) *
-	                                        ((an<bn ? an:bn) + 1));
+
+	if (pos && t) {
+		/* generate the matching block list */
+
+		curr = recurse(a, b, pos, 0, an, 0, bn, base);
+		if (!curr)
+			return -1;
 
-	if (pos && l.base && t) {
-		/* generate the matching block list */
-		recurse(a, b, pos, 0, an, 0, bn, &l);
-		l.head->a1 = l.head->a2 = an;
-		l.head->b1 = l.head->b2 = bn;
-		l.head++;
+		/* sentinel end hunk */
+		curr->next = (struct hunk *)malloc(sizeof(struct hunk));
+		if (!curr->next)
+			return -1;
+		curr = curr->next;
+		curr->a1 = curr->a2 = an;
+		curr->b1 = curr->b2 = bn;
+		curr->next = NULL;
 	}
 
 	free(pos);
 
 	/* normalize the hunk list, try to push each hunk towards the end */
-	for (curr = l.base; curr != l.head; curr++) {
-		struct hunk *next = curr + 1;
+	for (curr = base->next; curr; curr = curr->next) {
+		struct hunk *next = curr->next;
 		int shift = 0;
 
-		if (next == l.head)
+		if (!next)
 			break;
 
 		if (curr->a2 == next->a1)
@@ -297,16 +310,26 @@
 		next->a1 += shift;
 	}
 
-	return l;
+	for (curr = base->next; curr; curr = curr->next)
+		count++;
+	return count;
+}
+
+static void freehunks(struct hunk *l)
+{
+	struct hunk *n;
+	for (; l; l = n) {
+		n = l->next;
+		free(l);
+	}
 }
 
 static PyObject *blocks(PyObject *self, PyObject *args)
 {
 	PyObject *sa, *sb, *rl = NULL, *m;
 	struct line *a, *b;
-	struct hunklist l = {NULL, NULL};
-	struct hunk *h;
-	int an, bn, pos = 0;
+	struct hunk l, *h;
+	int an, bn, count, pos = 0;
 
 	if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
 		return NULL;
@@ -317,12 +340,16 @@
 	if (!a || !b)
 		goto nomem;
 
-	l = diff(a, an, b, bn);
-	rl = PyList_New(l.head - l.base);
-	if (!l.head || !rl)
+	l.next = NULL;
+	count = diff(a, an, b, bn, &l);
+	if (count < 0)
 		goto nomem;
 
-	for (h = l.base; h != l.head; h++) {
+	rl = PyList_New(count);
+	if (!rl)
+		goto nomem;
+
+	for (h = l.next; h; h = h->next) {
 		m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2);
 		PyList_SetItem(rl, pos, m);
 		pos++;
@@ -331,7 +358,7 @@
 nomem:
 	free(a);
 	free(b);
-	free(l.base);
+	freehunks(l.next);
 	return rl ? rl : PyErr_NoMemory();
 }
 
@@ -340,10 +367,9 @@
 	char *sa, *sb;
 	PyObject *result = NULL;
 	struct line *al, *bl;
-	struct hunklist l = {NULL, NULL};
-	struct hunk *h;
+	struct hunk l, *h;
 	char encode[12], *rb;
-	int an, bn, len = 0, la, lb;
+	int an, bn, len = 0, la, lb, count;
 
 	if (!PyArg_ParseTuple(args, "s#s#:bdiff", &sa, &la, &sb, &lb))
 		return NULL;
@@ -353,13 +379,14 @@
 	if (!al || !bl)
 		goto nomem;
 
-	l = diff(al, an, bl, bn);
-	if (!l.head)
+	l.next = NULL;
+	count = diff(al, an, bl, bn, &l);
+	if (count < 0)
 		goto nomem;
 
 	/* calculate length of output */
 	la = lb = 0;
-	for (h = l.base; h != l.head; h++) {
+	for (h = l.next; h; h = h->next) {
 		if (h->a1 != la || h->b1 != lb)
 			len += 12 + bl[h->b1].l - bl[lb].l;
 		la = h->a2;
@@ -375,7 +402,7 @@
 	rb = PyBytes_AsString(result);
 	la = lb = 0;
 
-	for (h = l.base; h != l.head; h++) {
+	for (h = l.next; h; h = h->next) {
 		if (h->a1 != la || h->b1 != lb) {
 			len = bl[h->b1].l - bl[lb].l;
 			*(uint32_t *)(encode)     = htonl(al[la].l - al->l);
@@ -392,7 +419,7 @@
 nomem:
 	free(al);
 	free(bl);
-	free(l.base);
+	freehunks(l.next);
 	return result ? result : PyErr_NoMemory();
 }
 
--- a/mercurial/cmdutil.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/cmdutil.py	Mon Dec 13 12:19:26 2010 -0600
@@ -147,6 +147,11 @@
         # attempt to parse old-style ranges first to deal with
         # things like old-tag which contain query metacharacters
         try:
+            if isinstance(spec, int):
+                seen.add(spec)
+                l.append(spec)
+                continue
+
             if revrangesep in spec:
                 start, end = spec.split(revrangesep, 1)
                 start = revfix(repo, start, 0)
@@ -228,7 +233,8 @@
     writable = 'w' in mode or 'a' in mode
 
     if not pat or pat == '-':
-        return writable and sys.stdout or sys.stdin
+        fp = writable and sys.stdout or sys.stdin
+        return os.fdopen(os.dup(fp.fileno()), mode)
     if hasattr(pat, 'write') and writable:
         return pat
     if hasattr(pat, 'read') and 'r' in mode:
@@ -694,6 +700,8 @@
         for chunk in patch.diff(repo, prev, node, opts=opts):
             fp.write(chunk)
 
+        fp.flush()
+
     for seqno, rev in enumerate(revs):
         single(rev, seqno + 1, fp)
 
@@ -796,7 +804,6 @@
         branch = ctx.branch()
         # don't show the default branch name
         if branch != 'default':
-            branch = encoding.tolocal(branch)
             self.ui.write(_("branch:      %s\n") % branch,
                           label='log.branch')
         for tag in self.repo.nodetags(changenode):
@@ -1352,8 +1359,7 @@
     if ctx.p2():
         edittext.append(_("HG: branch merge"))
     if ctx.branch():
-        edittext.append(_("HG: branch '%s'")
-                        % encoding.tolocal(ctx.branch()))
+        edittext.append(_("HG: branch '%s'") % ctx.branch())
     edittext.extend([_("HG: subrepo %s") % s for s in subs])
     edittext.extend([_("HG: added %s") % f for f in added])
     edittext.extend([_("HG: changed %s") % f for f in modified])
--- a/mercurial/commands.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/commands.py	Mon Dec 13 12:19:26 2010 -0600
@@ -126,7 +126,7 @@
         lastfunc = funcmap[-1]
         funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
 
-    ctx = repo[opts.get('rev')]
+    ctx = cmdutil.revsingle(repo, opts.get('rev'))
     m = cmdutil.match(repo, pats, opts)
     follow = not opts.get('no_follow')
     for abs in ctx.walk(m):
@@ -178,7 +178,7 @@
     Returns 0 on success.
     '''
 
-    ctx = repo[opts.get('rev')]
+    ctx = cmdutil.revsingle(repo, opts.get('rev'))
     if not ctx:
         raise util.Abort(_('no working directory: please specify a revision'))
     node = ctx.node()
@@ -243,7 +243,7 @@
         opts['date'] = util.parsedate(date)
 
     cmdutil.bail_if_changed(repo)
-    node = repo.lookup(rev)
+    node = cmdutil.revsingle(repo, rev).node()
 
     op1, op2 = repo.dirstate.parents()
     a = repo.changelog.ancestor(op1, node)
@@ -408,7 +408,8 @@
                     raise util.Abort(_("%s killed") % command)
                 else:
                     transition = "bad"
-                ctx = repo[rev or '.']
+                ctx = cmdutil.revsingle(repo, rev)
+                rev = None # clear for future iterations
                 state[transition].append(ctx.node())
                 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
                 check_state(state, interactive=False)
@@ -487,15 +488,14 @@
         repo.dirstate.setbranch(label)
         ui.status(_('reset working directory to branch %s\n') % label)
     elif label:
-        utflabel = encoding.fromlocal(label)
-        if not opts.get('force') and utflabel in repo.branchtags():
+        if not opts.get('force') and label in repo.branchtags():
             if label not in [p.branch() for p in repo.parents()]:
                 raise util.Abort(_('a branch of the same name already exists'
                                    " (use 'hg update' to switch to it)"))
-        repo.dirstate.setbranch(utflabel)
+        repo.dirstate.setbranch(label)
         ui.status(_('marked working directory as branch %s\n') % label)
     else:
-        ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
+        ui.write("%s\n" % repo.dirstate.branch())
 
 def branches(ui, repo, active=False, closed=False):
     """list repository named branches
@@ -524,9 +524,8 @@
 
     for isactive, node, tag in branches:
         if (not active) or isactive:
-            encodedtag = encoding.tolocal(tag)
             if ui.quiet:
-                ui.write("%s\n" % encodedtag)
+                ui.write("%s\n" % tag)
             else:
                 hn = repo.lookup(node)
                 if isactive:
@@ -542,10 +541,10 @@
                     notice = _(' (inactive)')
                 if tag == repo.dirstate.branch():
                     label = 'branches.current'
-                rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
+                rev = str(node).rjust(31 - encoding.colwidth(tag))
                 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
-                encodedtag = ui.label(encodedtag, label)
-                ui.write("%s %s%s\n" % (encodedtag, rev, notice))
+                tag = ui.label(tag, label)
+                ui.write("%s %s%s\n" % (tag, rev, notice))
 
 def bundle(ui, repo, fname, dest=None, **opts):
     """create a changegroup file
@@ -572,11 +571,14 @@
 
     Returns 0 on success, 1 if no changes found.
     """
-    revs = opts.get('rev') or None
+    revs = None
+    if 'rev' in opts:
+        revs = cmdutil.revrange(repo, opts['rev'])
+
     if opts.get('all'):
         base = ['null']
     else:
-        base = opts.get('base')
+        base = cmdutil.revrange(repo, opts.get('base'))
     if base:
         if dest:
             raise util.Abort(_("--base is incompatible with specifying "
@@ -1026,7 +1028,7 @@
 
 def debugrebuildstate(ui, repo, rev="tip"):
     """rebuild the dirstate as it would look like for the given revision"""
-    ctx = repo[rev]
+    ctx = cmdutil.revsingle(repo, rev)
     wlock = repo.wlock()
     try:
         repo.dirstate.rebuild(ctx.node(), ctx.manifest())
@@ -1116,7 +1118,7 @@
         key, old, new = keyinfo
         r = target.pushkey(namespace, key, old, new)
         ui.status(str(r) + '\n')
-        return not(r)
+        return not r
     else:
         for k, v in target.listkeys(namespace).iteritems():
             ui.write("%s\t%s\n" % (k.encode('string-escape'),
@@ -1140,12 +1142,12 @@
     Returns 0 on success.
     """
 
-    if not rev2:
-        rev2 = hex(nullid)
+    r1 = cmdutil.revsingle(repo, rev1).node()
+    r2 = cmdutil.revsingle(repo, rev2, 'null').node()
 
     wlock = repo.wlock()
     try:
-        repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
+        repo.dirstate.setparents(r1, r2)
     finally:
         wlock.release()
 
@@ -1174,9 +1176,8 @@
         ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
 
 def debugsub(ui, repo, rev=None):
-    if rev == '':
-        rev = None
-    for k, v in sorted(repo[rev].substate.items()):
+    ctx = cmdutil.revsingle(repo, rev, None)
+    for k, v in sorted(ctx.substate.items()):
         ui.write('path %s\n' % k)
         ui.write(' source   %s\n' % v[0])
         ui.write(' revision %s\n' % v[1])
@@ -1435,7 +1436,7 @@
 def debugrename(ui, repo, file1, *pats, **opts):
     """dump rename information"""
 
-    ctx = repo[opts.get('rev')]
+    ctx = cmdutil.revsingle(repo, opts.get('rev'))
     m = cmdutil.match(repo, (file1,) + pats, opts)
     for abs in ctx.walk(m):
         fctx = ctx[abs]
@@ -1808,10 +1809,9 @@
     Returns 0 if matching heads are found, 1 if not.
     """
 
-    if opts.get('rev'):
-        start = repo.lookup(opts['rev'])
-    else:
-        start = None
+    start = None
+    if 'rev' in opts:
+        start = cmdutil.revsingle(repo, opts['rev'], None).node()
 
     if opts.get('topo'):
         heads = [repo[h] for h in repo.heads(start)]
@@ -1828,8 +1828,7 @@
             heads += [repo[h] for h in ls if rev(h) in descendants]
 
     if branchrevs:
-        decode, encode = encoding.fromlocal, encoding.tolocal
-        branches = set(repo[decode(br)].branch() for br in branchrevs)
+        branches = set(repo[br].branch() for br in branchrevs)
         heads = [h for h in heads if h.branch() in branches]
 
     if not opts.get('closed'):
@@ -1842,7 +1841,7 @@
     if branchrevs:
         haveheads = set(h.branch() for h in heads)
         if branches - haveheads:
-            headless = ', '.join(encode(b) for b in branches - haveheads)
+            headless = ', '.join(b for b in branches - haveheads)
             msg = _('no open branch heads found on branches %s')
             if opts.get('rev'):
                 msg += _(' (started at %s)' % opts['rev'])
@@ -2200,14 +2199,14 @@
             output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
                                     (changed) and "+" or ""))
     else:
-        ctx = repo[rev]
+        ctx = cmdutil.revsingle(repo, rev)
         if default or id:
             output = [hexfunc(ctx.node())]
         if num:
             output.append(str(ctx.rev()))
 
     if repo.local() and default and not ui.quiet:
-        b = encoding.tolocal(ctx.branch())
+        b = ctx.branch()
         if b != 'default':
             output.append("(%s)" % b)
 
@@ -2217,7 +2216,7 @@
             output.append(t)
 
     if branch:
-        output.append(encoding.tolocal(ctx.branch()))
+        output.append(ctx.branch())
 
     if tags:
         output.extend(ctx.tags())
@@ -2279,6 +2278,7 @@
     d = opts["base"]
     strip = opts["strip"]
     wlock = lock = None
+    msgs = []
 
     def tryone(ui, hunk):
         tmpname, message, user, date, branch, nodeid, p1, p2 = \
@@ -2329,7 +2329,10 @@
             finally:
                 files = cmdutil.updatedir(ui, repo, files,
                                           similarity=sim / 100.0)
-            if not opts.get('no_commit'):
+            if opts.get('no_commit'):
+                if message:
+                    msgs.append(message)
+            else:
                 if opts.get('exact'):
                     m = None
                 else:
@@ -2378,6 +2381,8 @@
             if not haspatch:
                 raise util.Abort(_('no diffs found'))
 
+        if msgs:
+            repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
     finally:
         release(lock, wlock)
 
@@ -2437,7 +2442,7 @@
     Returns 0 if a match is found, 1 otherwise.
     """
     end = opts.get('print0') and '\0' or '\n'
-    rev = opts.get('rev') or None
+    rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
 
     ret = 1
     m = cmdutil.match(repo, pats, opts, default='relglob')
@@ -2572,7 +2577,7 @@
         node = rev
 
     decor = {'l':'644 @ ', 'x':'755 * ', '':'644   '}
-    ctx = repo[node]
+    ctx = cmdutil.revsingle(repo, node)
     for f in ctx:
         if ui.debugflag:
             ui.write("%40s " % hex(ctx.manifest()[f]))
@@ -2615,7 +2620,7 @@
         node = opts.get('rev')
 
     if not node:
-        branch = repo.changectx(None).branch()
+        branch = repo[None].branch()
         bheads = repo.branchheads(branch)
         if len(bheads) > 2:
             raise util.Abort(_(
@@ -2641,6 +2646,8 @@
             raise util.Abort(_('working dir not at a head rev - '
                                'use "hg update" or merge with an explicit rev'))
         node = parent == bheads[0] and bheads[-1] or bheads[0]
+    else:
+        node = cmdutil.revsingle(repo, node).node()
 
     if opts.get('preview'):
         # find nodes that are ancestors of p2 but not of p1
@@ -2686,11 +2693,8 @@
 
     Returns 0 on success.
     """
-    rev = opts.get('rev')
-    if rev:
-        ctx = repo[rev]
-    else:
-        ctx = repo[None]
+
+    ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
 
     if file_:
         m = cmdutil.match(repo, (file_,), opts)
@@ -3098,15 +3102,16 @@
             raise util.Abort(_("you can't specify a revision and a date"))
         opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
 
+    parent, p2 = repo.dirstate.parents()
+    if not opts.get('rev') and p2 != nullid:
+        raise util.Abort(_('uncommitted merge - '
+                           'use "hg update", see "hg help revert"'))
+
     if not pats and not opts.get('all'):
         raise util.Abort(_('no files or directories specified; '
                            'use --all to revert the whole repo'))
 
-    parent, p2 = repo.dirstate.parents()
-    if not opts.get('rev') and p2 != nullid:
-        raise util.Abort(_('uncommitted merge - please provide a '
-                           'specific revision'))
-    ctx = repo[opts.get('rev')]
+    ctx = cmdutil.revsingle(repo, opts.get('rev'))
     node = ctx.node()
     mf = ctx.manifest()
     if node == parent:
@@ -3715,7 +3720,7 @@
     if not rev_ and repo.dirstate.parents()[1] != nullid:
         raise util.Abort(_('uncommitted merge - please provide a '
                            'specific revision'))
-    r = repo[rev_].node()
+    r = cmdutil.revsingle(repo, rev_).node()
 
     if not message:
         # we don't translate commit messages
--- a/mercurial/config.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/config.py	Mon Dec 13 12:19:26 2010 -0600
@@ -130,7 +130,7 @@
                 name = m.group(1)
                 if sections and section not in sections:
                     continue
-                if self.get(section, name) != None:
+                if self.get(section, name) is not None:
                     del self._data[section][name]
                 continue
 
--- a/mercurial/context.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/context.py	Mon Dec 13 12:19:26 2010 -0600
@@ -7,7 +7,7 @@
 
 from node import nullid, nullrev, short, hex
 from i18n import _
-import ancestor, bdiff, error, util, subrepo, patch
+import ancestor, bdiff, error, util, subrepo, patch, encoding
 import os, errno, stat
 
 propertycache = util.propertycache
@@ -109,7 +109,7 @@
     def description(self):
         return self._changeset[4]
     def branch(self):
-        return self._changeset[5].get("branch")
+        return encoding.tolocal(self._changeset[5].get("branch"))
     def extra(self):
         return self._changeset[5]
     def tags(self):
@@ -179,7 +179,7 @@
         """
         # deal with workingctxs
         n2 = c2._node
-        if n2 == None:
+        if n2 is None:
             n2 = c2._parents[0]._node
         n = self._repo.changelog.ancestor(self._node, n2)
         return changectx(self._repo, n)
@@ -591,9 +591,8 @@
         if extra:
             self._extra = extra.copy()
         if 'branch' not in self._extra:
-            branch = self._repo.dirstate.branch()
             try:
-                branch = branch.decode('UTF-8').encode('UTF-8')
+                branch = encoding.fromlocal(self._repo.dirstate.branch())
             except UnicodeDecodeError:
                 raise util.Abort(_('branch name not in UTF-8!'))
             self._extra['branch'] = branch
@@ -603,6 +602,9 @@
     def __str__(self):
         return str(self._parents[0]) + "+"
 
+    def __repr__(self):
+        return "<workingctx %s>" % str(self)
+
     def __nonzero__(self):
         return True
 
@@ -712,7 +714,7 @@
         assert self._clean is not None  # must call status first
         return self._clean
     def branch(self):
-        return self._extra['branch']
+        return encoding.tolocal(self._extra['branch'])
     def extra(self):
         return self._extra
 
@@ -902,6 +904,9 @@
     def __str__(self):
         return "%s@%s" % (self.path(), self._changectx)
 
+    def __repr__(self):
+        return "<workingfilectx %s>" % str(self)
+
     def data(self):
         return self._repo.wread(self._path)
     def renamed(self):
@@ -1042,7 +1047,7 @@
     def clean(self):
         return self._status[6]
     def branch(self):
-        return self._extra['branch']
+        return encoding.tolocal(self._extra['branch'])
     def extra(self):
         return self._extra
     def flags(self, f):
--- a/mercurial/demandimport.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/demandimport.py	Mon Dec 13 12:19:26 2010 -0600
@@ -78,10 +78,10 @@
         self._load()
         setattr(self._module, attr, val)
 
-def _demandimport(name, globals=None, locals=None, fromlist=None, level=None):
+def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1):
     if not locals or name in ignore or fromlist == ('*',):
         # these cases we can't really delay
-        if level is None:
+        if level == -1:
             return _origimport(name, globals, locals, fromlist)
         else:
             return _origimport(name, globals, locals, fromlist, level)
@@ -91,7 +91,10 @@
             base, rest = name.split('.', 1)
             # email.__init__ loading email.mime
             if globals and globals.get('__name__', None) == base:
-                return _origimport(name, globals, locals, fromlist)
+                if level != -1:
+                    return _origimport(name, globals, locals, fromlist, level)
+                else:
+                    return _origimport(name, globals, locals, fromlist)
             # if a is already demand-loaded, add b to its submodule list
             if base in locals:
                 if isinstance(locals[base], _demandmod):
@@ -99,7 +102,7 @@
                 return locals[base]
         return _demandmod(name, globals, locals)
     else:
-        if level is not None:
+        if level != -1:
             # from . import b,c,d or from .a import b,c,d
             return _origimport(name, globals, locals, fromlist, level)
         # from a import b,c,d
@@ -111,7 +114,7 @@
             mod = getattr(mod, comp)
         for x in fromlist:
             # set requested submodules for demand load
-            if not(hasattr(mod, x)):
+            if not hasattr(mod, x):
                 setattr(mod, x, _demandmod(x, mod.__dict__, locals))
         return mod
 
--- a/mercurial/dirstate.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/dirstate.py	Mon Dec 13 12:19:26 2010 -0600
@@ -7,7 +7,7 @@
 
 from node import nullid
 from i18n import _
-import util, ignore, osutil, parsers
+import util, ignore, osutil, parsers, encoding
 import struct, os, stat, errno
 import cStringIO
 
@@ -36,7 +36,7 @@
 
 class dirstate(object):
 
-    def __init__(self, opener, ui, root):
+    def __init__(self, opener, ui, root, validate):
         '''Create a new dirstate object.
 
         opener is an open()-like callable that can be used to open the
@@ -44,6 +44,7 @@
         the dirstate.
         '''
         self._opener = opener
+        self._validate = validate
         self._root = root
         self._rootdir = os.path.join(root, '')
         self._dirty = False
@@ -197,10 +198,10 @@
             yield x
 
     def parents(self):
-        return self._pl
+        return [self._validate(p) for p in self._pl]
 
     def branch(self):
-        return self._branch
+        return encoding.tolocal(self._branch)
 
     def setparents(self, p1, p2=nullid):
         self._dirty = self._dirtypl = True
@@ -209,8 +210,8 @@
     def setbranch(self, branch):
         if branch in ['tip', '.', 'null']:
             raise util.Abort(_('the name \'%s\' is reserved') % branch)
-        self._branch = branch
-        self._opener("branch", "w").write(branch + '\n')
+        self._branch = encoding.fromlocal(branch)
+        self._opener("branch", "w").write(self._branch + '\n')
 
     def _read(self):
         self._map = {}
--- a/mercurial/discovery.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/discovery.py	Mon Dec 13 12:19:26 2010 -0600
@@ -220,8 +220,6 @@
             # - a local outgoing head descended from update
             # - a remote head that's known locally and not
             #   ancestral to an outgoing head
-            #
-            # New named branches cannot be created without --force.
 
             # 1. Create set of branches involved in the push.
             branches = set(repo[n].branch() for n in outg)
@@ -280,20 +278,30 @@
 
         # 5. Check for new heads.
         # If there are more heads after the push than before, a suitable
-        # warning, depending on unsynced status, is displayed.
+        # error message, depending on unsynced status, is displayed.
+        error = None
         for branch in branches:
-            if len(newmap[branch]) > len(oldmap[branch]):
+            newhs = set(newmap[branch])
+            oldhs = set(oldmap[branch])
+            if len(newhs) > len(oldhs):
+                if error is None:
+                    if branch:
+                        error = _("push creates new remote heads "
+                                  "on branch '%s'!") % branch
+                    else:
+                        error = _("push creates new remote heads!")
+                    if branch in unsynced:
+                        hint = _("you should pull and merge or "
+                                 "use push -f to force")
+                    else:
+                        hint = _("did you forget to merge? "
+                                 "use push -f to force")
                 if branch:
-                    msg = _("push creates new remote heads "
-                            "on branch '%s'!") % branch
-                else:
-                    msg = _("push creates new remote heads!")
-
-                if branch in unsynced:
-                    hint = _("you should pull and merge or use push -f to force")
-                else:
-                    hint = _("did you forget to merge? use push -f to force")
-                raise util.Abort(msg, hint=hint)
+                    repo.ui.debug("new remote heads on branch '%s'\n" % branch)
+                for h in (newhs - oldhs):
+                    repo.ui.debug("new remote head %s\n" % short(h))
+        if error:
+            raise util.Abort(error, hint=hint)
 
         # 6. Check for unsynced changes on involved branches.
         if unsynced:
--- a/mercurial/encoding.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/encoding.py	Mon Dec 13 12:19:26 2010 -0600
@@ -48,6 +48,16 @@
 encodingmode = os.environ.get("HGENCODINGMODE", "strict")
 fallbackencoding = 'ISO-8859-1'
 
+class localstr(str):
+    '''This class allows strings that are unmodified to be
+    round-tripped to the local encoding and back'''
+    def __new__(cls, u, l):
+        s = str.__new__(cls, l)
+        s._utf8 = u
+        return s
+    def __hash__(self):
+        return hash(self._utf8) # avoid collisions in local string space
+
 def tolocal(s):
     """
     Convert a string from internal UTF-8 to local encoding
@@ -57,17 +67,45 @@
     other character sets. We attempt to decode everything strictly
     using UTF-8, then Latin-1, and failing that, we use UTF-8 and
     replace unknown characters.
+
+    The localstr class is used to cache the known UTF-8 encoding of
+    strings next to their local representation to allow lossless
+    round-trip conversion back to UTF-8.
+
+    >>> u = 'foo: \\xc3\\xa4' # utf-8
+    >>> l = tolocal(u)
+    >>> l
+    'foo: ?'
+    >>> fromlocal(l)
+    'foo: \\xc3\\xa4'
+    >>> u2 = 'foo: \\xc3\\xa1'
+    >>> d = { l: 1, tolocal(u2): 2 }
+    >>> d # no collision
+    {'foo: ?': 1, 'foo: ?': 2}
+    >>> 'foo: ?' in d
+    False
+    >>> l1 = 'foo: \\xe4' # historical latin1 fallback
+    >>> l = tolocal(l1)
+    >>> l
+    'foo: ?'
+    >>> fromlocal(l) # magically in utf-8
+    'foo: \\xc3\\xa4'
     """
+
     for e in ('UTF-8', fallbackencoding):
         try:
             u = s.decode(e) # attempt strict decoding
-            return u.encode(encoding, "replace")
+            if e == 'UTF-8':
+                return localstr(s, u.encode(encoding, "replace"))
+            else:
+                return localstr(u.encode('UTF-8'),
+                                u.encode(encoding, "replace"))
         except LookupError, k:
             raise error.Abort("%s, please check your locale settings" % k)
         except UnicodeDecodeError:
             pass
     u = s.decode("utf-8", "replace") # last ditch
-    return u.encode(encoding, "replace")
+    return u.encode(encoding, "replace") # can't round-trip
 
 def fromlocal(s):
     """
@@ -79,6 +117,11 @@
     'replace', which replaces unknown characters with a special
     Unicode character, and 'ignore', which drops the character.
     """
+
+    # can we do a lossless round-trip?
+    if isinstance(s, localstr):
+        return s._utf8
+
     try:
         return s.decode(encoding, encodingmode).encode("utf-8")
     except UnicodeDecodeError, inst:
--- a/mercurial/hg.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/hg.py	Mon Dec 13 12:19:26 2010 -0600
@@ -32,24 +32,22 @@
         return revs, revs[0]
     branchmap = repo.branchmap()
 
-    def primary(butf8):
-        if butf8 == '.':
+    def primary(branch):
+        if branch == '.':
             if not lrepo or not lrepo.local():
                 raise util.Abort(_("dirstate branch not accessible"))
-            butf8 = lrepo.dirstate.branch()
-        if butf8 in branchmap:
-            revs.extend(node.hex(r) for r in reversed(branchmap[butf8]))
+            branch = lrepo.dirstate.branch()
+        if branch in branchmap:
+            revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
             return True
         else:
             return False
 
     for branch in branches:
-        butf8 = encoding.fromlocal(branch)
-        if not primary(butf8):
+        if not primary(branch):
             raise error.RepoLookupError(_("unknown branch '%s'") % branch)
     if hashbranch:
-        butf8 = encoding.fromlocal(hashbranch)
-        if not primary(butf8):
+        if not primary(hashbranch):
             revs.append(hashbranch)
     return revs, revs[0]
 
@@ -365,8 +363,7 @@
                     except error.RepoLookupError:
                         continue
                 bn = dest_repo[uprev].branch()
-                dest_repo.ui.status(_("updating to branch %s\n")
-                                    % encoding.tolocal(bn))
+                dest_repo.ui.status(_("updating to branch %s\n") % bn)
                 _update(dest_repo, uprev)
 
         return src_repo, dest_repo
--- a/mercurial/httprepo.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/httprepo.py	Mon Dec 13 12:19:26 2010 -0600
@@ -160,7 +160,7 @@
                     break
 
         tempname = changegroup.writebundle(cg, None, type)
-        fp = url.httpsendfile(tempname, "rb")
+        fp = url.httpsendfile(self.ui, tempname, "rb")
         headers = {'Content-Type': 'application/mercurial-0.1'}
 
         try:
--- a/mercurial/localrepo.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/localrepo.py	Mon Dec 13 12:19:26 2010 -0600
@@ -105,7 +105,7 @@
         self._tags = None
         self._tagtypes = None
 
-        self._branchcache = None  # in UTF-8
+        self._branchcache = None
         self._branchcachetip = None
         self.nodetagscache = None
         self.filterpats = {}
@@ -178,7 +178,19 @@
 
     @propertycache
     def dirstate(self):
-        return dirstate.dirstate(self.opener, self.ui, self.root)
+        warned = [0]
+        def validate(node):
+            try:
+                r = self.changelog.rev(node)
+                return node
+            except error.LookupError:
+                if not warned[0]:
+                    warned[0] = True
+                    self.ui.warn(_("warning: ignoring unknown"
+                                   " working parent %s!\n") % short(node))
+                return nullid
+
+        return dirstate.dirstate(self.opener, self.ui, self.root, validate)
 
     def __getitem__(self, changeid):
         if changeid is None:
@@ -423,7 +435,6 @@
             bt[bn] = tip
         return bt
 
-
     def _readbranchcache(self):
         partial = {}
         try:
@@ -443,7 +454,8 @@
                 if not l:
                     continue
                 node, label = l.split(" ", 1)
-                partial.setdefault(label.strip(), []).append(bin(node))
+                label = encoding.tolocal(label.strip())
+                partial.setdefault(label, []).append(bin(node))
         except KeyboardInterrupt:
             raise
         except Exception, inst:
@@ -458,7 +470,7 @@
             f.write("%s %s\n" % (hex(tip), tiprev))
             for label, nodes in branches.iteritems():
                 for node in nodes:
-                    f.write("%s %s\n" % (hex(node), label))
+                    f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
             f.rename()
         except (IOError, OSError):
             pass
@@ -617,10 +629,6 @@
 
     def wwrite(self, filename, data, flags):
         data = self._filter(self._decodefilterpats, filename, data)
-        try:
-            os.unlink(self.wjoin(filename))
-        except OSError:
-            pass
         if 'l' in flags:
             self.wopener.symlink(data, filename)
         else:
@@ -647,7 +655,8 @@
         except IOError:
             ds = ""
         self.opener("journal.dirstate", "w").write(ds)
-        self.opener("journal.branch", "w").write(self.dirstate.branch())
+        self.opener("journal.branch", "w").write(
+            encoding.fromlocal(self.dirstate.branch()))
         self.opener("journal.desc", "w").write("%d\n%s\n" % (len(self), desc))
 
         renames = [(self.sjoin("journal"), self.sjoin("undo")),
@@ -705,7 +714,7 @@
                 except IOError:
                     self.ui.warn(_("Named branch could not be reset, "
                                    "current branch still is: %s\n")
-                                 % encoding.tolocal(self.dirstate.branch()))
+                                 % self.dirstate.branch())
                 self.invalidate()
                 self.dirstate.invalidate()
                 self.destroyed()
@@ -1207,8 +1216,7 @@
     def heads(self, start=None):
         heads = self.changelog.heads(start)
         # sort the output in rev descending order
-        heads = [(-self.changelog.rev(h), h) for h in heads]
-        return [n for (r, n) in sorted(heads)]
+        return sorted(heads, key=self.changelog.rev, reverse=True)
 
     def branchheads(self, branch=None, start=None, closed=False):
         '''return a (possibly filtered) list of heads for the given branch
@@ -1493,8 +1501,13 @@
             group = cl.group(msng_cl_lst, identity, collect)
             for cnt, chnk in enumerate(group):
                 yield chnk
-                self.ui.progress(_('bundling changes'), cnt, unit=_('chunks'))
-            self.ui.progress(_('bundling changes'), None)
+                # revlog.group yields three entries per node, so
+                # dividing by 3 gives an approximation of how many
+                # nodes have been processed.
+                self.ui.progress(_('bundling'), cnt / 3,
+                                 unit=_('changesets'))
+            changecount = cnt / 3
+            self.ui.progress(_('bundling'), None)
 
             prune(mnfst, msng_mnfst_set)
             add_extra_nodes(1, msng_mnfst_set)
@@ -1506,10 +1519,17 @@
             group = mnfst.group(msng_mnfst_lst,
                                 lambda mnode: msng_mnfst_set[mnode],
                                 filenode_collector(changedfiles))
+            efiles = {}
             for cnt, chnk in enumerate(group):
+                if cnt % 3 == 1:
+                    mnode = chnk[:20]
+                    efiles.update(mnfst.readdelta(mnode))
                 yield chnk
-                self.ui.progress(_('bundling manifests'), cnt, unit=_('chunks'))
-            self.ui.progress(_('bundling manifests'), None)
+                # see above comment for why we divide by 3
+                self.ui.progress(_('bundling'), cnt / 3,
+                                 unit=_('manifests'), total=changecount)
+            self.ui.progress(_('bundling'), None)
+            efiles = len(efiles)
 
             # These are no longer needed, dereference and toss the memory for
             # them.
@@ -1523,8 +1543,7 @@
                     msng_filenode_set.setdefault(fname, {})
                     changedfiles.add(fname)
             # Go through all our files in order sorted by name.
-            cnt = 0
-            for fname in sorted(changedfiles):
+            for idx, fname in enumerate(sorted(changedfiles)):
                 filerevlog = self.file(fname)
                 if not len(filerevlog):
                     raise util.Abort(_("empty or missing revlog for %s") % fname)
@@ -1547,13 +1566,16 @@
                     group = filerevlog.group(nodeiter,
                                              lambda fnode: missingfnodes[fnode])
                     for chnk in group:
+                        # even though we print the same progress on
+                        # most loop iterations, put the progress call
+                        # here so that time estimates (if any) can be updated
                         self.ui.progress(
-                            _('bundling files'), cnt, item=fname, unit=_('chunks'))
-                        cnt += 1
+                            _('bundling'), idx, item=fname,
+                            unit=_('files'), total=efiles)
                         yield chnk
             # Signal that no more groups are left.
             yield changegroup.closechunk()
-            self.ui.progress(_('bundling files'), None)
+            self.ui.progress(_('bundling'), None)
 
             if msng_cl_lst:
                 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
@@ -1601,20 +1623,30 @@
             collect = changegroup.collector(cl, mmfs, changedfiles)
 
             for cnt, chnk in enumerate(cl.group(nodes, identity, collect)):
-                self.ui.progress(_('bundling changes'), cnt, unit=_('chunks'))
+                # revlog.group yields three entries per node, so
+                # dividing by 3 gives an approximation of how many
+                # nodes have been processed.
+                self.ui.progress(_('bundling'), cnt / 3, unit=_('changesets'))
                 yield chnk
-            self.ui.progress(_('bundling changes'), None)
+            changecount = cnt / 3
+            self.ui.progress(_('bundling'), None)
 
             mnfst = self.manifest
             nodeiter = gennodelst(mnfst)
+            efiles = {}
             for cnt, chnk in enumerate(mnfst.group(nodeiter,
                                                    lookuplinkrev_func(mnfst))):
-                self.ui.progress(_('bundling manifests'), cnt, unit=_('chunks'))
+                if cnt % 3 == 1:
+                    mnode = chnk[:20]
+                    efiles.update(mnfst.readdelta(mnode))
+                # see above comment for why we divide by 3
+                self.ui.progress(_('bundling'), cnt / 3,
+                                 unit=_('manifests'), total=changecount)
                 yield chnk
-            self.ui.progress(_('bundling manifests'), None)
+            efiles = len(efiles)
+            self.ui.progress(_('bundling'), None)
 
-            cnt = 0
-            for fname in sorted(changedfiles):
+            for idx, fname in enumerate(sorted(changedfiles)):
                 filerevlog = self.file(fname)
                 if not len(filerevlog):
                     raise util.Abort(_("empty or missing revlog for %s") % fname)
@@ -1626,10 +1658,10 @@
                     lookup = lookuplinkrev_func(filerevlog)
                     for chnk in filerevlog.group(nodeiter, lookup):
                         self.ui.progress(
-                            _('bundling files'), cnt, item=fname, unit=_('chunks'))
-                        cnt += 1
+                            _('bundling'), idx, item=fname,
+                            total=efiles, unit=_('files'))
                         yield chnk
-            self.ui.progress(_('bundling files'), None)
+            self.ui.progress(_('bundling'), None)
 
             yield changegroup.closechunk()
 
--- a/mercurial/manifest.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/manifest.py	Mon Dec 13 12:19:26 2010 -0600
@@ -171,19 +171,19 @@
                         raise AssertionError(
                                 _("failed to remove %s from manifest") % f)
                     l = ""
-                if dstart != None and dstart <= start and dend >= start:
+                if dstart is not None and dstart <= start and dend >= start:
                     if dend < end:
                         dend = end
                     if l:
                         dline.append(l)
                 else:
-                    if dstart != None:
+                    if dstart is not None:
                         delta.append([dstart, dend, "".join(dline)])
                     dstart = start
                     dend = end
                     dline = [l]
 
-            if dstart != None:
+            if dstart is not None:
                 delta.append([dstart, dend, "".join(dline)])
             # apply the delta to the addlist, and get a delta for addrevision
             cachedelta = (self.rev(p1), addlistdelta(addlist, delta))
--- a/mercurial/minirst.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/minirst.py	Mon Dec 13 12:19:26 2010 -0600
@@ -14,27 +14,8 @@
 are just indented blocks that look like they are nested. This relies
 on the user to keep the right indentation for the blocks.
 
-It only supports a small subset of reStructuredText:
-
-- sections
-
-- paragraphs
-
-- literal blocks
-
-- definition lists
-
-- specific admonitions
-
-- bullet lists (items must start with '-')
-
-- enumerated lists (no autonumbering)
-
-- field lists (colons cannot be escaped)
-
-- option lists (supports only long options without arguments)
-
-- inline literals (no other inline markup is not recognized)
+Remember to update http://mercurial.selenic.com/wiki/HelpStyleGuide
+when adding support for new constructs.
 """
 
 import re, sys
@@ -118,7 +99,8 @@
     return blocks
 
 _bulletre = re.compile(r'(-|[0-9A-Za-z]+\.|\(?[0-9A-Za-z]+\)|\|) ')
-_optionre = re.compile(r'^(--[a-z-]+)((?:[ =][a-zA-Z][\w-]*)?  +)(.*)$')
+_optionre = re.compile(r'^(-([a-zA-Z0-9]), )?(--[a-z0-9-]+)'
+                       r'((.*)  +)(.*)$')
 _fieldre = re.compile(r':(?![: ])([^:]*)(?<! ):[ ]+(.*)')
 _definitionre = re.compile(r'[^ ]')
 
@@ -192,6 +174,42 @@
     return blocks
 
 
+def updateoptionlists(blocks):
+    i = 0
+    while i < len(blocks):
+        if blocks[i]['type'] != 'option':
+            i += 1
+            continue
+
+        optstrwidth = 0
+        j = i
+        while j < len(blocks) and blocks[j]['type'] == 'option':
+            m = _optionre.match(blocks[j]['lines'][0])
+
+            shortoption = m.group(2)
+            group3 = m.group(3)
+            longoption = group3[2:].strip()
+            desc = m.group(6).strip()
+            longoptionarg = m.group(5).strip()
+            blocks[j]['lines'][0] = desc
+
+            noshortop = ''
+            if not shortoption:
+                noshortop = '   '
+
+            opt = "%s%s" %   (shortoption and "-%s " % shortoption or '',
+                            ("%s--%s %s") % (noshortop, longoption,
+                                             longoptionarg))
+            opt = opt.rstrip()
+            blocks[j]['optstr'] = opt
+            optstrwidth = max(optstrwidth, encoding.colwidth(opt))
+            j += 1
+
+        for block in blocks[i:j]:
+            block['optstrwidth'] = optstrwidth
+        i = j + 1
+    return blocks
+
 def prunecontainers(blocks, keep):
     """Prune unwanted containers.
 
@@ -297,8 +315,11 @@
     i = 0
     while i < len(blocks):
         b = blocks[i]
-        if b['type'] == 'paragraph' and b['lines'][0].startswith('.. '):
+        if b['type'] == 'paragraph' and (b['lines'][0].startswith('.. ') or
+                                         b['lines'] == ['..']):
             del blocks[i]
+            if i < len(blocks) and blocks[i]['type'] == 'margin':
+                del blocks[i]
         else:
             i += 1
     return blocks
@@ -338,6 +359,17 @@
                      'tip': _('Tip:'),
                      'warning': _('Warning!')}
 
+def formatoption(block, width):
+    desc = ' '.join(map(str.strip, block['lines']))
+    colwidth = encoding.colwidth(block['optstr'])
+    usablewidth = width - 1
+    hanging = block['optstrwidth']
+    initindent = '%s%s  ' % (block['optstr'], ' ' * ((hanging - colwidth)))
+    hangindent = ' ' * (encoding.colwidth(initindent) + 1)
+    return ' %s' % (util.wrap(desc, usablewidth,
+                                           initindent=initindent,
+                                           hangindent=hangindent))
+
 def formatblock(block, width):
     """Format a block according to width."""
     if width <= 0:
@@ -394,9 +426,7 @@
             key = key.ljust(_fieldwidth)
         block['lines'][0] = key + block['lines'][0]
     elif block['type'] == 'option':
-        m = _optionre.match(block['lines'][0])
-        option, arg, rest = m.groups()
-        subindent = indent + (len(option) + len(arg)) * ' '
+        return formatoption(block, width)
 
     text = ' '.join(map(str.strip, block['lines']))
     return util.wrap(text, width=width,
@@ -416,8 +446,9 @@
     blocks = hgrole(blocks)
     blocks = splitparagraphs(blocks)
     blocks = updatefieldlists(blocks)
+    blocks = updateoptionlists(blocks)
+    blocks = addmargins(blocks)
     blocks = prunecomments(blocks)
-    blocks = addmargins(blocks)
     blocks = findadmonitions(blocks)
     text = '\n'.join(formatblock(b, width) for b in blocks)
     if keep is None:
@@ -443,8 +474,9 @@
     blocks = debug(inlineliterals, blocks)
     blocks = debug(splitparagraphs, blocks)
     blocks = debug(updatefieldlists, blocks)
+    blocks = debug(updateoptionlists, blocks)
     blocks = debug(findsections, blocks)
+    blocks = debug(addmargins, blocks)
     blocks = debug(prunecomments, blocks)
-    blocks = debug(addmargins, blocks)
     blocks = debug(findadmonitions, blocks)
     print '\n'.join(formatblock(b, 30) for b in blocks)
--- a/mercurial/patch.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/patch.py	Mon Dec 13 12:19:26 2010 -0600
@@ -6,7 +6,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 cStringIO, email.Parser, os, re
+import cStringIO, email.Parser, os, errno, re
 import tempfile, zlib
 
 from i18n import _
@@ -429,10 +429,16 @@
         # Ensure supplied data ends in fname, being a regular file or
         # a symlink. cmdutil.updatedir will -too magically- take care
         # of setting it to the proper type afterwards.
+        st_mode = None
         islink = os.path.islink(fname)
         if islink:
             fp = cStringIO.StringIO()
         else:
+            try:
+                st_mode = os.lstat(fname).st_mode & 0777
+            except OSError, e:
+                if e.errno != errno.ENOENT:
+                    raise
             fp = self.opener(fname, 'w')
         try:
             if self.eolmode == 'auto':
@@ -451,6 +457,8 @@
                 fp.writelines(lines)
             if islink:
                 self.opener.symlink(fp.getvalue(), fname)
+            if st_mode is not None:
+                os.chmod(fname, st_mode)
         finally:
             fp.close()
 
@@ -976,7 +984,7 @@
     fp.seek(pos)
     return gitpatches
 
-def iterhunks(ui, fp, sourcefile=None):
+def iterhunks(ui, fp):
     """Read a patch and yield the following events:
     - ("file", afile, bfile, firsthunk): select a new target file.
     - ("hunk", hunk): a new hunk is ready to be applied, follows a
@@ -997,10 +1005,6 @@
     BFILE = 1
     context = None
     lr = linereader(fp)
-    # gitworkdone is True if a git operation (copy, rename, ...) was
-    # performed already for the current file. Useful when the file
-    # section may have no hunk.
-    gitworkdone = False
 
     while True:
         newfile = newgitfile = False
@@ -1012,7 +1016,7 @@
                 current_hunk.fix_newline()
             yield 'hunk', current_hunk
             current_hunk = None
-        if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
+        if (state == BFILE and ((not context and x[0] == '@') or
             ((context is not False) and x.startswith('***************')))):
             if context is None and x.startswith('***************'):
                 context = True
@@ -1034,7 +1038,6 @@
         elif x.startswith('diff --git'):
             # check for git diff, scanning the whole patch file if needed
             m = gitre.match(x)
-            gitworkdone = False
             if m:
                 afile, bfile = m.group(1, 2)
                 if not git:
@@ -1049,7 +1052,6 @@
                 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
                            or gp.mode):
                     afile = bfile
-                    gitworkdone = True
                 newgitfile = True
         elif x.startswith('---'):
             # check for a unified diff
@@ -1077,9 +1079,6 @@
             afile = parsefilename(x)
             bfile = parsefilename(l2)
 
-        if newfile:
-            gitworkdone = False
-
         if newgitfile or newfile:
             emitfile = True
             state = BFILE
@@ -1091,7 +1090,7 @@
             raise PatchError(_("malformed patch %s %s") % (afile,
                              current_hunk.desc))
 
-def applydiff(ui, fp, changed, strip=1, sourcefile=None, eolmode='strict'):
+def applydiff(ui, fp, changed, strip=1, eolmode='strict'):
     """Reads a patch from fp and tries to apply it.
 
     The dict 'changed' is filled in with all of the filenames changed
@@ -1105,13 +1104,10 @@
     Callers probably want to call 'cmdutil.updatedir' after this to
     apply certain categories of changes not done by this function.
     """
-    return _applydiff(
-        ui, fp, patchfile, copyfile,
-        changed, strip=strip, sourcefile=sourcefile, eolmode=eolmode)
+    return _applydiff(ui, fp, patchfile, copyfile, changed, strip=strip,
+                      eolmode=eolmode)
 
-
-def _applydiff(ui, fp, patcher, copyfn, changed, strip=1,
-               sourcefile=None, eolmode='strict'):
+def _applydiff(ui, fp, patcher, copyfn, changed, strip=1, eolmode='strict'):
     rejects = 0
     err = 0
     current_file = None
@@ -1126,7 +1122,7 @@
         current_file.write_rej()
         return len(current_file.rej)
 
-    for state, values in iterhunks(ui, fp, sourcefile):
+    for state, values in iterhunks(ui, fp):
         if state == 'hunk':
             if not current_file:
                 continue
@@ -1139,14 +1135,10 @@
             rejects += closefile()
             afile, bfile, first_hunk = values
             try:
-                if sourcefile:
-                    current_file = patcher(ui, sourcefile, opener,
-                                           eolmode=eolmode)
-                else:
-                    current_file, missing = selectfile(afile, bfile,
-                                                       first_hunk, strip)
-                    current_file = patcher(ui, current_file, opener,
-                                           missing=missing, eolmode=eolmode)
+                current_file, missing = selectfile(afile, bfile,
+                                                   first_hunk, strip)
+                current_file = patcher(ui, current_file, opener,
+                                       missing=missing, eolmode=eolmode)
             except PatchError, err:
                 ui.warn(str(err) + '\n')
                 current_file = None
--- a/mercurial/revlog.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/revlog.py	Mon Dec 13 12:19:26 2010 -0600
@@ -212,7 +212,7 @@
             return None
         self.mapfind_count += 1
         last = self.l - 1
-        while self.index[last] != None:
+        while self.index[last] is not None:
             if last == 0:
                 self.all = 1
                 self.allmap = 1
--- a/mercurial/revset.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/revset.py	Mon Dec 13 12:19:26 2010 -0600
@@ -202,9 +202,13 @@
     return [r for r in subset if r == l]
 
 def p1(repo, subset, x):
-    """``p1(set)``
-    First parent of changesets in set.
+    """``p1([set])``
+    First parent of changesets in set, or the working directory.
     """
+    if x is None:
+        p = repo[x].parents()[0].rev()
+        return [r for r in subset if r == p]
+
     ps = set()
     cl = repo.changelog
     for r in getset(repo, range(len(repo)), x):
@@ -212,9 +216,17 @@
     return [r for r in subset if r in ps]
 
 def p2(repo, subset, x):
-    """``p2(set)``
-    Second parent of changesets in set.
+    """``p2([set])``
+    Second parent of changesets in set, or the working directory.
     """
+    if x is None:
+        ps = repo[x].parents()
+        try:
+            p = ps[1].rev()
+            return [r for r in subset if r == p]
+        except IndexError:
+            return []
+
     ps = set()
     cl = repo.changelog
     for r in getset(repo, range(len(repo)), x):
@@ -222,9 +234,13 @@
     return [r for r in subset if r in ps]
 
 def parents(repo, subset, x):
-    """``parents(set)``
-    The set of all parents for all changesets in set.
+    """``parents([set])``
+    The set of all parents for all changesets in set, or the working directory.
     """
+    if x is None:
+        ps = tuple(p.rev() for p in repo[x].parents())
+        return [r for r in subset if r in ps]
+
     ps = set()
     cl = repo.changelog
     for r in getset(repo, range(len(repo)), x):
@@ -699,7 +715,7 @@
 }
 
 def optimize(x, small):
-    if x == None:
+    if x is None:
         return 0, x
 
     smallbonus = 1
--- a/mercurial/sshrepo.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/sshrepo.py	Mon Dec 13 12:19:26 2010 -0600
@@ -91,10 +91,11 @@
             size = util.fstat(self.pipee).st_size
             if size == 0:
                 break
-            l = self.pipee.readline()
-            if not l:
+            s = self.pipee.read(size)
+            if not s:
                 break
-            self.ui.status(_("remote: "), l)
+            for l in s.splitlines():
+                self.ui.status(_("remote: "), l, '\n')
 
     def _abort(self, exception):
         self.cleanup()
--- a/mercurial/subrepo.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/subrepo.py	Mon Dec 13 12:19:26 2010 -0600
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
-import stat, subprocess
+import stat, subprocess, tarfile
 from i18n import _
 import config, util, node, error, cmdutil
 hg = None
@@ -333,6 +333,8 @@
 
             def addpathconfig(key, value):
                 if value:
+                    if not os.path.isabs(value):
+                        value = os.path.relpath(os.path.abspath(value), root)
                     fp.write('%s = %s\n' % (key, value))
                     self._repo.ui.setconfig('paths', key, value)
 
@@ -484,13 +486,10 @@
     def _svncommand(self, commands, filename=''):
         path = os.path.join(self._ctx._repo.origroot, self._path, filename)
         cmd = ['svn'] + commands + [path]
-        cmd = [util.shellquote(arg) for arg in cmd]
-        cmd = util.quotecommand(' '.join(cmd))
         env = dict(os.environ)
         # Avoid localized output, preserve current locale for everything else.
         env['LC_MESSAGES'] = 'C'
-        p = subprocess.Popen(cmd, shell=True, bufsize=-1,
-                             close_fds=util.closefds,
+        p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                              universal_newlines=True, env=env)
         stdout, stderr = p.communicate()
@@ -604,7 +603,281 @@
         return self._svncommand(['cat'], name)
 
 
+class gitsubrepo(abstractsubrepo):
+    def __init__(self, ctx, path, state):
+        # TODO add git version check.
+        self._state = state
+        self._ctx = ctx
+        self._relpath = path
+        self._path = ctx._repo.wjoin(path)
+        self._ui = ctx._repo.ui
+
+    def _gitcommand(self, commands, env=None, stream=False):
+        return self._gitdir(commands, env=env, stream=stream)[0]
+
+    def _gitdir(self, commands, env=None, stream=False):
+        return self._gitnodir(commands, env=env, stream=stream, cwd=self._path)
+
+    def _gitnodir(self, commands, env=None, stream=False, cwd=None):
+        """Calls the git command
+
+        The methods tries to call the git command. versions previor to 1.6.0
+        are not supported and very probably fail.
+        """
+        self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
+        # unless ui.quiet is set, print git's stderr,
+        # which is mostly progress and useful info
+        errpipe = None
+        if self._ui.quiet:
+            errpipe = open(os.devnull, 'w')
+        p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
+                             close_fds=util.closefds,
+                             stdout=subprocess.PIPE, stderr=errpipe)
+        if stream:
+            return p.stdout, None
+
+        retdata = p.stdout.read().strip()
+        # wait for the child to exit to avoid race condition.
+        p.wait()
+
+        if p.returncode != 0 and p.returncode != 1:
+            # there are certain error codes that are ok
+            command = commands[0]
+            if command == 'cat-file':
+                return retdata, p.returncode
+            # for all others, abort
+            raise util.Abort('git %s error %d in %s' %
+                             (command, p.returncode, self._relpath))
+
+        return retdata, p.returncode
+
+    def _gitstate(self):
+        return self._gitcommand(['rev-parse', 'HEAD'])
+
+    def _githavelocally(self, revision):
+        out, code = self._gitdir(['cat-file', '-e', revision])
+        return code == 0
+
+    def _gitisancestor(self, r1, r2):
+        base = self._gitcommand(['merge-base', r1, r2])
+        return base == r1
+
+    def _gitbranchmap(self):
+        '''returns 3 things:
+        the current branch,
+        a map from git branch to revision
+        a map from revision to branches'''
+        branch2rev = {}
+        rev2branch = {}
+        current = None
+        out = self._gitcommand(['branch', '-a', '--no-color',
+                                '--verbose', '--no-abbrev'])
+        for line in out.split('\n'):
+            if line[2:].startswith('(no branch)'):
+                continue
+            branch, revision = line[2:].split()[:2]
+            if revision == '->' or branch.endswith('/HEAD'):
+                continue # ignore remote/HEAD redirects
+            if '/' in branch and not branch.startswith('remotes/'):
+                # old git compatability
+                branch = 'remotes/' + branch
+            if line[0] == '*':
+                current = branch
+            branch2rev[branch] = revision
+            rev2branch.setdefault(revision, []).append(branch)
+        return current, branch2rev, rev2branch
+
+    def _gittracking(self, branches):
+        'return map of remote branch to local tracking branch'
+        # assumes no more than one local tracking branch for each remote
+        tracking = {}
+        for b in branches:
+            if b.startswith('remotes/'):
+                continue
+            remote = self._gitcommand(['config', 'branch.%s.remote' % b])
+            if remote:
+                ref = self._gitcommand(['config', 'branch.%s.merge' % b])
+                tracking['remotes/%s/%s' % (remote, ref.split('/')[-1])] = b
+        return tracking
+
+    def _fetch(self, source, revision):
+        if not os.path.exists('%s/.git' % self._path):
+            self._ui.status(_('cloning subrepo %s\n') % self._relpath)
+            self._gitnodir(['clone', source, self._path])
+        if self._githavelocally(revision):
+            return
+        self._ui.status(_('pulling subrepo %s\n') % self._relpath)
+        # first try from origin
+        self._gitcommand(['fetch'])
+        if self._githavelocally(revision):
+            return
+        # then try from known subrepo source
+        self._gitcommand(['fetch', source])
+        if not self._githavelocally(revision):
+            raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
+                               (revision, self._path))
+
+    def dirty(self):
+        if self._state[1] != self._gitstate(): # version checked out changed?
+            return True
+        # check for staged changes or modified files; ignore untracked files
+        status = self._gitcommand(['status'])
+        return ('\n# Changed but not updated:' in status or
+                '\n# Changes to be committed:' in status)
+
+    def get(self, state):
+        source, revision, kind = state
+        self._fetch(source, revision)
+        # if the repo was set to be bare, unbare it
+        if self._gitcommand(['config', '--bool', 'core.bare']) == 'true':
+            self._gitcommand(['config', 'core.bare', 'false'])
+            if self._gitstate() == revision:
+                self._gitcommand(['reset', '--hard', 'HEAD'])
+                return
+        elif self._gitstate() == revision:
+            return
+        current, branch2rev, rev2branch = self._gitbranchmap()
+
+        def rawcheckout():
+            # no branch to checkout, check it out with no branch
+            self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
+                          self._relpath)
+            self._ui.warn(_('check out a git branch if you intend '
+                            'to make changes\n'))
+            self._gitcommand(['checkout', '-q', revision])
+
+        if revision not in rev2branch:
+            rawcheckout()
+            return
+        branches = rev2branch[revision]
+        firstlocalbranch = None
+        for b in branches:
+            if b == 'master':
+                # master trumps all other branches
+                self._gitcommand(['checkout', 'master'])
+                return
+            if not firstlocalbranch and not b.startswith('remotes/'):
+                firstlocalbranch = b
+        if firstlocalbranch:
+            self._gitcommand(['checkout', firstlocalbranch])
+            return
+
+        tracking = self._gittracking(branch2rev.keys())
+        # choose a remote branch already tracked if possible
+        remote = branches[0]
+        if remote not in tracking:
+            for b in branches:
+                if b in tracking:
+                    remote = b
+                    break
+
+        if remote not in tracking:
+            # create a new local tracking branch
+            local = remote.split('/')[-1]
+            self._gitcommand(['checkout', '-b', local, remote])
+        elif self._gitisancestor(branch2rev[tracking[remote]], remote):
+            # When updating to a tracked remote branch,
+            # if the local tracking branch is downstream of it,
+            # a normal `git pull` would have performed a "fast-forward merge"
+            # which is equivalent to updating the local branch to the remote.
+            # Since we are only looking at branching at update, we need to
+            # detect this situation and perform this action lazily.
+            if tracking[remote] != current:
+                self._gitcommand(['checkout', tracking[remote]])
+            self._gitcommand(['merge', '--ff', remote])
+        else:
+            # a real merge would be required, just checkout the revision
+            rawcheckout()
+
+    def commit(self, text, user, date):
+        cmd = ['commit', '-a', '-m', text]
+        env = os.environ.copy()
+        if user:
+            cmd += ['--author', user]
+        if date:
+            # git's date parser silently ignores when seconds < 1e9
+            # convert to ISO8601
+            env['GIT_AUTHOR_DATE'] = util.datestr(date,
+                                                  '%Y-%m-%dT%H:%M:%S %1%2')
+        self._gitcommand(cmd, env=env)
+        # make sure commit works otherwise HEAD might not exist under certain
+        # circumstances
+        return self._gitstate()
+
+    def merge(self, state):
+        source, revision, kind = state
+        self._fetch(source, revision)
+        base = self._gitcommand(['merge-base', revision, self._state[1]])
+        if base == revision:
+            self.get(state) # fast forward merge
+        elif base != self._state[1]:
+            self._gitcommand(['merge', '--no-commit', revision])
+
+    def push(self, force):
+        # if a branch in origin contains the revision, nothing to do
+        current, branch2rev, rev2branch = self._gitbranchmap()
+        if self._state[1] in rev2branch:
+            for b in rev2branch[self._state[1]]:
+                if b.startswith('remotes/origin/'):
+                    return True
+        for b, revision in branch2rev.iteritems():
+            if b.startswith('remotes/origin/'):
+                if self._gitisancestor(self._state[1], revision):
+                    return True
+        # otherwise, try to push the currently checked out branch
+        cmd = ['push']
+        if force:
+            cmd.append('--force')
+        if current:
+            # determine if the current branch is even useful
+            if not self._gitisancestor(self._state[1], current):
+                self._ui.warn(_('unrelated git branch checked out '
+                                'in subrepo %s\n') % self._relpath)
+                return False
+            self._ui.status(_('pushing branch %s of subrepo %s\n') %
+                            (current, self._relpath))
+            self._gitcommand(cmd + ['origin', current])
+            return True
+        else:
+            self._ui.warn(_('no branch checked out in subrepo %s\n'
+                            'cannot push revision %s') %
+                          (self._relpath, self._state[1]))
+            return False
+
+    def remove(self):
+        if self.dirty():
+            self._ui.warn(_('not removing repo %s because '
+                            'it has changes.\n') % self._path)
+            return
+        # we can't fully delete the repository as it may contain
+        # local-only history
+        self._ui.note(_('removing subrepo %s\n') % self._path)
+        self._gitcommand(['config', 'core.bare', 'true'])
+        for f in os.listdir(self._path):
+            if f == '.git':
+                continue
+            path = os.path.join(self._path, f)
+            if os.path.isdir(path) and not os.path.islink(path):
+                shutil.rmtree(path)
+            else:
+                os.remove(path)
+
+    def archive(self, archiver, prefix):
+        source, revision = self._state
+        self._fetch(source, revision)
+
+        # Parse git's native archive command.
+        # This should be much faster than manually traversing the trees
+        # and objects with many subprocess calls.
+        tarstream = self._gitcommand(['archive', revision], stream=True)
+        tar = tarfile.open(fileobj=tarstream, mode='r|')
+        for info in tar:
+            archiver.addfile(os.path.join(prefix, self._relpath, info.name),
+                             info.mode, info.issym(),
+                             tar.extractfile(info).read())
+
 types = {
     'hg': hgsubrepo,
     'svn': svnsubrepo,
+    'git': gitsubrepo,
     }
--- a/mercurial/templatekw.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/templatekw.py	Mon Dec 13 12:19:26 2010 -0600
@@ -148,7 +148,6 @@
 def showbranches(**args):
     branch = args['ctx'].branch()
     if branch != 'default':
-        branch = encoding.tolocal(branch)
         return showlist('branch', [branch], plural='branches', **args)
 
 def showchildren(**args):
@@ -163,9 +162,8 @@
     return ctx.description().strip()
 
 def showdiffstat(repo, ctx, templ, **args):
-    diff = patch.diff(repo, ctx.parents()[0].node(), ctx.node())
     files, adds, removes = 0, 0, 0
-    for i in patch.diffstatdata(util.iterlines(diff)):
+    for i in patch.diffstatdata(util.iterlines(ctx.diff())):
         files += 1
         adds += i[1]
         removes += i[2]
--- a/mercurial/ui.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/ui.py	Mon Dec 13 12:19:26 2010 -0600
@@ -589,7 +589,7 @@
         termination.
         '''
 
-        if pos == None or not self.debugflag:
+        if pos is None or not self.debugflag:
             return
 
         if unit:
--- a/mercurial/url.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/url.py	Mon Dec 13 12:19:26 2010 -0600
@@ -258,18 +258,36 @@
     defines a __len__ attribute to feed the Content-Length header.
     """
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, ui, *args, **kwargs):
         # We can't just "self._data = open(*args, **kwargs)" here because there
         # is an "open" function defined in this module that shadows the global
         # one
+        self.ui = ui
         self._data = __builtin__.open(*args, **kwargs)
-        self.read = self._data.read
         self.seek = self._data.seek
         self.close = self._data.close
         self.write = self._data.write
+        self._len = os.fstat(self._data.fileno()).st_size
+        self._pos = 0
+        self._total = len(self) / 1024 * 2
+
+    def read(self, *args, **kwargs):
+        try:
+            ret = self._data.read(*args, **kwargs)
+        except EOFError:
+            self.ui.progress(_('sending'), None)
+        self._pos += len(ret)
+        # We pass double the max for total because we currently have
+        # to send the bundle twice in the case of a server that
+        # requires authentication. Since we can't know until we try
+        # once whether authentication will be required, just lie to
+        # the user and maybe the push succeeds suddenly at 50%.
+        self.ui.progress(_('sending'), self._pos / 1024,
+                         unit=_('kb'), total=self._total)
+        return ret
 
     def __len__(self):
-        return os.fstat(self._data.fileno()).st_size
+        return self._len
 
 def _gen_sendfile(connection):
     def _sendfile(self, data):
--- a/mercurial/util.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/util.py	Mon Dec 13 12:19:26 2010 -0600
@@ -453,7 +453,7 @@
     else:
         try:
             shutil.copyfile(src, dest)
-            shutil.copystat(src, dest)
+            shutil.copymode(src, dest)
         except shutil.Error, inst:
             raise Abort(str(inst))
 
@@ -823,7 +823,7 @@
             self._fp.close()
             rename(self.temp, localpath(self.__name))
 
-    def __del__(self):
+    def close(self):
         if not self._fp:
             return
         if not self._fp.closed:
@@ -832,6 +832,9 @@
             except: pass
             self._fp.close()
 
+    def __del__(self):
+        self.close()
+
 def makedirs(name, mode=None):
     """recursive directory creation with parent mode inheritance"""
     parent = os.path.abspath(os.path.dirname(name))
@@ -880,7 +883,6 @@
             mode += "b" # for that other OS
 
         nlink = -1
-        st_mode = None
         dirname, basename = os.path.split(f)
         # If basename is empty, then the path is malformed because it points
         # to a directory. Let the posixfile() call below raise IOError.
@@ -891,7 +893,6 @@
                 return atomictempfile(f, mode, self.createmode)
             try:
                 if 'w' in mode:
-                    st_mode = os.lstat(f).st_mode & 0777
                     os.unlink(f)
                     nlink = 0
                 else:
@@ -911,10 +912,7 @@
                     rename(mktempcopy(f), f)
         fp = posixfile(f, mode)
         if nlink == 0:
-            if st_mode is None:
-                self._fixfilemode(f)
-            else:
-                os.chmod(f, st_mode)
+            self._fixfilemode(f)
         return fp
 
     def symlink(self, src, dst):
@@ -1059,7 +1057,7 @@
 
     # NOTE: unixtime = localunixtime + offset
     offset, date = timezone(string), string
-    if offset != None:
+    if offset is not None:
         date = " ".join(string.split()[:-1])
 
     # add missing elements from defaults
@@ -1335,15 +1333,26 @@
 #### naming convention of below implementation follows 'textwrap' module
 
 class MBTextWrapper(textwrap.TextWrapper):
+    """
+    Extend TextWrapper for double-width characters.
+
+    Some Asian characters use two terminal columns instead of one.
+    A good example of this behavior can be seen with u'\u65e5\u672c',
+    the two Japanese characters for "Japan":
+    len() returns 2, but when printed to a terminal, they eat 4 columns.
+
+    (Note that this has nothing to do whatsoever with unicode
+    representation, or encoding of the underlying string)
+    """
     def __init__(self, **kwargs):
         textwrap.TextWrapper.__init__(self, **kwargs)
 
     def _cutdown(self, str, space_left):
         l = 0
         ucstr = unicode(str, encoding.encoding)
-        w = unicodedata.east_asian_width
+        colwidth = unicodedata.east_asian_width
         for i in xrange(len(ucstr)):
-            l += w(ucstr[i]) in 'WFA' and 2 or 1
+            l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
             if space_left < l:
                 return (ucstr[:i].encode(encoding.encoding),
                         ucstr[i:].encode(encoding.encoding))
--- a/mercurial/verify.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/verify.py	Mon Dec 13 12:19:26 2010 -0600
@@ -34,7 +34,7 @@
         raise util.Abort(_("cannot verify bundle or remote repos"))
 
     def err(linkrev, msg, filename=None):
-        if linkrev != None:
+        if linkrev is not None:
             badrevs.add(linkrev)
         else:
             linkrev = '?'
--- a/mercurial/wireproto.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/mercurial/wireproto.py	Mon Dec 13 12:19:26 2010 -0600
@@ -25,7 +25,7 @@
 class wirerepository(repo.repository):
     def lookup(self, key):
         self.requirecap('lookup', _('look up remote revision'))
-        d = self._call("lookup", key=key)
+        d = self._call("lookup", key=encoding.fromlocal(key))
         success, data = d[:-1].split(" ", 1)
         if int(success):
             return bin(data)
@@ -44,14 +44,7 @@
             branchmap = {}
             for branchpart in d.splitlines():
                 branchname, branchheads = branchpart.split(' ', 1)
-                branchname = urllib.unquote(branchname)
-                # Earlier servers (1.3.x) send branch names in (their) local
-                # charset. The best we can do is assume it's identical to our
-                # own local charset, in case it's not utf-8.
-                try:
-                    branchname.decode('utf-8')
-                except UnicodeDecodeError:
-                    branchname = encoding.fromlocal(branchname)
+                branchname = encoding.tolocal(urllib.unquote(branchname))
                 branchheads = decodelist(branchheads)
                 branchmap[branchname] = branchheads
             return branchmap
@@ -83,17 +76,20 @@
         if not self.capable('pushkey'):
             return False
         d = self._call("pushkey",
-                      namespace=namespace, key=key, old=old, new=new)
+                       namespace=encoding.fromlocal(namespace),
+                       key=encoding.fromlocal(key),
+                       old=encoding.fromlocal(old),
+                       new=encoding.fromlocal(new))
         return bool(int(d))
 
     def listkeys(self, namespace):
         if not self.capable('pushkey'):
             return {}
-        d = self._call("listkeys", namespace=namespace)
+        d = self._call("listkeys", namespace=encoding.fromlocal(namespace))
         r = {}
         for l in d.splitlines():
             k, v = l.split('\t')
-            r[k.decode('string-escape')] = v.decode('string-escape')
+            r[encoding.tolocal(k)] = encoding.tolocal(v)
         return r
 
     def stream_out(self):
@@ -162,7 +158,7 @@
     branchmap = repo.branchmap()
     heads = []
     for branch, nodes in branchmap.iteritems():
-        branchname = urllib.quote(branch)
+        branchname = urllib.quote(encoding.fromlocal(branch))
         branchnodes = encodelist(nodes)
         heads.append('%s %s' % (branchname, branchnodes))
     return '\n'.join(heads)
@@ -213,14 +209,14 @@
     return "capabilities: %s\n" % (capabilities(repo, proto))
 
 def listkeys(repo, proto, namespace):
-    d = pushkeymod.list(repo, namespace).items()
-    t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
-                               v.encode('string-escape')) for k, v in d])
+    d = pushkeymod.list(repo, encoding.tolocal(namespace)).items()
+    t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
+                   for k, v in d])
     return t
 
 def lookup(repo, proto, key):
     try:
-        r = hex(repo.lookup(key))
+        r = hex(repo.lookup(encoding.tolocal(key)))
         success = 1
     except Exception, inst:
         r = str(inst)
@@ -228,7 +224,21 @@
     return "%s %s\n" % (success, r)
 
 def pushkey(repo, proto, namespace, key, old, new):
-    r = pushkeymod.push(repo, namespace, key, old, new)
+    # compatibility with pre-1.8 clients which were accidentally
+    # sending raw binary nodes rather than utf-8-encoded hex
+    if len(new) == 20 and new.encode('string-escape') != new:
+        # looks like it could be a binary node
+        try:
+            u = new.decode('utf-8')
+            new = encoding.tolocal(new) # but cleanly decodes as UTF-8
+        except UnicodeDecodeError:
+            pass # binary, leave unmodified
+    else:
+        new = encoding.tolocal(new) # normal path
+
+    r = pushkeymod.push(repo,
+                        encoding.tolocal(namespace), encoding.tolocal(key),
+                        encoding.tolocal(old), new)
     return '%s\n' % int(r)
 
 def _allowstream(ui):
--- a/tests/run-tests.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/run-tests.py	Mon Dec 13 12:19:26 2010 -0600
@@ -504,7 +504,8 @@
         vlog("# Running", cmd)
         exitcode, output = run(cmd, options, replacements)
         # do not merge output if skipped, return hghave message instead
-        if exitcode == SKIPPED_STATUS:
+        # similarly, with --debug, output is None
+        if exitcode == SKIPPED_STATUS or output is None:
             return exitcode, output
     finally:
         os.remove(name)
@@ -593,7 +594,7 @@
         tochild.close()
         output = fromchild.read()
         ret = fromchild.close()
-        if ret == None:
+        if ret is None:
             ret = 0
     else:
         proc = Popen4(cmd)
@@ -713,7 +714,7 @@
     # If we're not in --debug mode and reference output file exists,
     # check test output against it.
     if options.debug:
-        refout = None                   # to match out == None
+        refout = None                   # to match "out is None"
     elif os.path.exists(ref):
         f = open(ref, "r")
         refout = splitnewlines(f.read())
--- a/tests/test-acl.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-acl.t	Mon Dec 13 12:19:26 2010 -0600
@@ -90,38 +90,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -166,38 +166,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -245,38 +245,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -333,38 +333,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -420,38 +420,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -512,38 +512,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -601,38 +601,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -695,38 +695,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -786,38 +786,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -879,38 +879,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -974,38 +974,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -1074,38 +1074,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -1168,38 +1168,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -1274,38 +1274,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -1370,38 +1370,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -1462,38 +1462,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -1558,38 +1558,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
@@ -1651,38 +1651,38 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling changes: 7 chunks
-  bundling changes: 8 chunks
-  bundling changes: 9 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling manifests: 7 chunks
-  bundling manifests: 8 chunks
-  bundling manifests: 9 chunks
-  bundling files: foo/Bar/file.txt 0 chunks
-  bundling files: foo/Bar/file.txt 1 chunks
-  bundling files: foo/Bar/file.txt 2 chunks
-  bundling files: foo/Bar/file.txt 3 chunks
-  bundling files: foo/file.txt 4 chunks
-  bundling files: foo/file.txt 5 chunks
-  bundling files: foo/file.txt 6 chunks
-  bundling files: foo/file.txt 7 chunks
-  bundling files: quux/file.py 8 chunks
-  bundling files: quux/file.py 9 chunks
-  bundling files: quux/file.py 10 chunks
-  bundling files: quux/file.py 11 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 2 changesets
+  bundling: 3 changesets
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 0/3 manifests (0.00%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 1/3 manifests (33.33%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 2/3 manifests (66.67%)
+  bundling: 3/3 manifests (100.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/Bar/file.txt 0/3 files (0.00%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: foo/file.txt 1/3 files (33.33%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
+  bundling: quux/file.py 2/3 files (66.67%)
   changesets: 1 chunks
   add changeset ef1ea85a6374
   changesets: 2 chunks
--- a/tests/test-bundle.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-bundle.t	Mon Dec 13 12:19:26 2010 -0600
@@ -543,26 +543,26 @@
   list of changesets:
   d2ae7f538514cd87c17547b0de4cea71fe1af9fb
   5ece8e77363e2b5269e27c66828b72da29e4341a
-  bundling changes: 0 chunks
-  bundling changes: 1 chunks
-  bundling changes: 2 chunks
-  bundling changes: 3 chunks
-  bundling changes: 4 chunks
-  bundling changes: 5 chunks
-  bundling changes: 6 chunks
-  bundling manifests: 0 chunks
-  bundling manifests: 1 chunks
-  bundling manifests: 2 chunks
-  bundling manifests: 3 chunks
-  bundling manifests: 4 chunks
-  bundling manifests: 5 chunks
-  bundling manifests: 6 chunks
-  bundling files: b 0 chunks
-  bundling files: b 1 chunks
-  bundling files: b 2 chunks
-  bundling files: b 3 chunks
-  bundling files: b1 4 chunks
-  bundling files: b1 5 chunks
-  bundling files: b1 6 chunks
-  bundling files: b1 7 chunks
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 0 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 1 changesets
+  bundling: 2 changesets
+  bundling: 0/2 manifests (0.00%)
+  bundling: 0/2 manifests (0.00%)
+  bundling: 0/2 manifests (0.00%)
+  bundling: 1/2 manifests (50.00%)
+  bundling: 1/2 manifests (50.00%)
+  bundling: 1/2 manifests (50.00%)
+  bundling: 2/2 manifests (100.00%)
+  bundling: b 0/2 files (0.00%)
+  bundling: b 0/2 files (0.00%)
+  bundling: b 0/2 files (0.00%)
+  bundling: b 0/2 files (0.00%)
+  bundling: b1 1/2 files (50.00%)
+  bundling: b1 1/2 files (50.00%)
+  bundling: b1 1/2 files (50.00%)
+  bundling: b1 1/2 files (50.00%)
 
--- a/tests/test-check-code.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-check-code.t	Mon Dec 13 12:19:26 2010 -0600
@@ -34,7 +34,7 @@
    gratuitous whitespace in () or []
   ./wrong.py:2:
    >     del(arg2)
-   del isn't a function
+   Python keyword is not a function
   ./wrong.py:3:
    >     return ( 5+6, 9)
    missing whitespace in expression
@@ -52,3 +52,44 @@
    >     y = format(x)
    any/all/format not available in Python 2.4
   [1]
+
+  $ cat > is-op.py <<EOF
+  > # is-operator comparing number or string literal
+  > x = None
+  > y = x is 'foo'
+  > y = x is "foo"
+  > y = x is 5346
+  > y = x is -6
+  > y = x is not 'foo'
+  > y = x is not "foo"
+  > y = x is not 5346
+  > y = x is not -6
+  > EOF
+
+  $ "$check_code" ./is-op.py
+  ./is-op.py:3:
+   > y = x is 'foo'
+   object comparison with literal
+  ./is-op.py:4:
+   > y = x is "foo"
+   object comparison with literal
+  ./is-op.py:5:
+   > y = x is 5346
+   object comparison with literal
+  ./is-op.py:6:
+   > y = x is -6
+   object comparison with literal
+  ./is-op.py:7:
+   > y = x is not 'foo'
+   object comparison with literal
+  ./is-op.py:8:
+   > y = x is not "foo"
+   object comparison with literal
+  ./is-op.py:9:
+   > y = x is not 5346
+   object comparison with literal
+  ./is-op.py:10:
+   > y = x is not -6
+   object comparison with literal
+  [1]
+
--- a/tests/test-clone-failure.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-clone-failure.t	Mon Dec 13 12:19:26 2010 -0600
@@ -39,7 +39,6 @@
   >     rm a
   > else
   >     echo "abort: repository a not found!"
-  >     echo 255
   > fi
   abort: repository a not found!
 
--- a/tests/test-confused-revert.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-confused-revert.t	Mon Dec 13 12:19:26 2010 -0600
@@ -58,8 +58,8 @@
 
 Revert should fail:
 
-  $ hg revert --all
-  abort: uncommitted merge - please provide a specific revision
+  $ hg revert
+  abort: uncommitted merge - use "hg update", see "hg help revert"
   [255]
 
 Revert should be ok now:
--- a/tests/test-convert.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-convert.t	Mon Dec 13 12:19:26 2010 -0600
@@ -40,16 +40,16 @@
       have the following effects:
   
       --branchsort  convert from parent to child revision when possible, which
-                    means branches are usually converted one after the other. It
-                    generates more compact repositories.
+                    means branches are usually converted one after the other.
+                    It generates more compact repositories.
       --datesort    sort revisions by date. Converted repositories have good-
                     looking changelogs but are often an order of magnitude
                     larger than the same ones generated by --branchsort.
       --sourcesort  try to preserve source revisions order, only supported by
                     Mercurial sources.
   
-      If <REVMAP> isn't given, it will be put in a default location
-      (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file that
+      If "REVMAP" isn't given, it will be put in a default location
+      ("<dest>/.hg/shamap" by default). The "REVMAP" is a simple text file that
       maps each source commit ID to the destination ID for that revision, like
       so:
   
@@ -123,16 +123,19 @@
       Mercurial Source
       ''''''''''''''''
   
-      --config convert.hg.ignoreerrors=False    (boolean)
-          ignore integrity errors when reading. Use it to fix Mercurial
-          repositories with missing revlogs, by converting from and to
-          Mercurial.
+      The Mercurial source recognizes the following configuration options, which
+      you can set on the command line with "--config":
   
-      --config convert.hg.saverev=False         (boolean)
-          store original revision ID in changeset (forces target IDs to change)
-  
-      --config convert.hg.startrev=0            (hg revision identifier)
-          convert start revision and its descendants
+      convert.hg.ignoreerrors
+                  ignore integrity errors when reading. Use it to fix Mercurial
+                  repositories with missing revlogs, by converting from and to
+                  Mercurial. Default is False.
+      convert.hg.saverev
+                  store original. revision ID in changeset (forces target IDs to
+                  change). It takes and boolean argument and defaults to False.
+      convert.hg.startrev
+                  convert start revision and its descendants. It takes a hg
+                  revision identifier and defaults to 0.
   
       CVS Source
       ''''''''''
@@ -140,46 +143,45 @@
       CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
       indicate the starting point of what will be converted. Direct access to
       the repository files is not needed, unless of course the repository is
-      :local:. The conversion uses the top level directory in the sandbox to
+      ":local:". The conversion uses the top level directory in the sandbox to
       find the CVS repository, and then uses CVS rlog commands to find files to
       convert. This means that unless a filemap is given, all files under the
       starting directory will be converted, and that any directory
       reorganization in the CVS sandbox is ignored.
   
-      The options shown are the defaults.
-  
-      --config convert.cvsps.cache=True         (boolean)
-          Set to False to disable remote log caching, for testing and debugging
-          purposes.
-  
-      --config convert.cvsps.fuzz=60            (integer)
-          Specify the maximum time (in seconds) that is allowed between commits
-          with identical user and log message in a single changeset. When very
-          large files were checked in as part of a changeset then the default
-          may not be long enough.
+      The following options can be used with "--config":
   
-      --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
-          Specify a regular expression to which commit log messages are matched.
-          If a match occurs, then the conversion process will insert a dummy
-          revision merging the branch on which this log message occurs to the
-          branch indicated in the regex.
-  
-      --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
-          Specify a regular expression to which commit log messages are matched.
-          If a match occurs, then the conversion process will add the most
-          recent revision on the branch indicated in the regex as the second
-          parent of the changeset.
-  
-      --config hook.cvslog
-          Specify a Python function to be called at the end of gathering the CVS
-          log. The function is passed a list with the log entries, and can
-          modify the entries in-place, or add or delete them.
-  
-      --config hook.cvschangesets
-          Specify a Python function to be called after the changesets are
-          calculated from the the CVS log. The function is passed a list with
-          the changeset entries, and can modify the changesets in-place, or add
-          or delete them.
+      convert.cvsps.cache
+                  Set to False to disable remote log caching, for testing and
+                  debugging purposes. Default is True.
+      convert.cvsps.fuzz
+                  Specify the maximum time (in seconds) that is allowed between
+                  commits with identical user and log message in a single
+                  changeset. When very large files were checked in as part of a
+                  changeset then the default may not be long enough. The default
+                  is 60.
+      convert.cvsps.mergeto
+                  Specify a regular expression to which commit log messages are
+                  matched. If a match occurs, then the conversion process will
+                  insert a dummy revision merging the branch on which this log
+                  message occurs to the branch indicated in the regex. Default
+                  is "{{mergetobranch ([-\w]+)}}"
+      convert.cvsps.mergefrom
+                  Specify a regular expression to which commit log messages are
+                  matched. If a match occurs, then the conversion process will
+                  add the most recent revision on the branch indicated in the
+                  regex as the second parent of the changeset. Default is
+                  "{{mergefrombranch ([-\w]+)}}"
+      hook.cvslog
+                  Specify a Python function to be called at the end of gathering
+                  the CVS log. The function is passed a list with the log
+                  entries, and can modify the entries in-place, or add or delete
+                  them.
+      hook.cvschangesets
+                  Specify a Python function to be called after the changesets
+                  are calculated from the the CVS log. The function is passed a
+                  list with the changeset entries, and can modify the changesets
+                  in-place, or add or delete them.
   
       An additional "debugcvsps" Mercurial command allows the builtin changeset
       merging code to be run without doing a conversion. Its parameters and
@@ -199,21 +201,22 @@
       them to paths relative to the source URL, or leave them blank to disable
       auto detection.
   
-      --config convert.svn.branches=branches    (directory name)
-          specify the directory containing branches
+      The following options can be set with "--config":
   
-      --config convert.svn.tags=tags            (directory name)
-          specify the directory containing tags
-  
-      --config convert.svn.trunk=trunk          (directory name)
-          specify the name of the trunk branch
+      convert.svn.branches
+                  specify the directory containing branches. The defaults is
+                  "branches".
+      convert.svn.tags
+                  specify the directory containing tags. The default is "tags".
+      convert.svn.trunk
+                  specify the name of the trunk branch The defauls is "trunk".
   
       Source history can be retrieved starting at a specific revision, instead
       of being integrally converted. Only single branch conversions are
       supported.
   
-      --config convert.svn.startrev=0           (svn revision number)
-          specify start Subversion revision.
+      convert.svn.startrev
+                  specify start Subversion revision number. The default is 0.
   
       Perforce Source
       '''''''''''''''
@@ -222,25 +225,27 @@
       specification as source. It will convert all files in the source to a flat
       Mercurial repository, ignoring labels, branches and integrations. Note
       that when a depot path is given you then usually should specify a target
-      directory, because otherwise the target may be named ...-hg.
+      directory, because otherwise the target may be named "...-hg".
   
       It is possible to limit the amount of source history to be converted by
-      specifying an initial Perforce revision.
+      specifying an initial Perforce revision:
   
-      --config convert.p4.startrev=0            (perforce changelist number)
-          specify initial Perforce revision.
+      convert.p4.startrev
+                  specify initial Perforce revision, a Perforce changelist
+                  number).
   
       Mercurial Destination
       '''''''''''''''''''''
   
-      --config convert.hg.clonebranches=False   (boolean)
-          dispatch source branches in separate clones.
+      The following options are supported:
   
-      --config convert.hg.tagsbranch=default    (branch name)
-          tag revisions branch name
-  
-      --config convert.hg.usebranchnames=True   (boolean)
-          preserve branch names
+      convert.hg.clonebranches
+                  dispatch source branches in separate clones. The default is
+                  False.
+      convert.hg.tagsbranch
+                  branch name for tag revisions, defaults to "default".
+      convert.hg.usebranchnames
+                  preserve branch names. The default is True
   
   options:
   
--- a/tests/test-demandimport.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-demandimport.py	Mon Dec 13 12:19:26 2010 -0600
@@ -8,6 +8,7 @@
     l = repr(obj)
     l = rsub("0x[0-9a-fA-F]+", "0x?", l)
     l = rsub("from '.*'", "from '?'", l)
+    l = rsub("'<[a-z]*>'", "'<whatever>'", l)
     return l
 
 import os
--- a/tests/test-demandimport.py.out	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-demandimport.py.out	Mon Dec 13 12:19:26 2010 -0600
@@ -11,5 +11,5 @@
 fred.sub = <function sub at 0x?>
 fred = <proxied module 're'>
 re = <unloaded module 'sys'>
-re.stderr = <open file '<stderr>', mode 'w' at 0x?>
+re.stderr = <open file '<whatever>', mode 'w' at 0x?>
 re = <proxied module 'sys'>
--- a/tests/test-doctest.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-doctest.py	Mon Dec 13 12:19:26 2010 -0600
@@ -13,8 +13,8 @@
 import mercurial.match
 doctest.testmod(mercurial.match)
 
-import mercurial.url
-doctest.testmod(mercurial.url)
+import mercurial.encoding
+doctest.testmod(mercurial.encoding)
 
 import hgext.convert.cvsps
 doctest.testmod(hgext.convert.cvsps)
--- a/tests/test-encoding.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-encoding.t	Mon Dec 13 12:19:26 2010 -0600
@@ -240,6 +240,4 @@
   abort: decoding near '\xe9': 'ascii' codec can't decode byte 0xe9 in position 0: ordinal not in range(128)! (esc)
   [255]
   $ cp latin-1-tag .hg/branch
-  $ HGENCODING=latin-1 hg ci -m 'should fail'
-  abort: branch name not in UTF-8!
-  [255]
+  $ HGENCODING=latin-1 hg ci -m 'auto-promote legacy name'
--- a/tests/test-hardlinks-safety.t	Mon Dec 13 11:51:01 2010 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-some implementations of cp can't create hardlinks
-
-  $ cat > cp.py <<EOF
-  > from mercurial import util
-  > import sys
-  > util.copyfiles(sys.argv[1], sys.argv[2], hardlink=True)
-  > EOF
-
-Test hardlinking outside hg:
-
-  $ mkdir x
-  $ echo foo > x/a
-
-  $ python cp.py x y
-  $ echo bar >> y/a
-
-No diff if hardlink:
-
-  $ diff x/a y/a
-
-Test mq hardlinking:
-
-  $ echo "[extensions]" >> $HGRCPATH
-  $ echo "mq=" >> $HGRCPATH
-
-  $ hg init a
-  $ cd a
-
-  $ hg qimport -n foo - << EOF
-  > # HG changeset patch
-  > # Date 1 0
-  > diff -r 2588a8b53d66 a
-  > --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  > +++ b/a	Wed Jul 23 15:54:29 2008 +0200
-  > @@ -0,0 +1,1 @@
-  > +a
-  > EOF
-  adding foo to series file
-
-  $ hg qpush
-  applying foo
-  now at: foo
-
-  $ cd ..
-  $ python cp.py a b
-  $ cd b
-
-  $ hg qimport -n bar - << EOF
-  > # HG changeset patch
-  > # Date 2 0
-  > diff -r 2588a8b53d66 a
-  > --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  > +++ b/b	Wed Jul 23 15:54:29 2008 +0200
-  > @@ -0,0 +1,1 @@
-  > +b
-  > EOF
-  adding bar to series file
-
-  $ hg qpush
-  applying bar
-  now at: bar
-
-  $ cat .hg/patches/status
-  430ed4828a74fa4047bc816a25500f7472ab4bfe:foo
-  4e7abb4840c46a910f6d7b4d3c3fc7e5209e684c:bar
-
-  $ cat .hg/patches/series
-  foo
-  bar
-
-  $ cat ../a/.hg/patches/status
-  430ed4828a74fa4047bc816a25500f7472ab4bfe:foo
-
-  $ cat ../a/.hg/patches/series
-  foo
-
-Test tags hardlinking:
-
-  $ hg qdel -r qbase:qtip
-  patch foo finalized without changeset message
-  patch bar finalized without changeset message
-
-  $ hg tag -l lfoo
-  $ hg tag foo
-
-  $ cd ..
-  $ python cp.py b c
-  $ cd c
-
-  $ hg tag -l -r 0 lbar
-  $ hg tag -r 0 bar
-
-  $ cat .hgtags
-  4e7abb4840c46a910f6d7b4d3c3fc7e5209e684c foo
-  430ed4828a74fa4047bc816a25500f7472ab4bfe bar
-
-  $ cat .hg/localtags
-  4e7abb4840c46a910f6d7b4d3c3fc7e5209e684c lfoo
-  430ed4828a74fa4047bc816a25500f7472ab4bfe lbar
-
-  $ cat ../b/.hgtags
-  4e7abb4840c46a910f6d7b4d3c3fc7e5209e684c foo
-
-  $ cat ../b/.hg/localtags
-  4e7abb4840c46a910f6d7b4d3c3fc7e5209e684c lfoo
-
--- a/tests/test-hardlinks.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-hardlinks.t	Mon Dec 13 12:19:26 2010 -0600
@@ -10,6 +10,19 @@
   >     find $1 -type f | python $TESTTMP/nlinks.py
   > }
 
+Some implementations of cp can't create hardlinks (replaces 'cp -al' on Linux):
+
+  $ cat > linkcp.py <<EOF
+  > from mercurial import util
+  > import sys
+  > util.copyfiles(sys.argv[1], sys.argv[2], hardlink=True)
+  > EOF
+
+  $ linkcp()
+  > {
+  >     python $TESTTMP/linkcp.py $1 $2
+  > }
+
 Prepare repo r1:
 
   $ mkdir r1
@@ -152,3 +165,167 @@
   1 r2/.hg/store/data/f1.i
   1 r2/.hg/store/fncache
 
+
+  $ cd r3
+  $ hg tip --template '{rev}:{node|short}\n'
+  11:a6451b6bc41f
+  $ echo bla > f1
+  $ hg ci -m1
+  $ cd ..
+
+Create hardlinked copy r4 of r3 (on Linux, we would call 'cp -al'):
+
+  $ linkcp r3 r4
+
+r4 has hardlinks in the working dir (not just inside .hg):
+
+  $ nlinksdir r4
+  2 r4/.hg/00changelog.i
+  2 r4/.hg/branch
+  2 r4/.hg/branchheads.cache
+  2 r4/.hg/dirstate
+  2 r4/.hg/hgrc
+  2 r4/.hg/last-message.txt
+  2 r4/.hg/requires
+  2 r4/.hg/store/00changelog.i
+  2 r4/.hg/store/00manifest.i
+  2 r4/.hg/store/data/d1/f2.d
+  2 r4/.hg/store/data/d1/f2.i
+  2 r4/.hg/store/data/f1.i
+  2 r4/.hg/store/fncache
+  2 r4/.hg/store/undo
+  2 r4/.hg/tags.cache
+  2 r4/.hg/undo.branch
+  2 r4/.hg/undo.desc
+  2 r4/.hg/undo.dirstate
+  2 r4/d1/data1
+  2 r4/d1/f2
+  2 r4/f1
+
+Update back to revision 11 in r4 should break hardlink of file f1:
+
+  $ hg -R r4 up 11
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ nlinksdir r4
+  2 r4/.hg/00changelog.i
+  1 r4/.hg/branch
+  2 r4/.hg/branchheads.cache
+  1 r4/.hg/dirstate
+  2 r4/.hg/hgrc
+  2 r4/.hg/last-message.txt
+  2 r4/.hg/requires
+  2 r4/.hg/store/00changelog.i
+  2 r4/.hg/store/00manifest.i
+  2 r4/.hg/store/data/d1/f2.d
+  2 r4/.hg/store/data/d1/f2.i
+  2 r4/.hg/store/data/f1.i
+  2 r4/.hg/store/fncache
+  2 r4/.hg/store/undo
+  2 r4/.hg/tags.cache
+  2 r4/.hg/undo.branch
+  2 r4/.hg/undo.desc
+  2 r4/.hg/undo.dirstate
+  2 r4/d1/data1
+  2 r4/d1/f2
+  1 r4/f1
+
+
+Test hardlinking outside hg:
+
+  $ mkdir x
+  $ echo foo > x/a
+
+  $ linkcp x y
+  $ echo bar >> y/a
+
+No diff if hardlink:
+
+  $ diff x/a y/a
+
+Test mq hardlinking:
+
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "mq=" >> $HGRCPATH
+
+  $ hg init a
+  $ cd a
+
+  $ hg qimport -n foo - << EOF
+  > # HG changeset patch
+  > # Date 1 0
+  > diff -r 2588a8b53d66 a
+  > --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  > +++ b/a	Wed Jul 23 15:54:29 2008 +0200
+  > @@ -0,0 +1,1 @@
+  > +a
+  > EOF
+  adding foo to series file
+
+  $ hg qpush
+  applying foo
+  now at: foo
+
+  $ cd ..
+  $ linkcp a b
+  $ cd b
+
+  $ hg qimport -n bar - << EOF
+  > # HG changeset patch
+  > # Date 2 0
+  > diff -r 2588a8b53d66 a
+  > --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  > +++ b/b	Wed Jul 23 15:54:29 2008 +0200
+  > @@ -0,0 +1,1 @@
+  > +b
+  > EOF
+  adding bar to series file
+
+  $ hg qpush
+  applying bar
+  now at: bar
+
+  $ cat .hg/patches/status
+  430ed4828a74fa4047bc816a25500f7472ab4bfe:foo
+  4e7abb4840c46a910f6d7b4d3c3fc7e5209e684c:bar
+
+  $ cat .hg/patches/series
+  foo
+  bar
+
+  $ cat ../a/.hg/patches/status
+  430ed4828a74fa4047bc816a25500f7472ab4bfe:foo
+
+  $ cat ../a/.hg/patches/series
+  foo
+
+Test tags hardlinking:
+
+  $ hg qdel -r qbase:qtip
+  patch foo finalized without changeset message
+  patch bar finalized without changeset message
+
+  $ hg tag -l lfoo
+  $ hg tag foo
+
+  $ cd ..
+  $ linkcp b c
+  $ cd c
+
+  $ hg tag -l -r 0 lbar
+  $ hg tag -r 0 bar
+
+  $ cat .hgtags
+  4e7abb4840c46a910f6d7b4d3c3fc7e5209e684c foo
+  430ed4828a74fa4047bc816a25500f7472ab4bfe bar
+
+  $ cat .hg/localtags
+  4e7abb4840c46a910f6d7b4d3c3fc7e5209e684c lfoo
+  430ed4828a74fa4047bc816a25500f7472ab4bfe lbar
+
+  $ cat ../b/.hgtags
+  4e7abb4840c46a910f6d7b4d3c3fc7e5209e684c foo
+
+  $ cat ../b/.hg/localtags
+  4e7abb4840c46a910f6d7b4d3c3fc7e5209e684c lfoo
+
--- a/tests/test-hook.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-hook.t	Mon Dec 13 12:19:26 2010 -0600
@@ -73,8 +73,8 @@
   [1]
   $ hg cat b
   pre-cat hook: HG_ARGS=cat b HG_OPTS={'rev': '', 'decode': None, 'exclude': [], 'output': '', 'include': []} HG_PATS=['b'] 
+  b
   post-cat hook: HG_ARGS=cat b HG_OPTS={'rev': '', 'decode': None, 'exclude': [], 'output': '', 'include': []} HG_PATS=['b'] HG_RESULT=0 
-  b
 
   $ cd ../b
   $ hg pull ../a
--- a/tests/test-import.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-import.t	Mon Dec 13 12:19:26 2010 -0600
@@ -437,6 +437,13 @@
   $ hg revert -a
   reverting a
 
+
+import with --no-commit should have written .hg/last-message.txt
+
+  $ cat .hg/last-message.txt
+  change (no-eol)
+
+
 test fuzziness with eol=auto
 
   $ hg --config patch.eol=auto import --no-commit -v tip.patch
--- a/tests/test-minirst.py	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-minirst.py	Mon Dec 13 12:19:26 2010 -0600
@@ -120,16 +120,19 @@
 There is support for simple option lists,
 but only with long options:
 
---all      Output all.
---both     Output both (this description is
-           quite long).
---long     Output all day long.
+-X, --exclude  filter  an option with a short and long option with an argument
+-I, --include          an option with both a short option and a long option
+--all                  Output all.
+--both                 Output both (this description is
+                       quite long).
+--long                 Output all day long.
 
---par      This option has two paragraphs in its description.
-           This is the first.
+--par                 This option has two paragraphs in its description.
+                      This is the first.
 
-           This is the second.  Blank lines may be omitted between
-           options (as above) or left in (as here).
+                      This is the second.  Blank lines may be omitted between
+                      options (as above) or left in (as here).
+
 
 The next paragraph looks like an option list, but lacks the two-space
 marker after the option. It is treated as a normal paragraph:
@@ -221,6 +224,10 @@
    .. An indented comment
 
    Some indented text.
+
+..
+
+Empty comment above
 """
 
 debugformat('comments', comments, 30)
--- a/tests/test-minirst.py.out	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-minirst.py.out	Mon Dec 13 12:19:26 2010 -0600
@@ -180,14 +180,20 @@
 There is support for simple option lists, but only with long
 options:
 
---all      Output all.
---both     Output both (this description is quite long).
---long     Output all day long.
---par      This option has two paragraphs in its
-           description. This is the first.
+ -X --exclude filter  an option with a short and long option
+                      with an argument
+ -I --include         an option with both a short option and
+                      a long option
+    --all             Output all.
+    --both            Output both (this description is quite
+                      long).
+    --long            Output all day long.
+    --par             This option has two paragraphs in its
+                      description. This is the first.
 
-           This is the second.  Blank lines may be omitted
-           between options (as above) or left in (as here).
+                      This is the second.  Blank lines may
+                      be omitted between options (as above)
+                      or left in (as here).
 
 The next paragraph looks like an option list, but lacks the
 two-space marker after the option. It is treated as a normal
@@ -202,23 +208,62 @@
 option lists, but only with
 long options:
 
---all      Output all.
---both     Output both (this
-           description is
-           quite long).
---long     Output all day
-           long.
---par      This option has two
-           paragraphs in its
-           description. This
-           is the first.
+ -X --exclude filter  an
+                      option
+                      with a
+                      short
+                      and
+                      long
+                      option
+                      with an
+                      argumen
+                      t
+ -I --include         an
+                      option
+                      with
+                      both a
+                      short
+                      option
+                      and a
+                      long
+                      option
+    --all             Output
+                      all.
+    --both            Output
+                      both
+                      (this d
+                      escript
+                      ion is
+                      quite
+                      long).
+    --long            Output
+                      all day
+                      long.
+    --par             This
+                      option
+                      has two
+                      paragra
+                      phs in
+                      its des
+                      criptio
+                      n. This
+                      is the
+                      first.
 
-           This is the second.
-           Blank lines may be
-           omitted between
-           options (as above)
-           or left in (as
-           here).
+                      This is
+                      the
+                      second.
+                      Blank
+                      lines
+                      may be
+                      omitted
+                      between
+                      options
+                      (as
+                      above)
+                      or left
+                      in (as
+                      here).
 
 The next paragraph looks like
 an option list, but lacks the
@@ -339,5 +384,7 @@
 Some text.
 
    Some indented text.
+
+Empty comment above
 ----------------------------------------------------------------------
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-mq-qpush-exact.t	Mon Dec 13 12:19:26 2010 -0600
@@ -0,0 +1,290 @@
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "mq=" >> $HGRCPATH
+  $ echo "graphlog=" >> $HGRCPATH
+
+make a test repository that looks like this:
+
+o    2:28bc7b1afd6a
+|
+| @  1:d7fe2034f71b
+|/
+o    0/62ecad8b70e5
+
+  $ hg init r0
+  $ cd r0
+  $ touch f0
+  $ hg ci -m0 -Aq
+  $ touch f1
+  $ hg ci -m1 -Aq
+
+  $ hg update 0 -q
+  $ touch f2
+  $ hg ci -m2 -Aq
+  $ hg update 1 -q
+
+make some patches with a parent: 1:d7fe2034f71b -> p0 -> p1
+
+  $ echo cp0 >> fp0
+  $ hg add fp0
+  $ hg qnew p0 -d "0 0"
+
+  $ echo cp1 >> fp1
+  $ hg add fp1
+  $ hg qnew p1 -d "0 0"
+
+  $ hg qpop -aq
+  patch queue now empty
+
+qpush --exact when at the parent
+
+  $ hg update 1 -q
+  $ hg qpush -e
+  applying p0
+  now at: p0
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+  $ hg qpush -e p0
+  applying p0
+  now at: p0
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+  $ hg qpush -e p1
+  applying p0
+  applying p1
+  now at: p1
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+  $ hg qpush -ea
+  applying p0
+  applying p1
+  now at: p1
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+qpush --exact when at another rev
+
+  $ hg update 0 -q
+  $ hg qpush -e
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  applying p0
+  now at: p0
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+  $ hg update 0 -q
+  $ hg qpush -e p0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  applying p0
+  now at: p0
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+  $ hg update 0 -q
+  $ hg qpush -e p1
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  applying p0
+  applying p1
+  now at: p1
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+  $ hg update 0 -q
+  $ hg qpush -ea
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  applying p0
+  applying p1
+  now at: p1
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+qpush --exact while crossing branches
+
+  $ hg update 2 -q
+  $ hg qpush -e
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  applying p0
+  now at: p0
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+  $ hg update 2 -q
+  $ hg qpush -e p0
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  applying p0
+  now at: p0
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+  $ hg update 2 -q
+  $ hg qpush -e p1
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  applying p0
+  applying p1
+  now at: p1
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+  $ hg update 2 -q
+  $ hg qpush -ea
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  applying p0
+  applying p1
+  now at: p1
+  $ hg parents -qr qbase
+  1:d7fe2034f71b
+  $ hg qpop -aq
+  patch queue now empty
+
+qpush --exact --force with changes to an unpatched file
+
+  $ hg update 1 -q
+  $ echo c0 >> f0
+  $ hg qpush -e
+  abort: local changes found, refresh first
+  [255]
+  $ hg qpush -ef
+  applying p0
+  now at: p0
+  $ cat f0
+  c0
+  $ rm f0
+  $ touch f0
+  $ hg qpop -aq
+  patch queue now empty
+
+  $ hg update 1 -q
+  $ echo c0 >> f0
+  $ hg qpush -e p1
+  abort: local changes found, refresh first
+  [255]
+  $ hg qpush -e p1 -f
+  applying p0
+  applying p1
+  now at: p1
+  $ cat f0
+  c0
+  $ rm f0
+  $ touch f0
+  $ hg qpop -aq
+  patch queue now empty
+
+qpush --exact --force with changes to a patched file
+
+  $ hg update 1 -q
+  $ echo cp0-bad >> fp0
+  $ hg add fp0
+  $ hg qpush -e
+  abort: local changes found, refresh first
+  [255]
+  $ hg qpush -ef
+  applying p0
+  file fp0 already exists
+  1 out of 1 hunks FAILED -- saving rejects to file fp0.rej
+  patch failed, unable to continue (try -v)
+  patch failed, rejects left in working dir
+  errors during apply, please fix and refresh p0
+  [2]
+  $ cat fp0
+  cp0-bad
+  $ cat fp0.rej
+  --- fp0
+  +++ fp0
+  @@ -0,0 +1,1 @@
+  +cp0
+  $ hg qpop -aqf
+  patch queue now empty
+  $ rm fp0
+  $ rm fp0.rej
+
+  $ hg update 1 -q
+  $ echo cp1-bad >> fp1
+  $ hg add fp1
+  $ hg qpush -e p1
+  abort: local changes found, refresh first
+  [255]
+  $ hg qpush -e p1 -f
+  applying p0
+  applying p1
+  file fp1 already exists
+  1 out of 1 hunks FAILED -- saving rejects to file fp1.rej
+  patch failed, unable to continue (try -v)
+  patch failed, rejects left in working dir
+  errors during apply, please fix and refresh p1
+  [2]
+  $ cat fp1
+  cp1-bad
+  $ cat fp1.rej
+  --- fp1
+  +++ fp1
+  @@ -0,0 +1,1 @@
+  +cp1
+  $ hg qpop -aqf
+  patch queue now empty
+  $ rm fp1
+  $ rm fp1.rej
+
+qpush --exact when already at a patch
+
+  $ hg update 1
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg qpush -e p0
+  applying p0
+  now at: p0
+  $ hg qpush -e p1
+  abort: cannot push --exact with applied patches
+  [255]
+  $ hg qpop -aq
+  patch queue now empty
+
+qpush --exact --move should fail
+
+  $ hg qpush -e --move p1
+  abort: cannot use --exact and --move together
+  [255]
+
+qpush --exact a patch without a parent recorded
+
+  $ hg qpush -q
+  now at: p0
+  $ grep -v '# Parent' .hg/patches/p0 > p0.new
+  $ mv p0.new .hg/patches/p0
+  $ hg qpop -aq
+  patch queue now empty
+  $ hg qpush -e
+  abort: p0 does not have a parent recorded
+  [255]
+  $ hg qpush -e p0
+  abort: p0 does not have a parent recorded
+  [255]
+  $ hg qpush -e p1
+  abort: p0 does not have a parent recorded
+  [255]
+  $ hg qpush -ea
+  abort: p0 does not have a parent recorded
+  [255]
+
--- a/tests/test-no-symlinks	Mon Dec 13 11:51:01 2010 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-#!/bin/sh
-
-"$TESTDIR/hghave" no-symlink || exit 80
-
-# The following script was used to create the bundle:
-#
-# hg init symlinks
-# cd symlinks
-# echo a > a
-# mkdir d
-# echo b > d/b
-# ln -s a a.lnk
-# ln -s d/b d/b.lnk
-# hg ci -Am t
-# hg bundle --base null ../test-no-symlinks.hg
-
-# Extract a symlink on a platform not supporting them
-echo % unbundle
-hg init t
-cd t
-hg pull -q "$TESTDIR/test-no-symlinks.hg"
-hg update
-
-cat a.lnk && echo
-cat d/b.lnk && echo
-
-# Copy a symlink and move another
-echo % move and copy
-hg copy a.lnk d/a2.lnk
-hg mv d/b.lnk b2.lnk
-hg ci -Am copy
-cat d/a2.lnk && echo
-cat b2.lnk && echo
-
-# Bundle and extract again
-echo % bundle
-hg bundle --base null ../symlinks.hg
-cd ..
-
-hg init t2
-cd t2
-hg pull ../symlinks.hg
-hg update
-
-cat a.lnk && echo
-cat d/a2.lnk && echo
-cat b2.lnk && echo
--- a/tests/test-no-symlinks.out	Mon Dec 13 11:51:01 2010 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-% unbundle
-4 files updated, 0 files merged, 0 files removed, 0 files unresolved
-a
-d/b
-% move and copy
-a
-d/b
-% bundle
-2 changesets found
-pulling from ../symlinks.hg
-requesting all changes
-adding changesets
-adding manifests
-adding file changes
-added 2 changesets with 6 changes to 6 files
-(run 'hg update' to get a working copy)
-5 files updated, 0 files merged, 0 files removed, 0 files unresolved
-a
-a
-d/b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-no-symlinks.t	Mon Dec 13 12:19:26 2010 -0600
@@ -0,0 +1,59 @@
+  $ "$TESTDIR/hghave" no-symlink || exit 80
+
+# The following script was used to create the bundle:
+#
+# hg init symlinks
+# cd symlinks
+# echo a > a
+# mkdir d
+# echo b > d/b
+# ln -s a a.lnk
+# ln -s d/b d/b.lnk
+# hg ci -Am t
+# hg bundle --base null ../test-no-symlinks.hg
+
+Extract a symlink on a platform not supporting them
+
+  $ hg init t
+  $ cd t
+  $ hg pull -q "$TESTDIR/test-no-symlinks.hg"
+  $ hg update
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat a.lnk && echo
+  a
+  $ cat d/b.lnk && echo
+  d/b
+
+Copy a symlink and move another
+
+  $ hg copy a.lnk d/a2.lnk
+  $ hg mv d/b.lnk b2.lnk
+  $ hg ci -Am copy
+  $ cat d/a2.lnk && echo
+  a
+  $ cat b2.lnk && echo
+  d/b
+
+Bundle and extract again
+
+  $ hg bundle --base null ../symlinks.hg
+  2 changesets found
+  $ cd ..
+  $ hg init t2
+  $ cd t2
+  $ hg pull ../symlinks.hg
+  pulling from ../symlinks.hg
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 6 changes to 6 files
+  (run 'hg update' to get a working copy)
+  $ hg update
+  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat a.lnk && echo
+  a
+  $ cat d/a2.lnk && echo
+  a
+  $ cat b2.lnk && echo
+  d/b
--- a/tests/test-parentrevspec.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-parentrevspec.t	Mon Dec 13 12:19:26 2010 -0600
@@ -69,12 +69,12 @@
   6^^^^^: 0
   6^^^^^^: -1
   6^1: 5
-  6^2: abort: unknown revision '6^2'!
+  6^2: hg: parse error at 1: syntax error
   6^^2: 4
   6^1^2: 4
-  6^^3: abort: unknown revision '6^^3'!
+  6^^3: hg: parse error at 1: syntax error
   $ lookup "6~" "6~1" "6~2" "6~3" "6~4" "6~5" "6~42" "6~1^2" "6~1^2~2"
-  6~: abort: unknown revision '6~'!
+  6~: hg: parse error at 1: syntax error
   6~1: 5
   6~2: 3
   6~3: 2
@@ -102,4 +102,4 @@
   $ hg tag -l -r 2 "foo^bar"
   $ lookup "foo^bar" "foo^bar^"
   foo^bar: 2
-  foo^bar^: abort: unknown revision 'foo^bar^'!
+  foo^bar^: hg: parse error at 3: syntax error
--- a/tests/test-push-warn.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-push-warn.t	Mon Dec 13 12:19:26 2010 -0600
@@ -30,6 +30,23 @@
   (you should pull and merge or use push -f to force)
   [255]
 
+  $ hg push --debug ../a
+  pushing to ../a
+  searching for changes
+  examining 1c9246a22a0a:d8d565842d04
+  found incomplete branch 1c9246a22a0a:d8d565842d04
+  searching: 1 queries
+  narrowing 1:1 d8d565842d04
+  found new branch changeset 1c9246a22a0a
+  found new changesets starting at 1c9246a22a0a
+  1 total queries
+  common changesets up to d8d565842d04
+  new remote heads on branch 'default'
+  new remote head 1e108cc5548c
+  abort: push creates new remote heads on branch 'default'!
+  (you should pull and merge or use push -f to force)
+  [255]
+
   $ hg pull ../a
   pulling from ../a
   searching for changes
--- a/tests/test-record.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-record.t	Mon Dec 13 12:19:26 2010 -0600
@@ -883,7 +883,7 @@
   (branch merge, don't forget to commit)
 
   $ hg record -m'will abort'
-  abort: cannot partially commit a merge (use hg commit instead)
+  abort: cannot partially commit a merge (use "hg commit" instead)
   [255]
 
   $ hg up -C
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-revset-dirstate-parents.t	Mon Dec 13 12:19:26 2010 -0600
@@ -0,0 +1,52 @@
+  $ HGENCODING=utf-8
+  $ export HGENCODING
+
+  $ try() {
+  >   hg debugrevspec --debug $@
+  > }
+
+  $ log() {
+  >   hg log --template '{rev}\n' -r "$1"
+  > }
+
+  $ hg init repo
+  $ cd repo
+
+  $ try 'p1()'
+  ('func', ('symbol', 'p1'), None)
+  $ try 'p2()'
+  ('func', ('symbol', 'p2'), None)
+  $ try 'parents()'
+  ('func', ('symbol', 'parents'), None)
+
+null revision
+  $ log 'p1()'
+  $ log 'p2()'
+  $ log 'parents()'
+
+working dir with a single parent
+  $ echo a > a
+  $ hg ci -Aqm0
+  $ log 'p1()'
+  0
+  $ log 'tag() and p1()'
+  $ log 'p2()'
+  $ log 'parents()'
+  0
+  $ log 'tag() and parents()'
+
+merge in progress
+  $ echo b > b
+  $ hg ci -Aqm1
+  $ hg up -q 0
+  $ echo c > c
+  $ hg ci -Aqm2
+  $ hg merge -q
+  $ log 'p1()'
+  2
+  $ log 'p2()'
+  1
+  $ log 'tag() and p2()'
+  $ log 'parents()'
+  1
+  2
--- a/tests/test-ssh.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-ssh.t	Mon Dec 13 12:19:26 2010 -0600
@@ -214,7 +214,7 @@
   $ hg debugpushkey --config ui.ssh="python ../dummyssh" ssh://user@dummy/remote bookmarks
   foo	1160648e36cec0054048a7edc4110c6f84fde594
   $ hg book -f foo
-  $ hg push
+  $ hg push --traceback
   pushing to ssh://user@dummy/remote
   searching for changes
   no changes found
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-subrepo-git.t	Mon Dec 13 12:19:26 2010 -0600
@@ -0,0 +1,262 @@
+  $ "$TESTDIR/hghave" git || exit 80
+
+make git commits repeatable
+
+  $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
+  $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
+  $ GIT_AUTHOR_DATE='1234567891 +0000'; export GIT_AUTHOR_DATE
+  $ 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
+
+root hg repo
+
+  $ hg init t
+  $ cd t
+  $ echo a > a
+  $ hg add a
+  $ hg commit -m a
+  $ cd ..
+
+new external git repo
+
+  $ mkdir gitroot
+  $ cd gitroot
+  $ git init -q
+  $ echo g > g
+  $ git add g
+  $ git commit -q -m g
+
+add subrepo clone
+
+  $ cd ../t
+  $ echo 's = [git]../gitroot' > .hgsub
+  $ git clone -q ../gitroot s
+  $ hg add .hgsub
+  $ hg commit -m 'new git subrepo'
+  committing subrepository $TESTTMP/t/s
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
+
+record a new commit from upstream from a different branch
+
+  $ cd ../gitroot
+  $ git checkout -q -b testing
+  $ echo gg >> g
+  $ git commit -q -a -m gg
+
+  $ cd ../t/s
+  $ git pull -q >/dev/null 2>/dev/null
+  $ git checkout -q -b testing origin/testing >/dev/null
+
+  $ cd ..
+  $ hg commit -m 'update git subrepo'
+  committing subrepository $TESTTMP/t/s
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
+
+make $GITROOT pushable, by replacing it with a clone with nothing checked out
+
+  $ cd ..
+  $ git clone gitroot gitrootbare --bare -q
+  $ rm -rf gitroot
+  $ mv gitrootbare gitroot
+
+clone root
+
+  $ cd t
+  $ hg clone . ../tc
+  updating to branch default
+  cloning subrepo s
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd ../tc
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
+
+update to previous substate
+
+  $ hg update 1 -q
+  $ cat s/g
+  g
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
+
+clone root, make local change
+
+  $ cd ../t
+  $ hg clone . ../ta
+  updating to branch default
+  cloning subrepo s
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ cd ../ta
+  $ echo ggg >> s/g
+  $ hg commit -m ggg
+  committing subrepository $TESTTMP/ta/s
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision 79695940086840c99328513acbe35f90fcd55e57
+
+clone root separately, make different local change
+
+  $ cd ../t
+  $ hg clone . ../tb
+  updating to branch default
+  cloning subrepo s
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ cd ../tb/s
+  $ echo f > f
+  $ git add f
+  $ cd ..
+
+  $ hg commit -m f
+  committing subrepository $TESTTMP/tb/s
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision aa84837ccfbdfedcdcdeeedc309d73e6eb069edc
+
+user b push changes
+
+  $ hg push 2>/dev/null
+  pushing to $TESTTMP/t
+  pushing branch testing of subrepo s
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+user a pulls, merges, commits
+
+  $ cd ../ta
+  $ hg pull
+  pulling from $TESTTMP/t
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg merge 2>/dev/null
+  pulling subrepo s
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ cat s/f
+  f
+  $ cat s/g
+  g
+  gg
+  ggg
+  $ hg commit -m 'merge'
+  committing subrepository $TESTTMP/ta/s
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision f47b465e1bce645dbf37232a00574aa1546ca8d3
+  $ hg push 2>/dev/null
+  pushing to $TESTTMP/t
+  pushing branch testing of subrepo s
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+
+make upstream git changes
+
+  $ cd ..
+  $ git clone -q gitroot gitclone
+  $ cd gitclone
+  $ echo ff >> f
+  $ git commit -q -a -m ff
+  $ echo fff >> f
+  $ git commit -q -a -m fff
+  $ git push origin testing 2>/dev/null
+
+make and push changes to hg without updating the subrepo
+
+  $ cd ../t
+  $ hg clone . ../td
+  updating to branch default
+  cloning subrepo s
+  checking out detached HEAD in subrepo s
+  check out a git branch if you intend to make changes
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd ../td
+  $ echo aa >> a
+  $ hg commit -m aa
+  $ hg push
+  pushing to $TESTTMP/t
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+sync to upstream git, distribute changes
+
+  $ cd ../ta
+  $ hg pull -u -q
+  $ cd s
+  $ git pull -q >/dev/null 2>/dev/null
+  $ cd ..
+  $ hg commit -m 'git upstream sync'
+  committing subrepository $TESTTMP/ta/s
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
+  $ hg push -q
+
+  $ cd ../tb
+  $ hg pull -q
+  $ hg update 2>/dev/null
+  pulling subrepo s
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision 32a343883b74769118bb1d3b4b1fbf9156f4dddc
+
+update to a revision without the subrepo, keeping the local git repository
+
+  $ cd ../t
+  $ hg up 0
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ ls -a s
+  .
+  ..
+  .git
+
+  $ hg up 2
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ ls -a s
+  .
+  ..
+  .git
+  g
+
+archive subrepos
+
+  $ cd ../tc
+  $ hg pull -q
+  $ hg archive --subrepos -r 5 ../archive 2>/dev/null
+  pulling subrepo s
+  $ cd ../archive
+  $ cat s/f
+  f
+  $ cat s/g
+  g
+  gg
+  ggg
--- a/tests/test-subrepo-relative-path.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-subrepo-relative-path.t	Mon Dec 13 12:19:26 2010 -0600
@@ -67,3 +67,58 @@
    revision 863c1745b441bd97a8c4a096e87793073f4fb215
 
   $ "$TESTDIR/killdaemons.py"
+
+
+Create repo with nested relative subrepos
+
+  $ hg init r1
+  $ hg init r1/sub
+  $ echo sub = sub > r1/.hgsub
+  $ hg add --cwd r1 .hgsub
+  $ hg init r1/sub/subsub
+  $ echo subsub = subsub > r1/sub/.hgsub
+  $ hg add --cwd r1/sub .hgsub
+  $ echo c1 > r1/sub/subsub/f
+  $ hg add --cwd r1/sub/subsub f
+  $ hg ci --cwd r1 -m0
+  committing subrepository sub
+  committing subrepository sub/subsub
+
+Ensure correct relative paths are used when pulling
+
+  $ hg init r2
+  $ cd r2/
+  $ hg pull -u ../r1
+  pulling from ../r1
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 2 changes to 2 files
+  pulling subrepo sub from ../r1/sub
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 2 changes to 2 files
+  pulling subrepo sub/subsub from ../r1/sub/subsub
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd ..
+
+Verify subrepo default paths were set correctly
+
+  $ hg -R r2/sub paths
+  default = $TESTTMP/r1/sub
+  $ cat r2/sub/.hg/hgrc
+  [paths]
+  default = ../../r1/sub
+  $ hg -R r2/sub/subsub paths
+  default = $TESTTMP/r1/sub/subsub
+  $ cat r2/sub/subsub/.hg/hgrc
+  [paths]
+  default = ../../../r1/sub/subsub
--- a/tests/test-win32text.t	Mon Dec 13 11:51:01 2010 -0600
+++ b/tests/test-win32text.t	Mon Dec 13 12:19:26 2010 -0600
@@ -9,10 +9,6 @@
   >     data = data.replace('\n', '\r\n')
   >     file(path, 'wb').write(data)
   > EOF
-  $ cat > print.py <<EOF
-  > import sys
-  > print(sys.stdin.read().replace('\n', '<LF>').replace('\r', '<CR>').replace('\0', '<NUL>'))
-  > EOF
   $ echo '[hooks]' >> .hg/hgrc
   $ echo 'pretxncommit.crlf = python:hgext.win32text.forbidcrlf' >> .hg/hgrc
   $ echo 'pretxnchangegroup.crlf = python:hgext.win32text.forbidcrlf' >> .hg/hgrc
@@ -369,12 +365,13 @@
   $ python -c 'file("f4.bat", "wb").write("rem empty\x0D\x0A")'
   $ hg add f3 f4.bat
   $ hg ci -m 6
-  $ python print.py < bin
-  hello<NUL><CR><LF>
-  $ python print.py < f3
-  some<LF>text<LF>
-  $ python print.py < f4.bat
-  rem empty<CR><LF>
+  $ cat bin
+  hello\x00\r (esc)
+  $ cat f3
+  some
+  text
+  $ cat f4.bat
+  rem empty\r (esc)
   $ echo
   
   $ echo '[extensions]' >> .hg/hgrc
@@ -405,38 +402,39 @@
   tip
 
   $ rm f3 f4.bat bin
-  $ hg co -C 2>&1 | python -c 'import sys, os; sys.stdout.write(sys.stdin.read().replace(os.getcwd(), "...."))'
+  $ hg co -C
   WARNING: f4.bat already has CRLF line endings
   and does not need EOL conversion by the win32text plugin.
   Before your next commit, please reconsider your encode/decode settings in 
-  Mercurial.ini or ..../.hg/hgrc.
+  Mercurial.ini or $TESTTMP/t/.hg/hgrc.
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ python print.py < bin
-  hello<NUL><CR><LF>
-  $ python print.py < f3
-  some<CR><LF>text<CR><LF>
-  $ python print.py < f4.bat
-  rem empty<CR><LF>
+  $ cat bin
+  hello\x00\r (esc)
+  $ cat f3
+  some\r (esc)
+  text\r (esc)
+  $ cat f4.bat
+  rem empty\r (esc)
   $ echo
   
   $ python -c 'file("f5.sh", "wb").write("# empty\x0D\x0A")'
   $ hg add f5.sh
   $ hg ci -m 7
-  $ python print.py < f5.sh
-  # empty<CR><LF>
-  $ hg cat f5.sh | python print.py
-  # empty<LF>
+  $ cat f5.sh
+  # empty\r (esc)
+  $ hg cat f5.sh
+  # empty
   $ echo '% just linefeed' > linefeed
   $ hg ci -qAm 8 linefeed
-  $ python print.py < linefeed
-  % just linefeed<LF>
-  $ hg cat linefeed | python print.py
-  % just linefeed<LF>
+  $ cat linefeed
+  % just linefeed
+  $ hg cat linefeed
+  % just linefeed
   $ hg st -q
   $ hg revert -a linefeed
   no changes needed to linefeed
-  $ python print.py < linefeed
-  % just linefeed<LF>
+  $ cat linefeed
+  % just linefeed
   $ hg st -q
   $ echo modified >> linefeed
   $ hg st -q
@@ -444,5 +442,5 @@
   $ hg revert -a
   reverting linefeed
   $ hg st -q
-  $ python print.py < linefeed
-  % just linefeed<CR><LF>
+  $ cat linefeed
+  % just linefeed\r (esc)