changeset 13840:80664e245f84

merge with stable
author Martin Geisler <mg@aragost.com>
date Fri, 01 Apr 2011 12:21:59 +0200
parents 22f20d0fe2d3 (diff) 8d960240faac (current diff)
children 367805c848e5
files
diffstat 146 files changed, 4382 insertions(+), 1996 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Apr 01 12:18:37 2011 +0200
+++ b/.hgignore	Fri Apr 01 12:21:59 2011 +0200
@@ -7,6 +7,7 @@
 *.mergebackup
 *.o
 *.so
+*.dll
 *.pyd
 *.pyc
 *.pyo
--- a/contrib/check-code.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/contrib/check-code.py	Fri Apr 01 12:21:59 2011 +0200
@@ -66,6 +66,7 @@
     (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"),
+    (r'[^>]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
 ]
 
 testfilters = [
@@ -176,9 +177,10 @@
     (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
     (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
     (r'\w,\w', "missing whitespace after ,"),
-    (r'\w[+/*]\w', "missing whitespace in expression"),
+    (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
     (r'^#\s+\w', "use #foo, not # foo"),
     (r'[^\n]\Z', "no trailing newline"),
+    (r'^\s*#import\b', "use only #include in standard C code"),
 ]
 
 cfilters = [
--- a/contrib/shrink-revlog.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/contrib/shrink-revlog.py	Fri Apr 01 12:21:59 2011 +0200
@@ -102,15 +102,16 @@
 
     ui.status(_('writing revs\n'))
 
-    count = [0]
-    def progress(*args):
-        ui.progress(_('writing'), count[0], total=len(order))
-        count[0] += 1
 
     order = [r1.node(r) for r in order]
 
     # this is a bit ugly, but it works
-    lookup = lambda x: "%020d" % r1.linkrev(r1.rev(x))
+    count = [0]
+    def lookup(x):
+        count[0] += 1
+        ui.progress(_('writing'), count[0], total=len(order))
+        return "%020d" % r1.linkrev(r1.rev(x))
+
     unlookup = lambda x: int(x, 10)
 
     try:
--- a/contrib/zsh_completion	Fri Apr 01 12:18:37 2011 +0200
+++ b/contrib/zsh_completion	Fri Apr 01 12:21:59 2011 +0200
@@ -360,8 +360,8 @@
     '(--help -h)'{-h,--help}'[display help and exit]'
     '--debug[debug mode]'
     '--debugger[start debugger]'
-    '--encoding[set the charset encoding (default: UTF8)]'
-    '--encodingmode[set the charset encoding mode (default: strict)]'
+    '--encoding[set the charset encoding]'
+    '--encodingmode[set the charset encoding mode]'
     '--lsprof[print improved command execution profile]'
     '--traceback[print traceback on exception]'
     '--time[time how long the command takes]'
--- a/doc/hgrc.5.txt	Fri Apr 01 12:18:37 2011 +0200
+++ b/doc/hgrc.5.txt	Fri Apr 01 12:21:59 2011 +0200
@@ -911,9 +911,6 @@
     The conflict resolution program to use during a manual merge.
     For more information on merge tools see :hg:`help merge-tools`.
     For configuring merge tools see the merge-tools_ section.
-``patch``
-    command to use to apply patches. Look for ``gpatch`` or ``patch`` in
-    PATH if unset.
 ``quiet``
     Reduce the amount of output printed. True or False. Default is False.
 ``remotecmd``
--- a/hgext/bugzilla.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/bugzilla.py	Fri Apr 01 12:21:59 2011 +0200
@@ -1,6 +1,7 @@
 # bugzilla.py - bugzilla integration for mercurial
 #
 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+# Copyright 2011 Jim Hague <jim.hague@acm.org>
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
@@ -8,67 +9,65 @@
 '''hooks for integrating with the Bugzilla bug tracker
 
 This hook extension adds comments on bugs in Bugzilla when changesets
-that refer to bugs by Bugzilla ID are seen. The hook does not change
-bug status.
+that refer to bugs by Bugzilla ID are seen. The comment is formatted using
+the Mercurial template mechanism.
+
+The hook does not change bug status.
 
-The hook updates the Bugzilla database directly. Only Bugzilla
-installations using MySQL are supported.
+Three basic modes of access to Bugzilla are provided:
+
+1. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later.
+
+2. Check data via the Bugzilla XMLRPC interface and submit bug change
+   via email to Bugzilla email interface. Requires Bugzilla 3.4 or later.
 
-The hook relies on a Bugzilla script to send bug change notification
-emails. That script changes between Bugzilla versions; the
-'processmail' script used prior to 2.18 is replaced in 2.18 and
-subsequent versions by 'config/sendbugmail.pl'. Note that these will
-be run by Mercurial as the user pushing the change; you will need to
-ensure the Bugzilla install file permissions are set appropriately.
+2. Writing directly to the Bugzilla database. Only Bugzilla installations
+   using MySQL are supported. Requires Python MySQLdb.
 
-The extension is configured through three different configuration
-sections. These keys are recognized in the [bugzilla] section:
-
-host
-  Hostname of the MySQL server holding the Bugzilla database.
-
-db
-  Name of the Bugzilla database in MySQL. Default 'bugs'.
-
-user
-  Username to use to access MySQL server. Default 'bugs'.
+Writing directly to the database is susceptible to schema changes, and
+relies on a Bugzilla contrib script to send out bug change
+notification emails. This script runs as the user running Mercurial,
+must be run on the host with the Bugzilla install, and requires
+permission to read Bugzilla configuration details and the necessary
+MySQL user and password to have full access rights to the Bugzilla
+database. For these reasons this access mode is now considered
+deprecated, and will not be updated for new Bugzilla versions going
+forward.
 
-password
-  Password to use to access MySQL server.
-
-timeout
-  Database connection timeout (seconds). Default 5.
-
-version
-  Bugzilla version. Specify '3.0' for Bugzilla versions 3.0 and later,
-  '2.18' for Bugzilla versions from 2.18 and '2.16' for versions prior
-  to 2.18.
+Access via XMLRPC needs a Bugzilla username and password to be specified
+in the configuration. Comments are added under that username. Since the
+configuration must be readable by all Mercurial users, it is recommended
+that the rights of that user are restricted in Bugzilla to the minimum
+necessary to add comments.
 
-bzuser
-  Fallback Bugzilla user name to record comments with, if changeset
-  committer cannot be found as a Bugzilla user.
+Access via XMLRPC/email behaves uses XMLRPC to query Bugzilla, but sends
+email to the Bugzilla email interface to submit comments to bugs.
+The From: address in the email is set to the email address of the Mercurial
+user, so the comment appears to come from the Mercurial user. In the event
+that the Mercurial user email is not recognised by Bugzilla as a Bugzilla
+user, the Bugzilla username and password used to log into Bugzilla are
+used instead as the source of the comment.
 
-bzdir
-   Bugzilla install directory. Used by default notify. Default
-   '/var/www/html/bugzilla'.
+Configuration items common to all access modes:
 
-notify
-  The command to run to get Bugzilla to send bug change notification
-  emails. Substitutes from a map with 3 keys, 'bzdir', 'id' (bug id)
-  and 'user' (committer bugzilla email). Default depends on version;
-  from 2.18 it is "cd %(bzdir)s && perl -T contrib/sendbugmail.pl
-  %(id)s %(user)s".
+bugzilla.version
+  This access type to use. Values recognised are:
+  xmlrpc       Bugzilla XMLRPC interface.
+  xmlrpc+email Bugzilla XMLRPC and email interfaces.
+  3.0          MySQL access, Bugzilla 3.0 and later.
+  2.18         MySQL access, Bugzilla 2.18 and up to but not including 3.0.
+  2.16         MySQL access, Bugzilla 2.16 and up to but not including 2.18.
 
-regexp
+bugzilla.regexp
   Regular expression to match bug IDs in changeset commit message.
   Must contain one "()" group. The default expression matches 'Bug
   1234', 'Bug no. 1234', 'Bug number 1234', 'Bugs 1234,5678', 'Bug
   1234 and 5678' and variations thereof. Matching is case insensitive.
 
-style
+bugzilla.style
   The style file to use when formatting comments.
 
-template
+bugzilla.template
   Template to use when formatting comments. Overrides style if
   specified. In addition to the usual Mercurial keywords, the
   extension specifies::
@@ -81,24 +80,84 @@
   Default 'changeset {node|short} in repo {root} refers '
           'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}'
 
-strip
-  The number of slashes to strip from the front of {root} to produce
-  {webroot}. Default 0.
+bugzilla.strip
+  The number of path separator characters to strip from the front of the
+  Mercurial repository path ('{root}' in templates) to produce '{webroot}'.
+  For example, a repository with '{root}' '/var/local/my-project' with a
+  strip of 2 gives a value for '{webroot}' of 'my-project'. Default 0.
+
+web.baseurl
+  Base URL for browsing Mercurial repositories. Referenced from
+  templates as {hgweb}.
+
+Configuration items common to XMLRPC+email and MySQL access modes:
+
+bugzilla.usermap
+  Path of file containing Mercurial committer email to Bugzilla user email
+  mappings. If specified, the file should contain one mapping per
+  line::
 
-usermap
-  Path of file containing Mercurial committer ID to Bugzilla user ID
-  mappings. If specified, the file should contain one mapping per
-  line, "committer"="Bugzilla user". See also the [usermap] section.
+    committer = Bugzilla user
+
+  See also the [usermap] section.
+
+The ``[usermap]`` section is used to specify mappings of Mercurial
+committer email to Bugzilla user email. See also ``bugzilla.usermap``.
+Contains entries of the form ``committer = Bugzilla user``.
+
+XMLRPC access mode configuration:
+
+bugzilla.bzurl
+  The base URL for the Bugzilla installation.
+  Default 'http://localhost/bugzilla'.
+
+bugzilla.user
+  The username to use to log into Bugzilla via XMLRPC. Default 'bugs'.
+
+bugzilla.password
+  The password for Bugzilla login.
 
-The [usermap] section is used to specify mappings of Mercurial
-committer ID to Bugzilla user ID. See also [bugzilla].usermap.
-"committer"="Bugzilla user"
+XMLRPC+email access mode uses the XMLRPC access mode configuration items,
+and also:
+
+bugzilla.bzemail
+  The Bugzilla email address.
+
+In addition, the Mercurial email settings must be configured. See the
+documentation in hgrc(5), sections ``[email]`` and ``[smtp]``.
+
+MySQL access mode configuration:
+
+bugzilla.host
+  Hostname of the MySQL server holding the Bugzilla database.
+  Default 'localhost'.
+
+bugzilla.db
+  Name of the Bugzilla database in MySQL. Default 'bugs'.
+
+bugzilla.user
+  Username to use to access MySQL server. Default 'bugs'.
 
-Finally, the [web] section supports one entry:
+bugzilla.password
+  Password to use to access MySQL server.
+
+bugzilla.timeout
+  Database connection timeout (seconds). Default 5.
+
+bugzilla.bzuser
+  Fallback Bugzilla user name to record comments with, if changeset
+  committer cannot be found as a Bugzilla user.
 
-baseurl
-  Base URL for browsing Mercurial repositories. Reference from
-  templates as {hgweb}.
+bugzilla.bzdir
+   Bugzilla install directory. Used by default notify. Default
+   '/var/www/html/bugzilla'.
+
+bugzilla.notify
+  The command to run to get Bugzilla to send bug change notification
+  emails. Substitutes from a map with 3 keys, 'bzdir', 'id' (bug id)
+  and 'user' (committer bugzilla email). Default depends on version;
+  from 2.18 it is "cd %(bzdir)s && perl -T contrib/sendbugmail.pl
+  %(id)s %(user)s".
 
 Activating the extension::
 
@@ -109,11 +168,43 @@
     # run bugzilla hook on every change pulled or pushed in here
     incoming.bugzilla = python:hgext.bugzilla.hook
 
-Example configuration:
+Example configurations:
+
+XMLRPC example configuration. This uses the Bugzilla at
+'http://my-project.org/bugzilla', logging in as user 'bugmail@my-project.org'
+wityh password 'plugh'. It is used with a collection of Mercurial
+repositories in '/var/local/hg/repos/'. ::
+
+    [bugzilla]
+    bzurl=http://my-project.org/bugzilla
+    user=bugmail@my-project.org
+    password=plugh
+    version=xmlrpc
+
+    [web]
+    baseurl=http://my-project.org/hg
 
-This example configuration is for a collection of Mercurial
-repositories in /var/local/hg/repos/ used with a local Bugzilla 3.2
-installation in /opt/bugzilla-3.2. ::
+XMLRPC+email example configuration. This uses the Bugzilla at
+'http://my-project.org/bugzilla', logging in as user 'bugmail@my-project.org'
+wityh password 'plugh'. It is used with a collection of Mercurial
+repositories in '/var/local/hg/repos/'. Bug comments are sent to the
+Bugzilla email address 'buzilla@my-project.org'. ::
+
+    [bugzilla]
+    user=bugmail@my-project.org
+    password=plugh
+    version=xmlrpc
+    bzemail=bugzilla@my-project.org
+
+    [web]
+    baseurl=https://dev.laicatc.com/hg
+    bugzillaurl=https://dev.laicatc.com/bugzilla
+
+MySQL example configuration. This is for a collection of Mercurial
+repositories in '/var/local/hg/repos/' used with a local Bugzilla 3.2
+installation in /opt/bugzilla-3.2. The MySQL database is on 'localhost',
+the Bugzilla database name is 'bugs' and MySQL is accessed with MySQL
+username 'bugs' password 'XYZZY'. ::
 
     [bugzilla]
     host=localhost
@@ -132,7 +223,7 @@
     [usermap]
     user@emaildomain.com=user.name@bugzilladomain.com
 
-Commits add a comment to the Bugzilla bug record of the form::
+All the above add a comment to the Bugzilla bug record of the form::
 
     Changeset 3b16791d6642 in repository-name.
     http://dev.domain.com/hg/repository-name/rev/3b16791d6642
@@ -142,31 +233,83 @@
 
 from mercurial.i18n import _
 from mercurial.node import short
-from mercurial import cmdutil, templater, util
-import re, time
-
-MySQLdb = None
+from mercurial import cmdutil, mail, templater, util
+import re, time, xmlrpclib
 
-def buglist(ids):
-    return '(' + ','.join(map(str, ids)) + ')'
-
-class bugzilla_2_16(object):
-    '''support for bugzilla version 2.16.'''
+class bzaccess(object):
+    '''Base class for access to Bugzilla.'''
 
     def __init__(self, ui):
         self.ui = ui
+        usermap = self.ui.config('bugzilla', 'usermap')
+        if usermap:
+            self.ui.readconfig(usermap, sections=['usermap'])
+
+    def map_committer(self, user):
+        '''map name of committer to Bugzilla user name.'''
+        for committer, bzuser in self.ui.configitems('usermap'):
+            if committer.lower() == user.lower():
+                return bzuser
+        return user
+
+    # Methods to be implemented by access classes.
+    def filter_real_bug_ids(self, ids):
+        '''remove bug IDs that do not exist in Bugzilla from set.'''
+        pass
+
+    def filter_cset_known_bug_ids(self, node, ids):
+        '''remove bug IDs where node occurs in comment text from set.'''
+        pass
+
+    def add_comment(self, bugid, text, committer):
+        '''add comment to bug.
+
+        If possible add the comment as being from the committer of
+        the changeset. Otherwise use the default Bugzilla user.
+        '''
+        pass
+
+    def notify(self, ids, committer):
+        '''Force sending of Bugzilla notification emails.'''
+        pass
+
+# Bugzilla via direct access to MySQL database.
+class bzmysql(bzaccess):
+    '''Support for direct MySQL access to Bugzilla.
+
+    The earliest Bugzilla version this is tested with is version 2.16.
+
+    If your Bugzilla is version 3.2 or above, you are strongly
+    recommended to use the XMLRPC access method instead.
+    '''
+
+    @staticmethod
+    def sql_buglist(ids):
+        '''return SQL-friendly list of bug ids'''
+        return '(' + ','.join(map(str, ids)) + ')'
+
+    _MySQLdb = None
+
+    def __init__(self, ui):
+        try:
+            import MySQLdb as mysql
+            bzmysql._MySQLdb = mysql
+        except ImportError, err:
+            raise util.Abort(_('python mysql support not available: %s') % err)
+
+        bzaccess.__init__(self, ui)
+
         host = self.ui.config('bugzilla', 'host', 'localhost')
         user = self.ui.config('bugzilla', 'user', 'bugs')
         passwd = self.ui.config('bugzilla', 'password')
         db = self.ui.config('bugzilla', 'db', 'bugs')
         timeout = int(self.ui.config('bugzilla', 'timeout', 5))
-        usermap = self.ui.config('bugzilla', 'usermap')
-        if usermap:
-            self.ui.readconfig(usermap, sections=['usermap'])
         self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
                      (host, db, user, '*' * len(passwd)))
-        self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
-                                    db=db, connect_timeout=timeout)
+        self.conn = bzmysql._MySQLdb.connect(host=host,
+                                                   user=user, passwd=passwd,
+                                                   db=db,
+                                                   connect_timeout=timeout)
         self.cursor = self.conn.cursor()
         self.longdesc_id = self.get_longdesc_id()
         self.user_ids = {}
@@ -177,7 +320,7 @@
         self.ui.note(_('query: %s %s\n') % (args, kwargs))
         try:
             self.cursor.execute(*args, **kwargs)
-        except MySQLdb.MySQLError:
+        except bzmysql._MySQLdb.MySQLError:
             self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
             raise
 
@@ -190,22 +333,22 @@
         return ids[0][0]
 
     def filter_real_bug_ids(self, ids):
-        '''filter not-existing bug ids from list.'''
-        self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
-        return sorted([c[0] for c in self.cursor.fetchall()])
+        '''filter not-existing bug ids from set.'''
+        self.run('select bug_id from bugs where bug_id in %s' %
+                 bzmysql.sql_buglist(ids))
+        return set([c[0] for c in self.cursor.fetchall()])
 
-    def filter_unknown_bug_ids(self, node, ids):
-        '''filter bug ids from list that already refer to this changeset.'''
+    def filter_cset_known_bug_ids(self, node, ids):
+        '''filter bug ids that already refer to this changeset from set.'''
 
         self.run('''select bug_id from longdescs where
                     bug_id in %s and thetext like "%%%s%%"''' %
-                 (buglist(ids), short(node)))
-        unknown = set(ids)
+                 (bzmysql.sql_buglist(ids), short(node)))
         for (id,) in self.cursor.fetchall():
             self.ui.status(_('bug %d already knows about changeset %s\n') %
                            (id, short(node)))
-            unknown.discard(id)
-        return sorted(unknown)
+            ids.discard(id)
+        return ids
 
     def notify(self, ids, committer):
         '''tell bugzilla to send mail.'''
@@ -251,15 +394,8 @@
             self.user_ids[user] = userid
             return userid
 
-    def map_committer(self, user):
-        '''map name of committer to bugzilla user name.'''
-        for committer, bzuser in self.ui.configitems('usermap'):
-            if committer.lower() == user.lower():
-                return bzuser
-        return user
-
     def get_bugzilla_user(self, committer):
-        '''see if committer is a registered bugzilla user. Return
+        '''See if committer is a registered bugzilla user. Return
         bugzilla username and userid if so. If not, return default
         bugzilla username and userid.'''
         user = self.map_committer(committer)
@@ -292,19 +428,19 @@
                  (bugid, userid, now, self.longdesc_id))
         self.conn.commit()
 
-class bugzilla_2_18(bugzilla_2_16):
+class bzmysql_2_18(bzmysql):
     '''support for bugzilla 2.18 series.'''
 
     def __init__(self, ui):
-        bugzilla_2_16.__init__(self, ui)
+        bzmysql.__init__(self, ui)
         self.default_notify = \
             "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
 
-class bugzilla_3_0(bugzilla_2_18):
+class bzmysql_3_0(bzmysql_2_18):
     '''support for bugzilla 3.0 series.'''
 
     def __init__(self, ui):
-        bugzilla_2_18.__init__(self, ui)
+        bzmysql_2_18.__init__(self, ui)
 
     def get_longdesc_id(self):
         '''get identity of longdesc field'''
@@ -314,13 +450,176 @@
             raise util.Abort(_('unknown database schema'))
         return ids[0][0]
 
+# Buzgilla via XMLRPC interface.
+
+class CookieSafeTransport(xmlrpclib.SafeTransport):
+    """A SafeTransport that retains cookies over its lifetime.
+
+    The regular xmlrpclib transports ignore cookies. Which causes
+    a bit of a problem when you need a cookie-based login, as with
+    the Bugzilla XMLRPC interface.
+
+    So this is a SafeTransport which looks for cookies being set
+    in responses and saves them to add to all future requests.
+    It appears a SafeTransport can do both HTTP and HTTPS sessions,
+    which saves us having to do a CookieTransport too.
+    """
+
+    # Inspiration drawn from
+    # http://blog.godson.in/2010/09/how-to-make-python-xmlrpclib-client.html
+    # http://www.itkovian.net/base/transport-class-for-pythons-xml-rpc-lib/
+
+    cookies = []
+    def send_cookies(self, connection):
+        if self.cookies:
+            for cookie in self.cookies:
+                connection.putheader("Cookie", cookie)
+
+    def request(self, host, handler, request_body, verbose=0):
+        self.verbose = verbose
+
+        # issue XML-RPC request
+        h = self.make_connection(host)
+        if verbose:
+            h.set_debuglevel(1)
+
+        self.send_request(h, handler, request_body)
+        self.send_host(h, host)
+        self.send_cookies(h)
+        self.send_user_agent(h)
+        self.send_content(h, request_body)
+
+        # Deal with differences between Python 2.4-2.6 and 2.7.
+        # In the former h is a HTTP(S). In the latter it's a
+        # HTTP(S)Connection. Luckily, the 2.4-2.6 implementation of
+        # HTTP(S) has an underlying HTTP(S)Connection, so extract
+        # that and use it.
+        try:
+            response = h.getresponse()
+        except AttributeError:
+            response = h._conn.getresponse()
+
+        # Add any cookie definitions to our list.
+        for header in response.msg.getallmatchingheaders("Set-Cookie"):
+            val = header.split(": ", 1)[1]
+            cookie = val.split(";", 1)[0]
+            self.cookies.append(cookie)
+
+        if response.status != 200:
+            raise xmlrpclib.ProtocolError(host + handler, response.status,
+                                          response.reason, response.msg.headers)
+
+        payload = response.read()
+        parser, unmarshaller = self.getparser()
+        parser.feed(payload)
+        parser.close()
+
+        return unmarshaller.close()
+
+class bzxmlrpc(bzaccess):
+    """Support for access to Bugzilla via the Bugzilla XMLRPC API.
+
+    Requires a minimum Bugzilla version 3.4.
+    """
+
+    def __init__(self, ui):
+        bzaccess.__init__(self, ui)
+
+        bzweb = self.ui.config('bugzilla', 'bzurl',
+                               'http://localhost/bugzilla/')
+        bzweb = bzweb.rstrip("/") + "/xmlrpc.cgi"
+
+        user = self.ui.config('bugzilla', 'user', 'bugs')
+        passwd = self.ui.config('bugzilla', 'password')
+
+        self.bzproxy = xmlrpclib.ServerProxy(bzweb, CookieSafeTransport())
+        self.bzproxy.User.login(dict(login=user, password=passwd))
+
+    def get_bug_comments(self, id):
+        """Return a string with all comment text for a bug."""
+        c = self.bzproxy.Bug.comments(dict(ids=[id]))
+        return ''.join([t['text'] for t in c['bugs'][str(id)]['comments']])
+
+    def filter_real_bug_ids(self, ids):
+        res = set()
+        bugs = self.bzproxy.Bug.get(dict(ids=sorted(ids), permissive=True))
+        for bug in bugs['bugs']:
+            res.add(bug['id'])
+        return res
+
+    def filter_cset_known_bug_ids(self, node, ids):
+        for id in sorted(ids):
+            if self.get_bug_comments(id).find(short(node)) != -1:
+                self.ui.status(_('bug %d already knows about changeset %s\n') %
+                               (id, short(node)))
+                ids.discard(id)
+        return ids
+
+    def add_comment(self, bugid, text, committer):
+        self.bzproxy.Bug.add_comment(dict(id=bugid, comment=text))
+
+class bzxmlrpcemail(bzxmlrpc):
+    """Read data from Bugzilla via XMLRPC, send updates via email.
+
+    Advantages of sending updates via email:
+      1. Comments can be added as any user, not just logged in user.
+      2. Bug statuses and other fields not accessible via XMLRPC can
+        be updated. This is not currently used.
+    """
+
+    def __init__(self, ui):
+        bzxmlrpc.__init__(self, ui)
+
+        self.bzemail = self.ui.config('bugzilla', 'bzemail')
+        if not self.bzemail:
+            raise util.Abort(_("configuration 'bzemail' missing"))
+        mail.validateconfig(self.ui)
+
+    def send_bug_modify_email(self, bugid, commands, comment, committer):
+        '''send modification message to Bugzilla bug via email.
+
+        The message format is documented in the Bugzilla email_in.pl
+        specification. commands is a list of command lines, comment is the
+        comment text.
+
+        To stop users from crafting commit comments with
+        Bugzilla commands, specify the bug ID via the message body, rather
+        than the subject line, and leave a blank line after it.
+        '''
+        user = self.map_committer(committer)
+        matches = self.bzproxy.User.get(dict(match=[user]))
+        if not matches['users']:
+            user = self.ui.config('bugzilla', 'user', 'bugs')
+            matches = self.bzproxy.User.get(dict(match=[user]))
+            if not matches['users']:
+                raise util.Abort(_("default bugzilla user %s email not found") %
+                                 user)
+        user = matches['users'][0]['email']
+
+        text = "\n".join(commands) + "\n@bug_id = %d\n\n" % bugid + comment
+
+        _charsets = mail._charsets(self.ui)
+        user = mail.addressencode(self.ui, user, _charsets)
+        bzemail = mail.addressencode(self.ui, self.bzemail, _charsets)
+        msg = mail.mimeencode(self.ui, text, _charsets)
+        msg['From'] = user
+        msg['To'] = bzemail
+        msg['Subject'] = mail.headencode(self.ui, "Bug modification", _charsets)
+        sendmail = mail.connect(self.ui)
+        sendmail(user, bzemail, msg.as_string())
+
+    def add_comment(self, bugid, text, committer):
+        self.send_bug_modify_email(bugid, [], text, committer)
+
 class bugzilla(object):
     # supported versions of bugzilla. different versions have
     # different schemas.
     _versions = {
-        '2.16': bugzilla_2_16,
-        '2.18': bugzilla_2_18,
-        '3.0':  bugzilla_3_0
+        '2.16': bzmysql,
+        '2.18': bzmysql_2_18,
+        '3.0':  bzmysql_3_0,
+        'xmlrpc': bzxmlrpc,
+        'xmlrpc+email': bzxmlrpcemail
         }
 
     _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
@@ -353,10 +652,12 @@
     _split_re = None
 
     def find_bug_ids(self, ctx):
-        '''find valid bug ids that are referred to in changeset
-        comments and that do not already have references to this
-        changeset.'''
+        '''return set of integer bug IDs from commit comment.
 
+        Extract bug IDs from changeset comments. Filter out any that are
+        not known to Bugzilla, and any that already have a reference to
+        the given changeset in their comments.
+        '''
         if bugzilla._bug_re is None:
             bugzilla._bug_re = re.compile(
                 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
@@ -376,7 +677,7 @@
         if ids:
             ids = self.filter_real_bug_ids(ids)
         if ids:
-            ids = self.filter_unknown_bug_ids(ctx.node(), ids)
+            ids = self.filter_cset_known_bug_ids(ctx.node(), ids)
         return ids
 
     def update(self, bugid, ctx):
@@ -418,13 +719,6 @@
     '''add comment to bugzilla for each changeset that refers to a
     bugzilla bug id. only add a comment once per bug, so same change
     seen multiple times does not fill bug with duplicate data.'''
-    try:
-        import MySQLdb as mysql
-        global MySQLdb
-        MySQLdb = mysql
-    except ImportError, err:
-        raise util.Abort(_('python mysql support not available: %s') % err)
-
     if node is None:
         raise util.Abort(_('hook type %s does not pass a changeset id') %
                          hooktype)
@@ -436,6 +730,6 @@
             for id in ids:
                 bz.update(id, ctx)
             bz.notify(ids, util.email(ctx.user()))
-    except MySQLdb.MySQLError, err:
-        raise util.Abort(_('database error: %s') % err.args[1])
+    except Exception, e:
+        raise util.Abort(_('Bugzilla error: %s') % e)
 
--- a/hgext/color.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/color.py	Fri Apr 01 12:21:59 2011 +0200
@@ -18,11 +18,11 @@
 
 '''colorize output from some commands
 
-This extension modifies the status and resolve commands to add color to their
-output to reflect file status, the qseries command to add color to reflect
-patch status (applied, unapplied, missing), and to diff-related
-commands to highlight additions, removals, diff headers, and trailing
-whitespace.
+This extension modifies the status and resolve commands to add color
+to their output to reflect file status, the qseries command to add
+color to reflect patch status (applied, unapplied, missing), and to
+diff-related commands to highlight additions, removals, diff headers,
+and trailing whitespace.
 
 Other effects in addition to color, like bold and underlined text, are
 also available. Effects are rendered with the ECMA-48 SGR control
@@ -107,6 +107,7 @@
            'diff.trailingwhitespace': 'bold red_background',
            'diffstat.deleted': 'red',
            'diffstat.inserted': 'green',
+           'ui.prompt': 'yellow',
            'log.changeset': 'yellow',
            'resolve.resolved': 'green bold',
            'resolve.unresolved': 'red bold',
--- a/hgext/convert/__init__.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/convert/__init__.py	Fri Apr 01 12:21:59 2011 +0200
@@ -10,7 +10,7 @@
 import convcmd
 import cvsps
 import subversion
-from mercurial import commands
+from mercurial import commands, templatekw
 from mercurial.i18n import _
 
 # Commands definition was moved elsewhere to ease demandload job.
@@ -334,3 +334,34 @@
          ],
          _('hg debugcvsps [OPTION]... [PATH]...')),
 }
+
+def kwconverted(ctx, name):
+    rev = ctx.extra().get('convert_revision', '')
+    if rev.startswith('svn:'):
+        if name == 'svnrev':
+            return str(subversion.revsplit(rev)[2])
+        elif name == 'svnpath':
+            return subversion.revsplit(rev)[1]
+        elif name == 'svnuuid':
+            return subversion.revsplit(rev)[0]
+    return rev
+
+def kwsvnrev(repo, ctx, **args):
+    """:svnrev: String. Converted subversion revision number."""
+    return kwconverted(ctx, 'svnrev')
+
+def kwsvnpath(repo, ctx, **args):
+    """:svnpath: String. Converted subversion revision project path."""
+    return kwconverted(ctx, 'svnpath')
+
+def kwsvnuuid(repo, ctx, **args):
+    """:svnuuid: String. Converted subversion revision repository identifier."""
+    return kwconverted(ctx, 'svnuuid')
+
+def extsetup(ui):
+    templatekw.keywords['svnrev'] = kwsvnrev
+    templatekw.keywords['svnpath'] = kwsvnpath
+    templatekw.keywords['svnuuid'] = kwsvnuuid
+
+# tell hggettext to extract docstrings from these functions:
+i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
--- a/hgext/convert/common.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/convert/common.py	Fri Apr 01 12:21:59 2011 +0200
@@ -151,6 +151,13 @@
         """
         return None
 
+    def getbookmarks(self):
+        """Return the bookmarks as a dictionary of name: revision
+
+        Bookmark names are to be UTF-8 strings.
+        """
+        return {}
+
 class converter_sink(object):
     """Conversion sink (target) interface"""
 
@@ -228,6 +235,13 @@
     def after(self):
         pass
 
+    def putbookmarks(self, bookmarks):
+        """Put bookmarks into sink.
+
+        bookmarks: {bookmarkname: sink_rev_id, ...}
+        where bookmarkname is an UTF-8 string.
+        """
+        pass
 
 class commandline(object):
     def __init__(self, ui, command):
@@ -240,7 +254,7 @@
     def postrun(self):
         pass
 
-    def _cmdline(self, cmd, *args, **kwargs):
+    def _cmdline(self, cmd, closestdin, *args, **kwargs):
         cmdline = [self.command, cmd] + list(args)
         for k, v in kwargs.iteritems():
             if len(k) == 1:
@@ -257,16 +271,23 @@
         cmdline = [util.shellquote(arg) for arg in cmdline]
         if not self.ui.debugflag:
             cmdline += ['2>', util.nulldev]
-        cmdline += ['<', util.nulldev]
+        if closestdin:
+            cmdline += ['<', util.nulldev]
         cmdline = ' '.join(cmdline)
         return cmdline
 
     def _run(self, cmd, *args, **kwargs):
-        cmdline = self._cmdline(cmd, *args, **kwargs)
+        return self._dorun(util.popen, cmd, True, *args, **kwargs)
+
+    def _run2(self, cmd, *args, **kwargs):
+        return self._dorun(util.popen2, cmd, False, *args, **kwargs)
+
+    def _dorun(self, openfunc, cmd, closestdin, *args, **kwargs):
+        cmdline = self._cmdline(cmd, closestdin, *args, **kwargs)
         self.ui.debug('running: %s\n' % (cmdline,))
         self.prerun()
         try:
-            return util.popen(cmdline)
+            return openfunc(cmdline)
         finally:
             self.postrun()
 
@@ -322,8 +343,9 @@
         self._argmax = self._argmax / 2 - 1
         return self._argmax
 
-    def limit_arglist(self, arglist, cmd, *args, **kwargs):
-        limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
+    def limit_arglist(self, arglist, cmd, closestdin, *args, **kwargs):
+        cmdlen = len(self._cmdline(cmd, closestdin, *args, **kwargs))
+        limit = self.getargmax() - cmdlen
         bytes = 0
         fl = []
         for fn in arglist:
@@ -339,7 +361,7 @@
             yield fl
 
     def xargs(self, arglist, cmd, *args, **kwargs):
-        for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
+        for l in self.limit_arglist(arglist, cmd, True, *args, **kwargs):
             self.run0(cmd, *(list(args) + l), **kwargs)
 
 class mapfile(dict):
--- a/hgext/convert/convcmd.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/convert/convcmd.py	Fri Apr 01 12:21:59 2011 +0200
@@ -378,6 +378,16 @@
                     if tagsparents:
                         self.map[tagsparents[0][0]] = nrev
 
+            bookmarks = self.source.getbookmarks()
+            cbookmarks = {}
+            for k in bookmarks:
+                v = bookmarks[k]
+                if self.map.get(v, SKIPREV) != SKIPREV:
+                    cbookmarks[k] = self.map[v]
+
+            if c and cbookmarks:
+                self.dest.putbookmarks(cbookmarks)
+
             self.writeauthormap()
         finally:
             self.cleanup()
--- a/hgext/convert/git.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/convert/git.py	Fri Apr 01 12:21:59 2011 +0200
@@ -17,19 +17,27 @@
     # cannot remove environment variable. Just assume none have
     # both issues.
     if hasattr(os, 'unsetenv'):
-        def gitopen(self, s):
+        def gitopen(self, s, noerr=False):
             prevgitdir = os.environ.get('GIT_DIR')
             os.environ['GIT_DIR'] = self.path
             try:
-                return util.popen(s, 'rb')
+                if noerr:
+                    (stdin, stdout, stderr) = util.popen3(s)
+                    return stdout
+                else:
+                    return util.popen(s, 'rb')
             finally:
                 if prevgitdir is None:
                     del os.environ['GIT_DIR']
                 else:
                     os.environ['GIT_DIR'] = prevgitdir
     else:
-        def gitopen(self, s):
-            return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
+        def gitopen(self, s, noerr=False):
+            if noerr:
+                (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
+                return stdout
+            else:
+                util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
 
     def gitread(self, s):
         fh = self.gitopen(s)
@@ -168,3 +176,30 @@
             raise util.Abort(_('cannot read changes in %s') % version)
 
         return changes
+
+    def getbookmarks(self):
+        bookmarks = {}
+
+        # Interesting references in git are prefixed
+        prefix = 'refs/heads/'
+        prefixlen = len(prefix)
+
+        # factor two commands
+        gitcmd = { 'remote/': 'git ls-remote --heads origin',
+                          '': 'git show-ref'}
+
+        # Origin heads
+        for reftype in gitcmd:
+            try:
+                fh = self.gitopen(gitcmd[reftype], noerr=True)
+                for line in fh:
+                    line = line.strip()
+                    rev, name = line.split(None, 1)
+                    if not name.startswith(prefix):
+                        continue
+                    name = '%s%s' % (reftype, name[prefixlen:])
+                    bookmarks[name] = rev
+            except:
+                pass
+
+        return bookmarks
--- a/hgext/convert/hg.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/convert/hg.py	Fri Apr 01 12:21:59 2011 +0200
@@ -21,7 +21,7 @@
 import os, time, cStringIO
 from mercurial.i18n import _
 from mercurial.node import bin, hex, nullid
-from mercurial import hg, util, context, error
+from mercurial import hg, util, context, bookmarks, error
 
 from common import NoRepo, commit, converter_source, converter_sink
 
@@ -214,6 +214,16 @@
     def setfilemapmode(self, active):
         self.filemapmode = active
 
+    def putbookmarks(self, updatedbookmark):
+        if not len(updatedbookmark):
+            return
+
+        self.ui.status(_("updating bookmarks\n"))
+        for bookmark in updatedbookmark:
+            self.repo._bookmarks[bookmark] = bin(updatedbookmark[bookmark])
+            bookmarks.write(self.repo)
+
+
 class mercurial_source(converter_source):
     def __init__(self, ui, path, rev=None):
         converter_source.__init__(self, ui, path, rev)
@@ -374,3 +384,6 @@
             return hex(self.repo.lookup(rev))
         except error.RepoError:
             return None
+
+    def getbookmarks(self):
+        return bookmarks.listbookmarks(self.repo)
--- a/hgext/convert/monotone.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/convert/monotone.py	Fri Apr 01 12:21:59 2011 +0200
@@ -19,6 +19,8 @@
 
         self.ui = ui
         self.path = path
+        self.automatestdio = False
+        self.rev = rev
 
         norepo = NoRepo(_("%s does not look like a monotone repository")
                         % path)
@@ -64,18 +66,103 @@
 
         checktool('mtn', abort=False)
 
-        # test if there are any revisions
-        self.rev = None
-        try:
-            self.getheads()
-        except:
-            raise norepo
-        self.rev = rev
+    def mtnrun(self, *args, **kwargs):
+        if self.automatestdio:
+            return self.mtnrunstdio(*args, **kwargs)
+        else:
+            return self.mtnrunsingle(*args, **kwargs)
 
-    def mtnrun(self, *args, **kwargs):
+    def mtnrunsingle(self, *args, **kwargs):
         kwargs['d'] = self.path
         return self.run0('automate', *args, **kwargs)
 
+    def mtnrunstdio(self, *args, **kwargs):
+        # Prepare the command in automate stdio format
+        command = []
+        for k, v in kwargs.iteritems():
+            command.append("%s:%s" % (len(k), k))
+            if v:
+                command.append("%s:%s" % (len(v), v))
+        if command:
+            command.insert(0, 'o')
+            command.append('e')
+
+        command.append('l')
+        for arg in args:
+            command += "%s:%s" % (len(arg), arg)
+        command.append('e')
+        command = ''.join(command)
+
+        self.ui.debug("mtn: sending '%s'\n" % command)
+        self.mtnwritefp.write(command)
+        self.mtnwritefp.flush()
+
+        return self.mtnstdioreadcommandoutput(command)
+
+    def mtnstdioreadpacket(self):
+        read = None
+        commandnbr = ''
+        while read != ':':
+            read = self.mtnreadfp.read(1)
+            if not read:
+                raise util.Abort(_('bad mtn packet - no end of commandnbr'))
+            commandnbr += read
+        commandnbr = commandnbr[:-1]
+
+        stream = self.mtnreadfp.read(1)
+        if stream not in 'mewptl':
+            raise util.Abort(_('bad mtn packet - bad stream type %s' % stream))
+
+        read = self.mtnreadfp.read(1)
+        if read != ':':
+            raise util.Abort(_('bad mtn packet - no divider before size'))
+
+        read = None
+        lengthstr = ''
+        while read != ':':
+            read = self.mtnreadfp.read(1)
+            if not read:
+                raise util.Abort(_('bad mtn packet - no end of packet size'))
+            lengthstr += read
+        try:
+            length = long(lengthstr[:-1])
+        except TypeError:
+            raise util.Abort(_('bad mtn packet - bad packet size %s')
+                % lengthstr)
+
+        read = self.mtnreadfp.read(length)
+        if len(read) != length:
+            raise util.Abort(_("bad mtn packet - unable to read full packet "
+                "read %s of %s") % (len(read), length))
+
+        return (commandnbr, stream, length, read)
+
+    def mtnstdioreadcommandoutput(self, command):
+        retval = []
+        while True:
+            commandnbr, stream, length, output = self.mtnstdioreadpacket()
+            self.ui.debug('mtn: read packet %s:%s:%s\n' %
+                (commandnbr, stream, length))
+
+            if stream == 'l':
+                # End of command
+                if output != '0':
+                    raise util.Abort(_("mtn command '%s' returned %s") %
+                        (command, output))
+                break
+            elif stream in 'ew':
+                # Error, warning output
+                self.ui.warn(_('%s error:\n') % self.command)
+                self.ui.warn(output)
+            elif stream == 'p':
+                # Progress messages
+                self.ui.debug('mtn: ' + output)
+            elif stream == 'm':
+                # Main stream - command output
+                retval.append(output)
+
+        return ''.join(retval)
+
     def mtnloadmanifest(self, rev):
         if self.manifest_rev == rev:
             return
@@ -204,14 +291,18 @@
         return data, attr
 
     def getcommit(self, rev):
-        certs   = self.mtngetcerts(rev)
+        extra = {}
+        certs = self.mtngetcerts(rev)
+        if certs.get('suspend') == certs["branch"]:
+            extra['close'] = '1'
         return commit(
             author=certs["author"],
             date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
             desc=certs["changelog"],
             rev=rev,
             parents=self.mtnrun("parents", rev).splitlines(),
-            branch=certs["branch"])
+            branch=certs["branch"],
+            extra=extra)
 
     def gettags(self):
         tags = {}
@@ -225,3 +316,43 @@
         # This function is only needed to support --filemap
         # ... and we don't support that
         raise NotImplementedError()
+
+    def before(self):
+        # Check if we have a new enough version to use automate stdio
+        version = 0.0
+        try:
+            versionstr = self.mtnrunsingle("interface_version")
+            version = float(versionstr)
+        except Exception:
+            raise util.Abort(_("unable to determine mtn automate interface "
+                "version"))
+
+        if version >= 12.0:
+            self.automatestdio = True
+            self.ui.debug("mtn automate version %s - using automate stdio\n" %
+                version)
+
+            # launch the long-running automate stdio process
+            self.mtnwritefp, self.mtnreadfp = self._run2('automate', 'stdio',
+                '-d', self.path)
+            # read the headers
+            read = self.mtnreadfp.readline()
+            if read != 'format-version: 2\n':
+                raise util.Abort(_('mtn automate stdio header unexpected: %s')
+                    % read)
+            while read != '\n':
+                read = self.mtnreadfp.readline()
+                if not read:
+                    raise util.Abort(_("failed to reach end of mtn automate "
+                        "stdio headers"))
+        else:
+            self.ui.debug("mtn automate version %s - not using automate stdio "
+                "(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version)
+
+    def after(self):
+        if self.automatestdio:
+            self.mtnwritefp.close()
+            self.mtnwritefp = None
+            self.mtnreadfp.close()
+            self.mtnreadfp = None
+
--- a/hgext/convert/subversion.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/convert/subversion.py	Fri Apr 01 12:21:59 2011 +0200
@@ -41,6 +41,15 @@
 class SvnPathNotFound(Exception):
     pass
 
+def revsplit(rev):
+    """Parse a revision string and return (uuid, path, revnum)."""
+    url, revnum = rev.rsplit('@', 1)
+    parts = url.split('/', 1)
+    mod = ''
+    if len(parts) > 1:
+        mod = '/' + parts[1]
+    return parts[0][4:], mod, int(revnum)
+
 def geturl(path):
     try:
         return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
@@ -259,6 +268,7 @@
             except ValueError:
                 raise util.Abort(_('svn: revision %s is not an integer') % rev)
 
+        self.trunkname = self.ui.config('convert', 'svn.trunk', 'trunk').strip('/')
         self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
         try:
             self.startrev = int(self.startrev)
@@ -285,7 +295,7 @@
     def setrevmap(self, revmap):
         lastrevs = {}
         for revid in revmap.iterkeys():
-            uuid, module, revnum = self.revsplit(revid)
+            uuid, module, revnum = revsplit(revid)
             lastrevnum = lastrevs.setdefault(module, revnum)
             if revnum > lastrevnum:
                 lastrevs[module] = revnum
@@ -380,7 +390,7 @@
             files, self.removed, copies = self.expandpaths(rev, paths, parents)
         else:
             # Perform a full checkout on roots
-            uuid, module, revnum = self.revsplit(rev)
+            uuid, module, revnum = revsplit(rev)
             entries = svn.client.ls(self.baseurl + urllib.quote(module),
                                     optrev(revnum), True, self.ctx)
             files = [n for n, e in entries.iteritems()
@@ -402,7 +412,7 @@
 
     def getcommit(self, rev):
         if rev not in self.commits:
-            uuid, module, revnum = self.revsplit(rev)
+            uuid, module, revnum = revsplit(rev)
             self.module = module
             self.reparent(module)
             # We assume that:
@@ -529,16 +539,6 @@
     def revnum(self, rev):
         return int(rev.split('@')[-1])
 
-    def revsplit(self, rev):
-        url, revnum = rev.rsplit('@', 1)
-        revnum = int(revnum)
-        parts = url.split('/', 1)
-        uuid = parts.pop(0)[4:]
-        mod = ''
-        if parts:
-            mod = '/' + parts[0]
-        return uuid, mod, revnum
-
     def latest(self, path, stop=0):
         """Find the latest revid affecting path, up to stop. It may return
         a revision in a different module, since a branch may be moved without
@@ -605,7 +605,7 @@
         changed, removed = set(), set()
         copies = {}
 
-        new_module, revnum = self.revsplit(rev)[1:]
+        new_module, revnum = revsplit(rev)[1:]
         if new_module != self.module:
             self.module = new_module
             self.reparent(self.module)
@@ -622,7 +622,7 @@
                     continue
                 # Copy sources not in parent revisions cannot be
                 # represented, ignore their origin for now
-                pmodule, prevnum = self.revsplit(parents[0])[1:]
+                pmodule, prevnum = revsplit(parents[0])[1:]
                 if ent.copyfrom_rev < prevnum:
                     continue
                 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
@@ -633,7 +633,7 @@
                 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
             elif kind == 0: # gone, but had better be a deleted *file*
                 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
-                pmodule, prevnum = self.revsplit(parents[0])[1:]
+                pmodule, prevnum = revsplit(parents[0])[1:]
                 parentpath = pmodule + "/" + entrypath
                 fromkind = self._checkpath(entrypath, prevnum, pmodule)
 
@@ -659,7 +659,7 @@
                 if ent.action == 'R' and parents:
                     # If a directory is replacing a file, mark the previous
                     # file as deleted
-                    pmodule, prevnum = self.revsplit(parents[0])[1:]
+                    pmodule, prevnum = revsplit(parents[0])[1:]
                     pkind = self._checkpath(entrypath, prevnum, pmodule)
                     if pkind == svn.core.svn_node_file:
                         removed.add(self.recode(entrypath))
@@ -681,7 +681,7 @@
                     continue
                 # Copy sources not in parent revisions cannot be
                 # represented, ignore their origin for now
-                pmodule, prevnum = self.revsplit(parents[0])[1:]
+                pmodule, prevnum = revsplit(parents[0])[1:]
                 if ent.copyfrom_rev < prevnum:
                     continue
                 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule)
@@ -736,7 +736,7 @@
                     # ent.copyfrom_rev may not be the actual last revision
                     previd = self.latest(newpath, ent.copyfrom_rev)
                     if previd is not None:
-                        prevmodule, prevnum = self.revsplit(previd)[1:]
+                        prevmodule, prevnum = revsplit(previd)[1:]
                         if prevnum >= self.startrev:
                             parents = [previd]
                             self.ui.note(
@@ -761,9 +761,8 @@
             author = author and self.recode(author) or ''
             try:
                 branch = self.module.split("/")[-1]
-                trunkname = self.ui.config('convert', 'svn.trunk', 'trunk')
-                if branch == trunkname.strip('/'):
-                    branch = ''
+                if branch == self.trunkname:
+                    branch = None
             except IndexError:
                 branch = None
 
@@ -834,7 +833,7 @@
             raise IOError()
         mode = ''
         try:
-            new_module, revnum = self.revsplit(rev)[1:]
+            new_module, revnum = revsplit(rev)[1:]
             if self.module != new_module:
                 self.module = new_module
                 self.reparent(self.module)
@@ -944,6 +943,7 @@
 
 class svn_sink(converter_sink, commandline):
     commit_re = re.compile(r'Committed revision (\d+).', re.M)
+    uuid_re = re.compile(r'Repository UUID:\s*(\S+)', re.M)
 
     def prerun(self):
         if self.wc:
@@ -964,8 +964,6 @@
 
     def __init__(self, ui, path):
 
-        if svn is None:
-            raise MissingTool(_('Could not load Subversion python bindings'))
         converter_sink.__init__(self, ui, path)
         commandline.__init__(self, ui, 'svn')
         self.delete = []
@@ -1012,8 +1010,8 @@
             fp.close()
             util.set_flags(hook, False, True)
 
-        xport = transport.SvnRaTransport(url=geturl(path))
-        self.uuid = svn.ra.get_uuid(xport.ra)
+        output = self.run0('info')
+        self.uuid = self.uuid_re.search(output).group(1).strip()
 
     def wjoin(self, *names):
         return os.path.join(self.wc, *names)
--- a/hgext/eol.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/eol.py	Fri Apr 01 12:21:59 2011 +0200
@@ -73,11 +73,13 @@
 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
-you must migrate to a ``.hgeol`` file first before using the hook.
-Remember to enable the eol extension in the repository where you
-install the hook.
+have been unified into a single hook named ``eol.checkheadshook``. The
+hook will lookup the expected line endings from the ``.hgeol`` file,
+which means you must migrate to a ``.hgeol`` file first before using
+the hook. ``eol.checkheadshook`` only checks heads, intermediate
+invalid revisions will be pushed. To forbid them completely, use the
+``eol.checkallhook`` hook. These hooks are best used as
+``pretxnchangegroup`` hooks.
 
 See :hg:`help patterns` for more information about the glob patterns
 used.
@@ -127,36 +129,119 @@
     'cleverdecode:': tocrlf
 }
 
+class eolfile(object):
+    def __init__(self, ui, root, data):
+        self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
+        self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
 
-def hook(ui, repo, node, hooktype, **kwargs):
-    """verify that files have expected EOLs"""
+        self.cfg = config.config()
+        # Our files should not be touched. The pattern must be
+        # inserted first override a '** = native' pattern.
+        self.cfg.set('patterns', '.hg*', 'BIN')
+        # We can then parse the user's patterns.
+        self.cfg.parse('.hgeol', data)
+
+        isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
+        self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
+        iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
+        self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
+
+        include = []
+        exclude = []
+        for pattern, style in self.cfg.items('patterns'):
+            key = style.upper()
+            if key == 'BIN':
+                exclude.append(pattern)
+            else:
+                include.append(pattern)
+        # This will match the files for which we need to care
+        # about inconsistent newlines.
+        self.match = match.match(root, '', [], include, exclude)
+
+    def setfilters(self, ui):
+        for pattern, style in self.cfg.items('patterns'):
+            key = style.upper()
+            try:
+                ui.setconfig('decode', pattern, self._decode[key])
+                ui.setconfig('encode', pattern, self._encode[key])
+            except KeyError:
+                ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
+                        % (style, self.cfg.source('patterns', pattern)))
+
+    def checkrev(self, repo, ctx, files):
+        failed = []
+        for f in (files or ctx.files()):
+            if f not in ctx:
+                continue
+            for pattern, style in self.cfg.items('patterns'):
+                if not match.match(repo.root, '', [pattern])(f):
+                    continue
+                target = self._encode[style.upper()]
+                data = ctx[f].data()
+                if (target == "to-lf" and "\r\n" in data
+                    or target == "to-crlf" and singlelf.search(data)):
+                    failed.append((str(ctx), target, f))
+                break
+        return failed
+
+def parseeol(ui, repo, nodes):
+    try:
+        for node in nodes:
+            try:
+                if node is None:
+                    # Cannot use workingctx.data() since it would load
+                    # and cache the filters before we configure them.
+                    data = repo.wfile('.hgeol').read()
+                else:
+                    data = repo[node]['.hgeol'].data()
+                return eolfile(ui, repo.root, data)
+            except (IOError, LookupError):
+                pass
+    except error.ParseError, inst:
+        ui.warn(_("warning: ignoring .hgeol file due to parse error "
+                  "at %s: %s\n") % (inst.args[1], inst.args[0]))
+    return None
+
+def _checkhook(ui, repo, node, headsonly):
+    # Get revisions to check and touched files at the same time
     files = set()
+    revs = set()
     for rev in xrange(repo[node].rev(), len(repo)):
-        files.update(repo[rev].files())
-    tip = repo['tip']
-    for f in files:
-        if f not in tip:
-            continue
-        for pattern, target in ui.configitems('encode'):
-            if match.match(repo.root, '', [pattern])(f):
-                data = tip[f].data()
-                if target == "to-lf" and "\r\n" in data:
-                    raise util.Abort(_("%s should not have CRLF line endings")
-                                     % f)
-                elif target == "to-crlf" and singlelf.search(data):
-                    raise util.Abort(_("%s should not have LF line endings")
-                                     % f)
-                # Ignore other rules for this file
-                break
+        revs.add(rev)
+        if headsonly:
+            ctx = repo[rev]
+            files.update(ctx.files())
+            for pctx in ctx.parents():
+                revs.discard(pctx.rev())
+    failed = []
+    for rev in revs:
+        ctx = repo[rev]
+        eol = parseeol(ui, repo, [ctx.node()])
+        if eol:
+            failed.extend(eol.checkrev(repo, ctx, files))
 
+    if failed:
+        eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
+        msgs = []
+        for node, target, f in failed:
+            msgs.append(_("  %s in %s should not have %s line endings") %
+                        (f, node, eols[target]))
+        raise util.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
+
+def checkallhook(ui, repo, node, hooktype, **kwargs):
+    """verify that files have expected EOLs"""
+    _checkhook(ui, repo, node, False)
+
+def checkheadshook(ui, repo, node, hooktype, **kwargs):
+    """verify that files have expected EOLs"""
+    _checkhook(ui, repo, node, True)
+
+# "checkheadshook" used to be called "hook"
+hook = checkheadshook
 
 def preupdate(ui, repo, hooktype, parent1, parent2):
     #print "preupdate for %s: %s -> %s" % (repo.root, parent1, parent2)
-    try:
-        repo.readhgeol(parent1)
-    except error.ParseError, inst:
-        ui.warn(_("warning: ignoring .hgeol file due to parse error "
-                  "at %s: %s\n") % (inst.args[1], inst.args[0]))
+    repo.loadeol([parent1])
     return False
 
 def uisetup(ui):
@@ -184,66 +269,15 @@
 
     class eolrepo(repo.__class__):
 
-        _decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
-        _encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
-
-        def readhgeol(self, node=None, data=None):
-            if data is None:
-                try:
-                    if node is None:
-                        data = self.wfile('.hgeol').read()
-                    else:
-                        data = self[node]['.hgeol'].data()
-                except (IOError, LookupError):
-                    return None
-
-            if self.ui.config('eol', 'native', os.linesep) in ('LF', '\n'):
-                self._decode['NATIVE'] = 'to-lf'
-            else:
-                self._decode['NATIVE'] = 'to-crlf'
-
-            eol = config.config()
-            # Our files should not be touched. The pattern must be
-            # inserted first override a '** = native' pattern.
-            eol.set('patterns', '.hg*', 'BIN')
-            # We can then parse the user's patterns.
-            eol.parse('.hgeol', data)
-
-            if eol.get('repository', 'native') == 'CRLF':
-                self._encode['NATIVE'] = 'to-crlf'
-            else:
-                self._encode['NATIVE'] = 'to-lf'
-
-            for pattern, style in eol.items('patterns'):
-                key = style.upper()
-                try:
-                    self.ui.setconfig('decode', pattern, self._decode[key])
-                    self.ui.setconfig('encode', pattern, self._encode[key])
-                except KeyError:
-                    self.ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
-                                 % (style, eol.source('patterns', pattern)))
-
-            include = []
-            exclude = []
-            for pattern, style in eol.items('patterns'):
-                key = style.upper()
-                if key == 'BIN':
-                    exclude.append(pattern)
-                else:
-                    include.append(pattern)
-
-            # This will match the files for which we need to care
-            # about inconsistent newlines.
-            return match.match(self.root, '', [], include, exclude)
+        def loadeol(self, nodes):
+            eol = parseeol(self.ui, self, nodes)
+            if eol is None:
+                return None
+            eol.setfilters(self.ui)
+            return eol.match
 
         def _hgcleardirstate(self):
-            try:
-                self._eolfile = self.readhgeol() or self.readhgeol('tip')
-            except error.ParseError, inst:
-                ui.warn(_("warning: ignoring .hgeol file due to parse error "
-                          "at %s: %s\n") % (inst.args[1], inst.args[0]))
-                self._eolfile = None
-
+            self._eolfile = self.loadeol([None, 'tip'])
             if not self._eolfile:
                 self._eolfile = util.never
                 return
--- a/hgext/extdiff.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/extdiff.py	Fri Apr 01 12:21:59 2011 +0200
@@ -121,7 +121,7 @@
         msg = _('cannot specify --rev and --change at the same time')
         raise util.Abort(msg)
     elif change:
-        node2 = repo.lookup(change)
+        node2 = cmdutil.revsingle(repo, change, None).node()
         node1a, node1b = repo.changelog.parents(node2)
     else:
         node1a, node2 = cmdutil.revpair(repo, revs)
@@ -187,14 +187,14 @@
         # Handle bogus modifies correctly by checking if the files exist
         if len(common) == 1:
             common_file = util.localpath(common.pop())
-            dir1a = os.path.join(dir1a, common_file)
+            dir1a = os.path.join(tmproot, dir1a, common_file)
             label1a = common_file + rev1a
-            if not os.path.isfile(os.path.join(tmproot, dir1a)):
+            if not os.path.isfile(dir1a):
                 dir1a = os.devnull
             if do3way:
-                dir1b = os.path.join(dir1b, common_file)
+                dir1b = os.path.join(tmproot, dir1b, common_file)
                 label1b = common_file + rev1b
-                if not os.path.isfile(os.path.join(tmproot, dir1b)):
+                if not os.path.isfile(dir1b):
                     dir1b = os.devnull
             dir2 = os.path.join(dir2root, dir2, common_file)
             label2 = common_file + rev2
--- a/hgext/graphlog.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/graphlog.py	Fri Apr 01 12:21:59 2011 +0200
@@ -324,6 +324,7 @@
             except TypeError, e:
                 if len(args) > wrapfn.func_code.co_argcount:
                     raise util.Abort(_('--graph option allows at most one file'))
+                raise
         return orig(*args, **kwargs)
     entry = extensions.wrapcommand(table, cmd, graph)
     entry[1].append(('G', 'graph', None, _("show the revision DAG")))
--- a/hgext/keyword.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/keyword.py	Fri Apr 01 12:21:59 2011 +0200
@@ -109,11 +109,26 @@
 }
 
 # date like in cvs' $Date
-utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
+def utcdate(text):
+    ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
+    '''
+    return util.datestr((text[0], 0), '%Y/%m/%d %H:%M:%S')
 # date like in svn's $Date
-svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
+def svnisodate(text):
+    ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
+    +0200 (Tue, 18 Aug 2009)".
+    '''
+    return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
 # date like in svn's $Id
-svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ')
+def svnutcdate(text):
+    ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
+    11:00:13Z".
+    '''
+    return util.datestr((text[0], 0), '%Y-%m-%d %H:%M:%SZ')
+
+templatefilters.filters.update({'utcdate': utcdate,
+                                'svnisodate': svnisodate,
+                                'svnutcdate': svnutcdate})
 
 # make keyword tools accessible
 kwtools = {'templater': None, 'hgcmd': ''}
@@ -176,9 +191,6 @@
                                   for k, v in kwmaps)
         else:
             self.templates = _defaultkwmaps(self.ui)
-        templatefilters.filters.update({'utcdate': utcdate,
-                                        'svnisodate': svnisodate,
-                                        'svnutcdate': svnutcdate})
 
     @util.propertycache
     def escape(self):
--- a/hgext/mq.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/mq.py	Fri Apr 01 12:21:59 2011 +0200
@@ -1899,7 +1899,7 @@
     With -g/--git, patches imported with --rev will use the git diff
     format. See the diffs help topic for information on why this is
     important for preserving rename/copy information and permission
-    changes.
+    changes. Use :hg:`qfinish` to remove changesets from mq control.
 
     To import a patch from standard input, pass - as the patch file.
     When importing from standard input, a patch name must be specified
--- a/hgext/rebase.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/rebase.py	Fri Apr 01 12:21:59 2011 +0200
@@ -90,7 +90,8 @@
         contf = opts.get('continue')
         abortf = opts.get('abort')
         collapsef = opts.get('collapse', False)
-        extrafn = opts.get('extrafn')
+        collapsemsg = cmdutil.logmessage(opts)
+        extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
         keepf = opts.get('keep', False)
         keepbranchesf = opts.get('keepbranches', False)
         detachf = opts.get('detach', False)
@@ -98,6 +99,10 @@
         # other extensions
         keepopen = opts.get('keepopen', False)
 
+        if collapsemsg and not collapsef:
+            raise util.Abort(
+                _('message can only be specified with collapse'))
+
         if contf or abortf:
             if contf and abortf:
                 raise util.Abort(_('cannot use both abort and continue'))
@@ -138,8 +143,7 @@
                     external = checkexternal(repo, state, targetancestors)
 
         if keepbranchesf:
-            if extrafn:
-                raise util.Abort(_('cannot use both keepbranches and extrafn'))
+            assert not extrafn, 'cannot use both keepbranches and extrafn'
             def extrafn(ctx, extra):
                 extra['branch'] = ctx.branch()
 
@@ -190,11 +194,14 @@
         if collapsef and not keepopen:
             p1, p2 = defineparents(repo, min(state), target,
                                                         state, targetancestors)
-            commitmsg = 'Collapsed revision'
-            for rebased in state:
-                if rebased not in skipped and state[rebased] != nullmerge:
-                    commitmsg += '\n* %s' % repo[rebased].description()
-            commitmsg = ui.edit(commitmsg, repo.ui.username())
+            if collapsemsg:
+                commitmsg = collapsemsg
+            else:
+                commitmsg = 'Collapsed revision'
+                for rebased in state:
+                    if rebased not in skipped and state[rebased] != nullmerge:
+                        commitmsg += '\n* %s' % repo[rebased].description()
+                commitmsg = ui.edit(commitmsg, repo.ui.username())
             newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
                                   extrafn=extrafn)
 
@@ -270,7 +277,7 @@
         if k in m1:
             if v in m1 or v in m2:
                 repo.dirstate.copy(v, k)
-                if v in m2 and v not in m1:
+                if v in m2 and v not in m1 and k in m2:
                     repo.dirstate.remove(v)
 
 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
@@ -354,6 +361,8 @@
     'Update rebased mq patches - finalize and then import them'
     mqrebase = {}
     mq = repo.mq
+    original_series = mq.full_series[:]
+
     for p in mq.applied:
         rev = repo[p.node].rev()
         if rev in state:
@@ -371,6 +380,15 @@
                 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
                 mq.qimport(repo, (), patchname=name, git=isgit,
                                 rev=[str(state[rev])])
+
+        # Restore missing guards
+        for s in original_series:
+            pname = mq.guard_re.split(s, 1)[0]
+            if pname in mq.full_series:
+                repo.ui.debug('restoring guard for patch %s' % (pname))
+                mq.full_series.remove(pname)
+                mq.full_series.append(s)
+                mq.series_dirty = True
         mq.save_dirty()
 
 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
@@ -475,9 +493,10 @@
 
     if src:
         commonbase = repo[src].ancestor(repo[dest])
+        samebranch = repo[src].branch() == repo[dest].branch()
         if commonbase == repo[src]:
             raise util.Abort(_('source is ancestor of destination'))
-        if commonbase == repo[dest]:
+        if samebranch and commonbase == repo[dest]:
             raise util.Abort(_('source is descendant of destination'))
         source = repo[src].rev()
         if detach:
@@ -565,6 +584,10 @@
         ('d', 'dest', '',
          _('rebase onto the specified changeset'), _('REV')),
         ('', 'collapse', False, _('collapse the rebased changesets')),
+        ('m', 'message', '',
+         _('use text as collapse commit message'), _('TEXT')),
+        ('l', 'logfile', '',
+         _('read collapse commit message from file'), _('FILE')),
         ('', 'keep', False, _('keep original changesets')),
         ('', 'keepbranches', False, _('keep original branch names')),
         ('', 'detach', False, _('force detaching of source from its original '
--- a/hgext/record.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/record.py	Fri Apr 01 12:21:59 2011 +0200
@@ -324,10 +324,12 @@
         for i, chunk in enumerate(h.hunks):
             if skipfile is None and skipall is None:
                 chunk.pretty(ui)
-            msg = (total == 1
-                   and (_('record this change to %r?') % chunk.filename())
-                   or (_('record change %d/%d to %r?') %
-                       (pos - len(h.hunks) + i, total, chunk.filename())))
+            if total == 1:
+                msg = _('record this change to %r?') % chunk.filename()
+            else:
+                idx = pos - len(h.hunks) + i
+                msg = _('record change %d/%d to %r?') % (idx, total,
+                                                         chunk.filename())
             r, skipfile, skipall = prompt(skipfile, skipall, msg)
             if r:
                 if fixoffset:
--- a/hgext/relink.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/relink.py	Fri Apr 01 12:21:59 2011 +0200
@@ -172,8 +172,8 @@
 
     ui.progress(_('relinking'), None)
 
-    ui.status(_('relinked %d files (%d bytes reclaimed)\n') %
-              (relinked, savedbytes))
+    ui.status(_('relinked %d files (%s reclaimed)\n') %
+              (relinked, util.bytecount(savedbytes)))
 
 cmdtable = {
     'relink': (
--- a/hgext/schemes.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/schemes.py	Fri Apr 01 12:21:59 2011 +0200
@@ -40,8 +40,9 @@
 same name.
 """
 
-import re
-from mercurial import hg, templater
+import os, re
+from mercurial import extensions, hg, templater, url as urlmod, util
+from mercurial.i18n import _
 
 
 class ShortRepository(object):
@@ -58,6 +59,7 @@
         return '<ShortRepository: %s>' % self.scheme
 
     def instance(self, ui, url, create):
+        # Should this use urlmod.url(), or is manual parsing better?
         url = url.split('://', 1)[1]
         parts = url.split('/', self.parts)
         if len(parts) > self.parts:
@@ -69,6 +71,12 @@
         url = ''.join(self.templater.process(self.url, context)) + tail
         return hg._lookup(url).instance(ui, url, create)
 
+def hasdriveletter(orig, path):
+    for scheme in schemes:
+        if path.startswith(scheme + ':'):
+            return False
+    return orig(path)
+
 schemes = {
     'py': 'http://hg.python.org/',
     'bb': 'https://bitbucket.org/',
@@ -81,4 +89,10 @@
     schemes.update(dict(ui.configitems('schemes')))
     t = templater.engine(lambda x: x)
     for scheme, url in schemes.items():
+        if (os.name == 'nt' and len(scheme) == 1 and scheme.isalpha()
+            and os.path.exists('%s:\\' % scheme)):
+            raise util.Abort(_('custom scheme %s:// conflicts with drive '
+                               'letter %s:\\\n') % (scheme, scheme.upper()))
         hg.schemes[scheme] = ShortRepository(url, scheme, t)
+
+    extensions.wrapfunction(urlmod, 'hasdriveletter', hasdriveletter)
--- a/hgext/transplant.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/hgext/transplant.py	Fri Apr 01 12:21:59 2011 +0200
@@ -17,7 +17,7 @@
 import os, tempfile
 from mercurial import bundlerepo, cmdutil, hg, merge, match
 from mercurial import patch, revlog, util, error
-from mercurial import revset
+from mercurial import revset, templatekw
 
 class transplantentry(object):
     def __init__(self, lnode, rnode):
@@ -177,12 +177,11 @@
             lock.release()
             wlock.release()
 
-    def filter(self, filter, changelog, patchfile):
+    def filter(self, filter, node, changelog, patchfile):
         '''arbitrarily rewrite changeset before applying it'''
 
         self.ui.status(_('filtering %s\n') % patchfile)
         user, date, msg = (changelog[1], changelog[2], changelog[4])
-
         fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
         fp = os.fdopen(fd, 'w')
         fp.write("# HG changeset patch\n")
@@ -194,7 +193,9 @@
         try:
             util.system('%s %s %s' % (filter, util.shellquote(headerfile),
                                    util.shellquote(patchfile)),
-                        environ={'HGUSER': changelog[1]},
+                        environ={'HGUSER': changelog[1],
+                                 'HGREVISION': revlog.hex(node),
+                                 },
                         onerr=util.Abort, errprefix=_('filter failed'))
             user, date, msg = self.parselog(file(headerfile))[1:4]
         finally:
@@ -209,7 +210,7 @@
         date = "%d %d" % (time, timezone)
         extra = {'transplant_source': node}
         if filter:
-            (user, date, message) = self.filter(filter, cl, patchfile)
+            (user, date, message) = self.filter(filter, node, cl, patchfile)
 
         if log:
             # we don't translate messages inserted into commits
@@ -345,6 +346,8 @@
         message = []
         node = revlog.nullid
         inmsg = False
+        user = None
+        date = None
         for line in fp.read().splitlines():
             if inmsg:
                 message.append(line)
@@ -359,6 +362,8 @@
             elif not line.startswith('# '):
                 inmsg = True
                 message.append(line)
+        if None in (user, date):
+            raise util.Abort(_("filter corrupted changeset (no user or date)"))
         return (node, user, date, '\n'.join(message), parents)
 
     def log(self, user, date, message, p1, p2, merge=False):
@@ -547,8 +552,8 @@
     if source:
         sourcerepo = ui.expandpath(source)
         source = hg.repository(ui, sourcerepo)
-        source, incoming, bundle = bundlerepo.getremotechanges(ui, repo, source,
-                                    force=True)
+        source, common, incoming, bundle = bundlerepo.getremotechanges(ui, repo,
+                                            source, force=True)
     else:
         source = repo
 
@@ -607,8 +612,15 @@
         cs.add(r)
     return [r for r in s if r in cs]
 
+def kwtransplanted(repo, ctx, **args):
+    """:transplanted: String. The node identifier of the transplanted
+    changeset if any."""
+    n = ctx.extra().get('transplant_source')
+    return n and revlog.hex(n) or ''
+
 def extsetup(ui):
     revset.symbols['transplanted'] = revsettransplanted
+    templatekw.keywords['transplanted'] = kwtransplanted
 
 cmdtable = {
     "transplant":
@@ -632,4 +644,4 @@
 }
 
 # tell hggettext to extract docstrings from these functions:
-i18nfunctions = [revsettransplanted]
+i18nfunctions = [revsettransplanted, kwtransplanted]
--- a/mercurial/ancestor.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/ancestor.py	Fri Apr 01 12:21:59 2011 +0200
@@ -9,9 +9,10 @@
 
 def ancestor(a, b, pfunc):
     """
-    return a minimal-distance ancestor of nodes a and b, or None if there is no
-    such ancestor. Note that there can be several ancestors with the same
-    (minimal) distance, and the one returned is arbitrary.
+    Returns the common ancestor of a and b that is furthest from a
+    root (as measured by longest path) or None if no ancestor is
+    found. If there are multiple common ancestors at the same
+    distance, the first one found is returned.
 
     pfunc must return a list of parent vertices for a given vertex
     """
@@ -22,6 +23,7 @@
     a, b = sorted([a, b])
 
     # find depth from root of all ancestors
+    # depth is stored as a negative for heapq
     parentcache = {}
     visit = [a, b]
     depth = {}
@@ -39,6 +41,7 @@
                 if p not in depth:
                     visit.append(p)
             if visit[-1] == vertex:
+                # -(maximum distance of parents + 1)
                 depth[vertex] = min([depth[p] for p in pl]) - 1
                 visit.pop()
 
--- a/mercurial/archival.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/archival.py	Fri Apr 01 12:21:59 2011 +0200
@@ -9,7 +9,7 @@
 from node import hex
 import cmdutil
 import util, encoding
-import cStringIO, os, stat, tarfile, time, zipfile
+import cStringIO, os, tarfile, time, zipfile
 import zlib, gzip
 
 def tidyprefix(dest, kind, prefix):
@@ -172,10 +172,10 @@
         # unzip will not honor unix file modes unless file creator is
         # set to unix (id 3).
         i.create_system = 3
-        ftype = stat.S_IFREG
+        ftype = 0x8000 # UNX_IFREG in unzip source code
         if islink:
             mode = 0777
-            ftype = stat.S_IFLNK
+            ftype = 0xa000 # UNX_IFLNK in unzip source code
         i.external_attr = (mode | ftype) << 16L
         self.z.writestr(i, data)
 
--- a/mercurial/bdiff.c	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/bdiff.c	Fri Apr 01 12:21:59 2011 +0200
@@ -49,7 +49,7 @@
 #include "util.h"
 
 struct line {
-	int h, len, n, e;
+	int hash, len, n, e;
 	const char *l;
 };
 
@@ -63,9 +63,10 @@
 	struct hunk *next;
 };
 
-int splitlines(const char *a, int len, struct line **lr)
+static int splitlines(const char *a, int len, struct line **lr)
 {
-	int h, i;
+	unsigned hash;
+	int i;
 	const char *p, *b = a;
 	const char * const plast = a + len - 1;
 	struct line *l;
@@ -81,14 +82,14 @@
 		return -1;
 
 	/* build the line array and calculate hashes */
-	h = 0;
+	hash = 0;
 	for (p = a; p < a + len; p++) {
 		/* Leonid Yuriev's hash */
-		h = (h * 1664525) + *p + 1013904223;
+		hash = (hash * 1664525) + (unsigned char)*p + 1013904223;
 
 		if (*p == '\n' || p == plast) {
-			l->h = h;
-			h = 0;
+			l->hash = hash;
+			hash = 0;
 			l->len = p - b + 1;
 			l->l = b;
 			l->n = INT_MAX;
@@ -98,14 +99,15 @@
 	}
 
 	/* set up a sentinel */
-	l->h = l->len = 0;
+	l->hash = 0;
+	l->len = 0;
 	l->l = a + len;
 	return i - 1;
 }
 
-int inline cmp(struct line *a, struct line *b)
+static inline int cmp(struct line *a, struct line *b)
 {
-	return a->h != b->h || a->len != b->len || memcmp(a->l, b->l, a->len);
+	return a->hash != b->hash || a->len != b->len || memcmp(a->l, b->l, a->len);
 }
 
 static int equatelines(struct line *a, int an, struct line *b, int bn)
@@ -138,7 +140,7 @@
 	/* add lines to the hash table chains */
 	for (i = bn - 1; i >= 0; i--) {
 		/* find the equivalence class */
-		for (j = b[i].h & buckets; h[j].pos != INT_MAX;
+		for (j = b[i].hash & buckets; h[j].pos != INT_MAX;
 		     j = (j + 1) & buckets)
 			if (!cmp(b + i, b + h[j].pos))
 				break;
@@ -156,7 +158,7 @@
 	/* match items in a to their equivalence class in b */
 	for (i = 0; i < an; i++) {
 		/* find the equivalence class */
-		for (j = a[i].h & buckets; h[j].pos != INT_MAX;
+		for (j = a[i].hash & buckets; h[j].pos != INT_MAX;
 		     j = (j + 1) & buckets)
 			if (!cmp(a + i, b + h[j].pos))
 				break;
--- a/mercurial/bookmarks.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/bookmarks.py	Fri Apr 01 12:21:59 2011 +0200
@@ -101,13 +101,7 @@
     if current == mark:
         return
 
-    refs = repo._bookmarks
-
-    # do not update if we do update to a rev equal to the current bookmark
-    if (mark and mark not in refs and
-        current and refs[current] == repo.changectx('.').node()):
-        return
-    if mark not in refs:
+    if mark not in repo._bookmarks:
         mark = ''
     if not valid(mark):
         raise util.Abort(_("bookmark '%s' contains illegal "
@@ -122,6 +116,15 @@
         wlock.release()
     repo._bookmarkcurrent = mark
 
+def updatecurrentbookmark(repo, oldnode, curbranch):
+    try:
+        update(repo, oldnode, repo.branchtags()[curbranch])
+    except KeyError:
+        if curbranch == "default": # no default branch!
+            update(repo, oldnode, repo.lookup("tip"))
+        else:
+            raise util.Abort(_("branch %s not found") % curbranch)
+
 def update(repo, parents, node):
     marks = repo._bookmarks
     update = False
@@ -163,6 +166,28 @@
     finally:
         w.release()
 
+def updatefromremote(ui, repo, remote):
+    ui.debug("checking for updated bookmarks\n")
+    rb = remote.listkeys('bookmarks')
+    changed = False
+    for k in rb.keys():
+        if k in repo._bookmarks:
+            nr, nl = rb[k], repo._bookmarks[k]
+            if nr in repo:
+                cr = repo[nr]
+                cl = repo[nl]
+                if cl.rev() >= cr.rev():
+                    continue
+                if cr in cl.descendants():
+                    repo._bookmarks[k] = cr.node()
+                    changed = True
+                    ui.status(_("updating bookmark %s\n") % k)
+                else:
+                    ui.warn(_("not updating divergent"
+                                   " bookmark %s\n") % k)
+    if changed:
+        write(repo)
+
 def diff(ui, repo, remote):
     ui.status(_("searching for changed bookmarks\n"))
 
--- a/mercurial/bundlerepo.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/bundlerepo.py	Fri Apr 01 12:21:59 2011 +0200
@@ -15,7 +15,7 @@
 from i18n import _
 import os, struct, tempfile, shutil
 import changegroup, util, mdiff, discovery
-import localrepo, changelog, manifest, filelog, revlog, error
+import localrepo, changelog, manifest, filelog, revlog, error, url
 
 class bundlerevlog(revlog.revlog):
     def __init__(self, opener, indexfile, bundle,
@@ -274,9 +274,9 @@
             cwd = os.path.join(cwd,'')
             if parentpath.startswith(cwd):
                 parentpath = parentpath[len(cwd):]
-    path = util.drop_scheme('file', path)
-    if path.startswith('bundle:'):
-        path = util.drop_scheme('bundle', path)
+    u = url.url(path)
+    path = u.localpath()
+    if u.scheme == 'bundle':
         s = path.split("+", 1)
         if len(s) == 1:
             repopath, bundlename = parentpath, s[0]
@@ -286,15 +286,17 @@
         repopath, bundlename = parentpath, path
     return bundlerepository(ui, repopath, bundlename)
 
-def getremotechanges(ui, repo, other, revs=None, bundlename=None, force=False):
-    tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force)
+def getremotechanges(ui, repo, other, revs=None, bundlename=None,
+                     force=False, usecommon=False):
+    tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force,
+                                       commononly=usecommon)
     common, incoming, rheads = tmp
     if not incoming:
         try:
             os.unlink(bundlename)
         except:
             pass
-        return other, None, None
+        return other, None, None, None
 
     bundle = None
     if bundlename or not other.local():
@@ -303,7 +305,9 @@
         if revs is None and other.capable('changegroupsubset'):
             revs = rheads
 
-        if revs is None:
+        if usecommon:
+            cg = other.getbundle('incoming', common=common, heads=revs)
+        elif revs is None:
             cg = other.changegroup(incoming, "incoming")
         else:
             cg = other.changegroupsubset(incoming, revs, 'incoming')
@@ -315,5 +319,5 @@
         if not other.local():
             # use the created uncompressed bundlerepo
             other = bundlerepository(ui, repo.root, fname)
-    return (other, incoming, bundle)
+    return (other, common, incoming, bundle)
 
--- a/mercurial/changegroup.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/changegroup.py	Fri Apr 01 12:21:59 2011 +0200
@@ -49,15 +49,6 @@
     "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
 }
 
-def collector(cl, mmfs, files):
-    # Gather information about changeset nodes going out in a bundle.
-    # We want to gather manifests needed and filelogs affected.
-    def collect(node):
-        c = cl.read(node)
-        files.update(c[3])
-        mmfs.setdefault(c[0], node)
-    return collect
-
 # hgweb uses this list to communicate its preferred type
 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
 
@@ -203,3 +194,18 @@
     if version != '10':
         raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
     return unbundle10(fh, alg)
+
+class bundle10(object):
+    def __init__(self, lookup):
+        self._lookup = lookup
+    def close(self):
+        return closechunk()
+    def fileheader(self, fname):
+        return chunkheader(len(fname)) + fname
+    def revchunk(self, revlog, node='', p1='', p2='', prefix='', data=''):
+        linknode = self._lookup(revlog, node)
+        meta = node + p1 + p2 + linknode + prefix
+        l = len(meta) + len(data)
+        yield chunkheader(l)
+        yield meta
+        yield data
--- a/mercurial/cmdutil.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/cmdutil.py	Fri Apr 01 12:21:59 2011 +0200
@@ -230,7 +230,7 @@
 def make_file(repo, pat, node=None,
               total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
 
-    writable = 'w' in mode or 'a' in mode
+    writable = mode not in ('r', 'rb')
 
     if not pat or pat == '-':
         fp = writable and sys.stdout or sys.stdin
--- a/mercurial/commands.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/commands.py	Fri Apr 01 12:21:59 2011 +0200
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from node import hex, nullid, nullrev, short
+from node import hex, bin, nullid, nullrev, short
 from lock import release
 from i18n import _, gettext
 import os, re, sys, difflib, time, tempfile
@@ -13,7 +13,7 @@
 import patch, help, mdiff, url, encoding, templatekw, discovery
 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
 import merge as mergemod
-import minirst, revset
+import minirst, revset, templatefilters
 import dagparser
 
 # Commands start here, listed alphabetically
@@ -126,8 +126,12 @@
         lastfunc = funcmap[-1]
         funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
 
+    def bad(x, y):
+        raise util.Abort("%s: %s" % (x, y))
+
     ctx = cmdutil.revsingle(repo, opts.get('rev'))
     m = cmdutil.match(repo, pats, opts)
+    m.bad = bad
     follow = not opts.get('no_follow')
     for abs in ctx.walk(m):
         fctx = ctx[abs]
@@ -303,7 +307,8 @@
     return 0
 
 def bisect(ui, repo, rev=None, extra=None, command=None,
-               reset=None, good=None, bad=None, skip=None, noupdate=None):
+               reset=None, good=None, bad=None, skip=None, extend=None,
+               noupdate=None):
     """subdivision search of changesets
 
     This command helps to find changesets which introduce problems. To
@@ -326,6 +331,17 @@
 
     Returns 0 on success.
     """
+    def extendbisectrange(nodes, good):
+        # bisect is incomplete when it ends on a merge node and
+        # one of the parent was not checked.
+        parents = repo[nodes[0]].parents()
+        if len(parents) > 1:
+            side = good and state['bad'] or state['good']
+            num = len(set(i.node() for i in parents) & set(side))
+            if num == 1:
+                 return parents[0].ancestor(parents[1])
+        return None
+
     def print_result(nodes, good):
         displayer = cmdutil.show_changeset(ui, repo, {})
         if len(nodes) == 1:
@@ -336,14 +352,12 @@
                 ui.write(_("The first bad revision is:\n"))
             displayer.show(repo[nodes[0]])
             parents = repo[nodes[0]].parents()
-            if len(parents) > 1:
-                side = good and state['bad'] or state['good']
-                num = len(set(i.node() for i in parents) & set(side))
-                if num == 1:
-                    common = parents[0].ancestor(parents[1])
-                    ui.write(_('Not all ancestors of this changeset have been'
-                               ' checked.\nTo check the other ancestors, start'
-                               ' from the common ancestor, %s.\n' % common))
+            extendnode = extendbisectrange(nodes, good)
+            if extendnode is not None:
+                ui.write(_('Not all ancestors of this changeset have been'
+                           ' checked.\nUse bisect --extend to continue the '
+                           'bisection from\nthe common ancestor, %s.\n')
+                         % short(extendnode.node()))
         else:
             # multiple possible revisions
             if good:
@@ -376,7 +390,7 @@
             bad = True
         else:
             reset = True
-    elif extra or good + bad + skip + reset + bool(command) > 1:
+    elif extra or good + bad + skip + reset + extend + bool(command) > 1:
         raise util.Abort(_('incompatible arguments'))
 
     if reset:
@@ -440,6 +454,18 @@
 
     # actually bisect
     nodes, changesets, good = hbisect.bisect(repo.changelog, state)
+    if extend:
+        if not changesets:
+            extendnode = extendbisectrange(nodes, good)
+            if extendnode is not None:
+                ui.write(_("Extending search to changeset %d:%s\n"
+                         % (extendnode.rev(), short(extendnode.node()))))
+                if noupdate:
+                    return
+                cmdutil.bail_if_changed(repo)
+                return hg.clean(repo, extendnode.node())
+        raise util.Abort(_("nothing to extend"))
+
     if changesets == 0:
         print_result(nodes, good)
     else:
@@ -1175,6 +1201,7 @@
         if len(items) > 1 or items and sections:
             raise util.Abort(_('only one config item permitted'))
     for section, name, value in ui.walkconfig(untrusted=untrusted):
+        value = str(value).replace('\n', '\\n')
         sectname = section + '.' + name
         if values:
             for v in values:
@@ -1191,6 +1218,81 @@
                      ui.configsource(section, name, untrusted))
             ui.write('%s=%s\n' % (sectname, value))
 
+def debugknown(ui, repopath, *ids, **opts):
+    """test whether node ids are known to a repo
+
+    Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
+    indicating unknown/known.
+    """
+    repo = hg.repository(ui, repopath)
+    if not repo.capable('known'):
+        raise util.Abort("known() not supported by target repository")
+    flags = repo.known([bin(s) for s in ids])
+    ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
+
+def debugbundle(ui, bundlepath, all=None, **opts):
+    """lists the contents of a bundle"""
+    f = url.open(ui, bundlepath)
+    try:
+        gen = changegroup.readbundle(f, bundlepath)
+        if all:
+            ui.write("format: id, p1, p2, cset, len(delta)\n")
+
+            def showchunks(named):
+                ui.write("\n%s\n" % named)
+                while 1:
+                    chunkdata = gen.parsechunk()
+                    if not chunkdata:
+                        break
+                    node = chunkdata['node']
+                    p1 = chunkdata['p1']
+                    p2 = chunkdata['p2']
+                    cs = chunkdata['cs']
+                    delta = chunkdata['data']
+                    ui.write("%s %s %s %s %s\n" %
+                             (hex(node), hex(p1), hex(p2),
+                              hex(cs), len(delta)))
+
+            showchunks("changelog")
+            showchunks("manifest")
+            while 1:
+                fname = gen.chunk()
+                if not fname:
+                    break
+                showchunks(fname)
+        else:
+            while 1:
+                chunkdata = gen.parsechunk()
+                if not chunkdata:
+                    break
+                node = chunkdata['node']
+                ui.write("%s\n" % hex(node))
+    finally:
+        f.close()
+
+def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
+    """retrieves a bundle from a repo
+
+    Every ID must be a full-length hex node id string. Saves the bundle to the
+    given file.
+    """
+    repo = hg.repository(ui, repopath)
+    if not repo.capable('getbundle'):
+        raise util.Abort("getbundle() not supported by target repository")
+    args = {}
+    if common:
+        args['common'] = [bin(s) for s in common]
+    if head:
+        args['heads'] = [bin(s) for s in head]
+    bundle = repo.getbundle('debug', **args)
+
+    bundletype = opts.get('type', 'bzip2').lower()
+    btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
+    bundletype = btypes.get(bundletype)
+    if bundletype not in changegroup.bundletypes:
+        raise util.Abort(_('unknown bundle type specified with --type'))
+    changegroup.writebundle(bundle, bundlepath, bundletype)
+
 def debugpushkey(ui, repopath, namespace, *keyinfo):
     '''access the pushkey key/value protocol
 
@@ -1214,7 +1316,7 @@
 def debugrevspec(ui, repo, expr):
     '''parse and apply a revision specification'''
     if ui.verbose:
-        tree = revset.parse(expr)
+        tree = revset.parse(expr)[0]
         ui.note(tree, "\n")
     func = revset.match(expr)
     for c in func(repo, range(len(repo))):
@@ -1238,11 +1340,15 @@
     finally:
         wlock.release()
 
-def debugstate(ui, repo, nodates=None):
+def debugstate(ui, repo, nodates=None, datesort=None):
     """show the contents of the current dirstate"""
     timestr = ""
     showdate = not nodates
-    for file_, ent in sorted(repo.dirstate._map.iteritems()):
+    if datesort:
+        keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
+    else:
+        keyfunc = None # sort by filename
+    for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
         if showdate:
             if ent[3] == -1:
                 # Pad or slice to locale representation
@@ -1457,45 +1563,6 @@
         ui.write(_(" (templates seem to have been installed incorrectly)\n"))
         problems += 1
 
-    # patch
-    ui.status(_("Checking patch...\n"))
-    patchproblems = 0
-    a = "1\n2\n3\n4\n"
-    b = "1\n2\n3\ninsert\n4\n"
-    fa = writetemp(a)
-    d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
-        os.path.basename(fa))
-    fd = writetemp(d)
-
-    files = {}
-    try:
-        patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
-    except util.Abort, e:
-        ui.write(_(" patch call failed:\n"))
-        ui.write(" " + str(e) + "\n")
-        patchproblems += 1
-    else:
-        if list(files) != [os.path.basename(fa)]:
-            ui.write(_(" unexpected patch output!\n"))
-            patchproblems += 1
-        a = open(fa).read()
-        if a != b:
-            ui.write(_(" patch test failed!\n"))
-            patchproblems += 1
-
-    if patchproblems:
-        if ui.config('ui', 'patch'):
-            ui.write(_(" (Current patch tool may be incompatible with patch,"
-                       " or misconfigured. Please check your configuration"
-                       " file)\n"))
-        else:
-            ui.write(_(" Internal patcher failure, please report this error"
-                       " to http://mercurial.selenic.com/wiki/BugTracker\n"))
-    problems += patchproblems
-
-    os.unlink(fa)
-    os.unlink(fd)
-
     # editor
     ui.status(_("Checking commit editor...\n"))
     editor = ui.geteditor()
@@ -1555,6 +1622,21 @@
         line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
         ui.write("%s\n" % line.rstrip())
 
+def debugwireargs(ui, repopath, *vals, **opts):
+    repo = hg.repository(hg.remoteui(ui, opts), repopath)
+    for opt in remoteopts:
+        del opts[opt[1]]
+    args = {}
+    for k, v in opts.iteritems():
+        if v:
+            args[k] = v
+    # run twice to check that we don't mess up the stream for the next command
+    res1 = repo.debugwireargs(*vals, **args)
+    res2 = repo.debugwireargs(*vals, **args)
+    ui.write("%s\n" % res1)
+    if res1 != res2:
+        ui.warn("%s\n" % res2)
+
 def diff(ui, repo, *pats, **opts):
     """diff repository (or selected files)
 
@@ -1595,7 +1677,7 @@
         msg = _('cannot specify --rev and --change at the same time')
         raise util.Abort(msg)
     elif change:
-        node2 = repo.lookup(change)
+        node2 = cmdutil.revsingle(repo, change, None).node()
         node1 = repo[node2].parents()[0].node()
     else:
         node1, node2 = cmdutil.revpair(repo, revs)
@@ -1962,7 +2044,7 @@
     Returns 0 if successful.
     """
     option_lists = []
-    textwidth = ui.termwidth() - 2
+    textwidth = min(ui.termwidth(), 80) - 2
 
     def addglobalopts(aliases):
         if ui.verbose:
@@ -2141,6 +2223,8 @@
                    'extensions\n'))
 
     help.addtopichook('revsets', revset.makedoc)
+    help.addtopichook('templates', templatekw.makedoc)
+    help.addtopichook('templates', templatefilters.makedoc)
 
     if name and name != 'shortlist':
         i = None
@@ -2267,6 +2351,7 @@
     output = []
 
     revs = []
+    bms = []
     if source:
         source, branches = hg.parseurl(ui.expandpath(source))
         repo = hg.repository(ui, source)
@@ -2277,10 +2362,19 @@
             rev = revs[0]
         if not rev:
             rev = "tip"
-        if num or branch or tags or bookmarks:
-            raise util.Abort(_("can't query remote revision number,"
-                             " branch, tags, or bookmarks"))
-        output = [hexfunc(repo.lookup(rev))]
+        if num or branch or tags:
+            raise util.Abort(
+                _("can't query remote revision number, branch, or tags"))
+
+        remoterev = repo.lookup(rev)
+        if default or id:
+            output = [hexfunc(remoterev)]
+
+        if 'bookmarks' in repo.listkeys('namespaces'):
+            hexremoterev = hex(remoterev)
+            bms = [bm for bm, bmrev in repo.listkeys('bookmarks').iteritems()
+                   if bmrev == hexremoterev]
+
     elif not rev:
         ctx = repo[None]
         parents = ctx.parents()
@@ -2300,6 +2394,9 @@
         if num:
             output.append(str(ctx.rev()))
 
+    if repo.local():
+        bms = ctx.bookmarks()
+
     if repo.local() and default and not ui.quiet:
         b = ctx.branch()
         if b != 'default':
@@ -2310,8 +2407,9 @@
         if t:
             output.append(t)
 
+    if default and not ui.quiet:
         # multiple bookmarks for a single parent separated by '/'
-        bm = '/'.join(ctx.bookmarks())
+        bm = '/'.join(bms)
         if bm:
             output.append(bm)
 
@@ -2322,7 +2420,7 @@
         output.extend(ctx.tags())
 
     if bookmarks:
-        output.extend(ctx.bookmarks())
+        output.extend(bms)
 
     ui.write("%s\n" % ' '.join(output))
 
@@ -2891,7 +2989,13 @@
         else:
             ui.status(_("not updating, since new heads added\n"))
     if modheads > 1:
-        ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
+        currentbranchheads = len(repo.branchheads())
+        if currentbranchheads == modheads:
+            ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
+        elif currentbranchheads > 1:
+            ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
+        else:
+            ui.status(_("(run 'hg heads' to see heads)\n"))
     else:
         ui.status(_("(run 'hg update' to get a working copy)\n"))
 
@@ -2938,6 +3042,7 @@
             raise util.Abort(err)
 
     modheads = repo.pull(other, heads=revs, force=opts.get('force'))
+    bookmarks.updatefromremote(ui, repo, other)
     if checkout:
         checkout = str(repo.changelog.rev(other.lookup(checkout)))
     repo._subtoppath = source
@@ -3998,15 +4103,16 @@
     fnames = (fname1,) + fnames
 
     lock = repo.lock()
+    wc = repo['.']
     try:
         for fname in fnames:
             f = url.open(ui, fname)
             gen = changegroup.readbundle(f, fname)
             modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
                                            lock=lock)
+        bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
     finally:
         lock.release()
-
     return postincoming(ui, repo, modheads, opts.get('update'), None)
 
 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
@@ -4053,7 +4159,7 @@
     if rev and node:
         raise util.Abort(_("please specify just one revision"))
 
-    if not rev:
+    if rev is None or rev == '':
         rev = node
 
     # if we defined a bookmark, we have to remember the original bookmark name
@@ -4269,6 +4375,7 @@
           ('g', 'good', False, _('mark changeset good')),
           ('b', 'bad', False, _('mark changeset bad')),
           ('s', 'skip', False, _('skip testing changeset')),
+          ('e', 'extend', False, _('extend the bisect range')),
           ('c', 'command', '',
            _('use command to check changeset state'), _('CMD')),
           ('U', 'noupdate', False, _('do not update to target'))],
@@ -4359,6 +4466,11 @@
           ('n', 'new-file', None, _('add new file at each rev')),
          ],
          _('[OPTION]... TEXT')),
+    "debugbundle":
+        (debugbundle,
+         [('a', 'all', None, _('show all details')),
+          ],
+         _('FILE')),
     "debugcheckstate": (debugcheckstate, [], ''),
     "debugcommands": (debugcommands, [], _('[COMMAND]')),
     "debugcomplete":
@@ -4379,12 +4491,20 @@
          _('[-e] DATE [RANGE]')),
     "debugdata": (debugdata, [], _('FILE REV')),
     "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
+    "debuggetbundle":
+        (debuggetbundle,
+         [('H', 'head', [], _('id of head node'), _('ID')),
+          ('C', 'common', [], _('id of common node'), _('ID')),
+          ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
+         ],
+         _('REPO FILE [-H|-C ID]...')),
     "debugignore": (debugignore, [], ''),
     "debugindex": (debugindex,
                    [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
                    _('FILE')),
     "debugindexdot": (debugindexdot, [], _('FILE')),
     "debuginstall": (debuginstall, [], ''),
+    "debugknown": (debugknown, [], _('REPO ID...')),
     "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
     "debugrebuildstate":
         (debugrebuildstate,
@@ -4402,7 +4522,8 @@
         (debugsetparents, [], _('REV1 [REV2]')),
     "debugstate":
         (debugstate,
-         [('', 'nodates', None, _('do not display the saved mtime'))],
+         [('', 'nodates', None, _('do not display the saved mtime')),
+          ('', 'datesort', None, _('sort by saved mtime'))],
          _('[OPTION]...')),
     "debugsub":
         (debugsub,
@@ -4410,6 +4531,12 @@
            _('revision to check'), _('REV'))],
          _('[-r REV] [REV]')),
     "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
+    "debugwireargs":
+        (debugwireargs,
+         [('', 'three', '', 'three'),
+          ('', 'four', '', 'four'),
+          ] + remoteopts,
+         _('REPO [OPTIONS]... [ONE [TWO]]')),
     "^diff":
         (diff,
          [('r', 'rev', [],
@@ -4743,6 +4870,7 @@
 }
 
 norepo = ("clone init version help debugcommands debugcomplete"
-          " debugdate debuginstall debugfsinfo debugpushkey")
+          " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
+          " debugknown debuggetbundle debugbundle")
 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
                 " debugdata debugindex debugindexdot")
--- a/mercurial/config.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/config.py	Fri Apr 01 12:21:59 2011 +0200
@@ -138,5 +138,5 @@
 
     def read(self, path, fp=None, sections=None, remap=None):
         if not fp:
-            fp = open(path)
+            fp = util.posixfile(path)
         self.parse(path, fp.read(), sections, remap, self.read)
--- a/mercurial/dirstate.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/dirstate.py	Fri Apr 01 12:21:59 2011 +0200
@@ -49,6 +49,7 @@
         self._rootdir = os.path.join(root, '')
         self._dirty = False
         self._dirtypl = False
+        self._lastnormaltime = None
         self._ui = ui
 
     @propertycache
@@ -236,6 +237,7 @@
                 "_ignore"):
             if a in self.__dict__:
                 delattr(self, a)
+        self._lastnormaltime = None
         self._dirty = False
 
     def copy(self, source, dest):
@@ -281,9 +283,15 @@
         self._dirty = True
         self._addpath(f)
         s = os.lstat(self._join(f))
-        self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
+        mtime = int(s.st_mtime)
+        self._map[f] = ('n', s.st_mode, s.st_size, mtime)
         if f in self._copymap:
             del self._copymap[f]
+        if mtime > self._lastnormaltime:
+            # Remember the most recent modification timeslot for status(),
+            # to make sure we won't miss future size-preserving file content
+            # modifications that happen within the same timeslot.
+            self._lastnormaltime = mtime
 
     def normallookup(self, f):
         '''Mark a file normal, but possibly dirty.'''
@@ -397,6 +405,7 @@
             delattr(self, "_dirs")
         self._copymap = {}
         self._pl = [nullid, nullid]
+        self._lastnormaltime = None
         self._dirty = True
 
     def rebuild(self, parent, files):
@@ -444,6 +453,7 @@
             write(f)
         st.write(cs.getvalue())
         st.rename()
+        self._lastnormaltime = None
         self._dirty = self._dirtypl = False
 
     def _dirignore(self, f):
@@ -680,6 +690,7 @@
                 # lines are an expansion of "islink => checklink"
                 # where islink means "is this a link?" and checklink
                 # means "can we check links?".
+                mtime = int(st.st_mtime)
                 if (size >= 0 and
                     (size != st.st_size
                      or ((mode ^ st.st_mode) & 0100 and self._checkexec))
@@ -687,9 +698,15 @@
                     or size == -2 # other parent
                     or fn in self._copymap):
                     madd(fn)
-                elif (time != int(st.st_mtime)
+                elif (mtime != time
                       and (mode & lnkkind != lnkkind or self._checklink)):
                     ladd(fn)
+                elif mtime == self._lastnormaltime:
+                    # fn may have been changed in the same timeslot without
+                    # changing its size. This can happen if we quickly do
+                    # multiple commits in a single transaction.
+                    # Force lookup, so we don't miss such a racy file change.
+                    ladd(fn)
                 elif listclean:
                     cadd(fn)
             elif state == 'm':
--- a/mercurial/discovery.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/discovery.py	Fri Apr 01 12:21:59 2011 +0200
@@ -9,9 +9,10 @@
 from i18n import _
 import util, error
 
-def findcommonincoming(repo, remote, heads=None, force=False):
-    """Return a tuple (common, missing roots, heads) used to identify
-    missing nodes from remote.
+def findcommonincoming(repo, remote, heads=None, force=False, commononly=False):
+    """Return a tuple (common, missing, heads) used to identify missing nodes
+    from remote. "missing" is either a boolean indicating if any nodes are missing
+    (when commononly=True), or else a list of the root nodes of the missing set.
 
     If a list of heads is specified, return only nodes which are heads
     or ancestors of these heads.
@@ -36,6 +37,13 @@
     # and start by examining the heads
     repo.ui.status(_("searching for changes\n"))
 
+    if commononly:
+        myheads = repo.heads()
+        known = remote.known(myheads)
+        if util.all(known):
+            hasincoming = set(heads).difference(set(myheads)) and True
+            return myheads, hasincoming, heads
+
     unknown = []
     for h in heads:
         if h not in m:
--- a/mercurial/help.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/help.py	Fri Apr 01 12:21:59 2011 +0200
@@ -115,3 +115,19 @@
 
 def addtopichook(topic, rewriter):
     helphooks.setdefault(topic, []).append(rewriter)
+
+def makeitemsdoc(topic, doc, marker, items):
+    """Extract docstring from the items key to function mapping, build a
+    .single documentation block and use it to overwrite the marker in doc
+    """
+    entries = []
+    for name in sorted(items):
+        text = (items[name].__doc__ or '').rstrip()
+        if not text:
+            continue
+        text = gettext(text)
+        lines = text.splitlines()
+        lines[1:] = [('  ' + l.strip()) for l in lines[1:]]
+        entries.append('\n'.join(lines))
+    entries = '\n\n'.join(entries)
+    return doc.replace(marker, entries)
--- a/mercurial/help/templates.txt	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/help/templates.txt	Fri Apr 01 12:21:59 2011 +0200
@@ -23,52 +23,7 @@
 keywords depends on the exact context of the templater. These
 keywords are usually available for templating a log-like command:
 
-:author: String. The unmodified author of the changeset.
-
-:branch: String. The name of the branch on which the changeset was
-    committed.
-
-:branches: List of strings. The name of the branch on which the
-    changeset was committed. Will be empty if the branch name was
-    default.
-
-:children: List of strings. The children of the changeset.
-
-:date: Date information. The date when the changeset was committed.
-
-:desc: String. The text of the changeset description.
-
-:diffstat: String. Statistics of changes with the following format:
-    "modified files: +added/-removed lines"
-
-:files: List of strings. All files modified, added, or removed by this
-    changeset.
-
-:file_adds: List of strings. Files added by this changeset.
-
-:file_copies: List of strings. Files copied in this changeset with
-    their sources.
-
-:file_copies_switch: List of strings. Like "file_copies" but displayed
-    only if the --copied switch is set.
-
-:file_mods: List of strings. Files modified by this changeset.
-
-:file_dels: List of strings. Files removed by this changeset.
-
-:node: String. The changeset identification hash, as a 40 hexadecimal
-    digit string.
-
-:parents: List of strings. The parents of the changeset.
-
-:rev: Integer. The repository-local changeset revision number.
-
-:tags: List of strings. Any tags associated with the changeset.
-
-:latesttag: String. Most recent global tag in the ancestors of this
-    changeset.
-
-:latesttagdistance: Integer. Longest path to the latest tag.
+.. keywordsmarker
 
 The "date" keyword does not produce human-readable output. If you
 want to use a date in your output, you can use a filter to process
@@ -82,82 +37,4 @@
 
 List of filters:
 
-:addbreaks: Any text. Add an XHTML "<br />" tag before the end of
-    every line except the last.
-
-:age: Date. Returns a human-readable date/time difference between the
-    given date/time and the current date/time.
-
-:basename: Any text. Treats the text as a path, and returns the last
-    component of the path after splitting by the path separator
-    (ignoring trailing separators). For example, "foo/bar/baz" becomes
-    "baz" and "foo/bar//" becomes "bar".
-
-:stripdir: Treat the text as path and strip a directory level, if
-    possible. For example, "foo" and "foo/bar" becomes "foo".
-
-:date: Date. Returns a date in a Unix date format, including the
-    timezone: "Mon Sep 04 15:13:13 2006 0700".
-
-:domain: Any text. Finds the first string that looks like an email
-    address, and extracts just the domain component. Example: ``User
-    <user@example.com>`` becomes ``example.com``.
-
-:email: Any text. Extracts the first string that looks like an email
-    address. Example: ``User <user@example.com>`` becomes
-    ``user@example.com``.
-
-:escape: Any text. Replaces the special XML/XHTML characters "&", "<"
-    and ">" with XML entities.
-
-:hex: Any text. Convert a binary Mercurial node identifier into
-    its long hexadecimal representation.
-
-:fill68: Any text. Wraps the text to fit in 68 columns.
-
-:fill76: Any text. Wraps the text to fit in 76 columns.
-
-:firstline: Any text. Returns the first line of text.
-
-:nonempty: Any text. Returns '(none)' if the string is empty.
-
-:hgdate: Date. Returns the date as a pair of numbers: "1157407993
-    25200" (Unix timestamp, timezone offset).
-
-:isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
-    +0200".
-
-:isodatesec: Date. Returns the date in ISO 8601 format, including
-    seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
-    filter.
-
-:localdate: Date. Converts a date to local date.
-
-:obfuscate: Any text. Returns the input text rendered as a sequence of
-    XML entities.
-
-:person: Any text. Returns the text before an email address.
-
-:rfc822date: Date. Returns a date using the same format used in email
-    headers: "Tue, 18 Aug 2009 13:00:13 +0200".
-
-:rfc3339date: Date. Returns a date using the Internet date format
-    specified in RFC 3339: "2009-08-18T13:00:13+02:00".
-
-:short: Changeset hash. Returns the short form of a changeset hash,
-    i.e. a 12 hexadecimal digit string.
-
-:shortdate: Date. Returns a date like "2006-09-18".
-
-:stringify: Any type. Turns the value into text by converting values into
-    text and concatenating them.
-
-:strip: Any text. Strips all leading and trailing whitespace.
-
-:tabindent: Any text. Returns the text, with every line except the
-     first starting with a tab character.
-
-:urlescape: Any text. Escapes all "special" characters. For example,
-    "foo bar" becomes "foo%20bar".
-
-:user: Any text. Returns the user portion of an email address.
+.. filtersmarker
--- a/mercurial/hg.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/hg.py	Fri Apr 01 12:21:59 2011 +0200
@@ -9,7 +9,7 @@
 from i18n import _
 from lock import release
 from node import hex, nullid, nullrev, short
-import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
+import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo, bookmarks
 import lock, util, extensions, error, encoding, node
 import cmdutil, discovery, url
 import merge as mergemod
@@ -17,7 +17,7 @@
 import errno, os, shutil
 
 def _local(path):
-    path = util.expandpath(util.drop_scheme('file', path))
+    path = util.expandpath(url.localpath(path))
     return (os.path.isfile(path) and bundlerepo or localrepo)
 
 def addbranchrevs(lrepo, repo, branches, revs):
@@ -51,13 +51,15 @@
             revs.append(hashbranch)
     return revs, revs[0]
 
-def parseurl(url, branches=None):
+def parseurl(path, branches=None):
     '''parse url#branch, returning (url, (branch, branches))'''
 
-    if '#' not in url:
-        return url, (None, branches or [])
-    url, branch = url.split('#', 1)
-    return url, (branch, branches or [])
+    u = url.url(path)
+    if not u.fragment:
+        return path, (None, branches or [])
+    branch = u.fragment
+    u.fragment = None
+    return str(u), (branch, branches or [])
 
 schemes = {
     'bundle': bundlerepo,
@@ -69,11 +71,8 @@
 }
 
 def _lookup(path):
-    scheme = 'file'
-    if path:
-        c = path.find(':')
-        if c > 0:
-            scheme = path[:c]
+    u = url.url(path)
+    scheme = u.scheme or 'file'
     thing = schemes.get(scheme) or schemes['file']
     try:
         return thing(path)
@@ -103,15 +102,6 @@
     '''return default destination of clone if none is given'''
     return os.path.basename(os.path.normpath(source))
 
-def localpath(path):
-    if path.startswith('file://localhost/'):
-        return path[16:]
-    if path.startswith('file://'):
-        return path[7:]
-    if path.startswith('file:'):
-        return path[5:]
-    return path
-
 def share(ui, source, dest=None, update=True):
     '''create a shared repository'''
 
@@ -143,7 +133,7 @@
 
     if not os.path.isdir(root):
         os.mkdir(root)
-    os.mkdir(roothg)
+    util.makedir(roothg, notindexed=True)
 
     requirements = ''
     try:
@@ -231,8 +221,8 @@
     else:
         dest = ui.expandpath(dest)
 
-    dest = localpath(dest)
-    source = localpath(source)
+    dest = url.localpath(dest)
+    source = url.localpath(source)
 
     if os.path.exists(dest):
         if not os.path.isdir(dest):
@@ -258,7 +248,7 @@
         abspath = origsource
         copy = False
         if src_repo.cancopy() and islocal(dest):
-            abspath = os.path.abspath(util.drop_scheme('file', origsource))
+            abspath = os.path.abspath(url.localpath(origsource))
             copy = not pull and not rev
 
         if copy:
@@ -281,7 +271,7 @@
                 dir_cleanup.dir_ = hgdir
             try:
                 dest_path = hgdir
-                os.mkdir(dest_path)
+                util.makedir(dest_path, notindexed=True)
             except OSError, inst:
                 if inst.errno == errno.EEXIST:
                     dir_cleanup.close()
@@ -366,6 +356,21 @@
                 dest_repo.ui.status(_("updating to branch %s\n") % bn)
                 _update(dest_repo, uprev)
 
+        # clone all bookmarks
+        if dest_repo.local() and src_repo.capable("pushkey"):
+            rb = src_repo.listkeys('bookmarks')
+            for k, n in rb.iteritems():
+                try:
+                    m = dest_repo.lookup(n)
+                    dest_repo._bookmarks[k] = m
+                except:
+                    pass
+            if rb:
+                bookmarks.write(dest_repo)
+        elif src_repo.local() and dest_repo.capable("pushkey"):
+            for k, n in src_repo._bookmarks.iteritems():
+                dest_repo.pushkey('bookmarks', k, '', hex(n))
+
         return src_repo, dest_repo
     finally:
         release(src_lock, dest_lock)
@@ -421,14 +426,19 @@
 
     if revs:
         revs = [other.lookup(rev) for rev in revs]
-    other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs,
-                                opts["bundle"], opts["force"])
-    if incoming is None:
+    usecommon = other.capable('getbundle')
+    other, common, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other,
+                                       revs, opts["bundle"], opts["force"],
+                                       usecommon=usecommon)
+    if not incoming:
         ui.status(_("no changes found\n"))
         return subreporecurse()
 
     try:
-        chlist = other.changelog.nodesbetween(incoming, revs)[0]
+        if usecommon:
+            chlist = other.changelog.findmissing(common, revs)
+        else:
+            chlist = other.changelog.nodesbetween(incoming, revs)[0]
         displayer = cmdutil.show_changeset(ui, other, opts, buffered)
 
         # XXX once graphlog extension makes it into core,
--- a/mercurial/hgweb/common.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/hgweb/common.py	Fri Apr 01 12:21:59 2011 +0200
@@ -73,10 +73,29 @@
     def __init__(self, code, message=None, headers=[]):
         if message is None:
             message = _statusmessage(code)
-        Exception.__init__(self, code, message)
+        Exception.__init__(self)
         self.code = code
         self.message = message
         self.headers = headers
+    def __str__(self):
+        return self.message
+
+class continuereader(object):
+    def __init__(self, f, write):
+        self.f = f
+        self._write = write
+        self.continued = False
+
+    def read(self, amt=-1):
+        if not self.continued:
+            self.continued = True
+            self._write('HTTP/1.1 100 Continue\r\n\r\n')
+        return self.f.read(amt)
+
+    def __getattr__(self, attr):
+        if attr in ('close', 'readline', 'readlines', '__iter__'):
+            return getattr(self.f, attr)
+        raise AttributeError()
 
 def _statusmessage(code):
     from BaseHTTPServer import BaseHTTPRequestHandler
--- a/mercurial/hgweb/hgweb_mod.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/hgweb/hgweb_mod.py	Fri Apr 01 12:21:59 2011 +0200
@@ -17,6 +17,7 @@
 perms = {
     'changegroup': 'pull',
     'changegroupsubset': 'pull',
+    'getbundle': 'pull',
     'stream_out': 'pull',
     'listkeys': 'pull',
     'unbundle': 'push',
@@ -121,7 +122,11 @@
                     self.check_perm(req, perms[cmd])
                 return protocol.call(self.repo, req, cmd)
             except ErrorResponse, inst:
-                if cmd == 'unbundle':
+                # A client that sends unbundle without 100-continue will
+                # break if we respond early.
+                if (cmd == 'unbundle' and
+                    req.env.get('HTTP_EXPECT',
+                                '').lower() != '100-continue'):
                     req.drain()
                 req.respond(inst, protocol.HGTYPE)
                 return '0\n%s\n' % inst.message
--- a/mercurial/hgweb/hgwebdir_mod.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/hgweb/hgwebdir_mod.py	Fri Apr 01 12:21:59 2011 +0200
@@ -6,10 +6,10 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import os, re, time, urlparse
+import os, re, time
 from mercurial.i18n import _
 from mercurial import ui, hg, util, templater
-from mercurial import error, encoding
+from mercurial import error, encoding, url
 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
                    get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
 from hgweb_mod import hgweb
@@ -40,9 +40,10 @@
 def urlrepos(prefix, roothead, paths):
     """yield url paths and filesystem paths from a list of repo paths
 
-    >>> list(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
+    >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
+    >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
     [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
-    >>> list(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
+    >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
     [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
     """
     for path in paths:
@@ -76,7 +77,10 @@
             if not os.path.exists(self.conf):
                 raise util.Abort(_('config file %s not found!') % self.conf)
             u.readconfig(self.conf, remap=map, trust=True)
-            paths = u.configitems('hgweb-paths')
+            paths = []
+            for name, ignored in u.configitems('hgweb-paths'):
+                for path in u.configlist('hgweb-paths', name):
+                    paths.append((name, path))
         elif isinstance(self.conf, (list, tuple)):
             paths = self.conf
         elif isinstance(self.conf, dict):
@@ -247,6 +251,9 @@
                 # update time with local timezone
                 try:
                     r = hg.repository(self.ui, path)
+                except IOError:
+                    u.warn(_('error accessing repository at %s\n') % path)
+                    continue
                 except error.RepoError:
                     u.warn(_('error accessing repository at %s\n') % path)
                     continue
@@ -354,17 +361,9 @@
         return tmpl
 
     def updatereqenv(self, env):
-        def splitnetloc(netloc):
-            if ':' in netloc:
-                return netloc.split(':', 1)
-            else:
-                return (netloc, None)
-
         if self._baseurl is not None:
-            urlcomp = urlparse.urlparse(self._baseurl)
-            host, port = splitnetloc(urlcomp[1])
-            path = urlcomp[2]
-            env['SERVER_NAME'] = host
-            if port:
-                env['SERVER_PORT'] = port
-            env['SCRIPT_NAME'] = path
+            u = url.url(self._baseurl)
+            env['SERVER_NAME'] = u.host
+            if u.port:
+                env['SERVER_PORT'] = u.port
+            env['SCRIPT_NAME'] = '/' + u.path
--- a/mercurial/hgweb/protocol.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/hgweb/protocol.py	Fri Apr 01 12:21:59 2011 +0200
@@ -22,7 +22,7 @@
             if k == '*':
                 star = {}
                 for key in self.req.form.keys():
-                    if key not in keys:
+                    if key != 'cmd' and key not in keys:
                         star[key] = self.req.form[key][0]
                 data['*'] = star
             else:
--- a/mercurial/hgweb/server.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/hgweb/server.py	Fri Apr 01 12:21:59 2011 +0200
@@ -8,6 +8,7 @@
 
 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
 from mercurial import util, error
+from mercurial.hgweb import common
 from mercurial.i18n import _
 
 def _splitURI(uri):
@@ -111,6 +112,9 @@
         env['SERVER_PROTOCOL'] = self.request_version
         env['wsgi.version'] = (1, 0)
         env['wsgi.url_scheme'] = self.url_scheme
+        if env.get('HTTP_EXPECT', '').lower() == '100-continue':
+            self.rfile = common.continuereader(self.rfile, self.wfile.write)
+
         env['wsgi.input'] = self.rfile
         env['wsgi.errors'] = _error_logger(self)
         env['wsgi.multithread'] = isinstance(self.server,
--- a/mercurial/hgweb/wsgicgi.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/hgweb/wsgicgi.py	Fri Apr 01 12:21:59 2011 +0200
@@ -10,6 +10,7 @@
 
 import os, sys
 from mercurial import util
+from mercurial.hgweb import common
 
 def launch(application):
     util.set_binary(sys.stdin)
@@ -23,7 +24,11 @@
         if environ['PATH_INFO'].startswith(scriptname):
             environ['PATH_INFO'] = environ['PATH_INFO'][len(scriptname):]
 
-    environ['wsgi.input'] = sys.stdin
+    stdin = sys.stdin
+    if environ.get('HTTP_EXPECT', '').lower() == '100-continue':
+        stdin = common.continuereader(stdin, sys.stdout.write)
+
+    environ['wsgi.input'] = stdin
     environ['wsgi.errors'] = sys.stderr
     environ['wsgi.version'] = (1, 0)
     environ['wsgi.multithread'] = False
--- a/mercurial/httprepo.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/httprepo.py	Fri Apr 01 12:21:59 2011 +0200
@@ -9,7 +9,7 @@
 from node import nullid
 from i18n import _
 import changegroup, statichttprepo, error, url, util, wireproto
-import os, urllib, urllib2, urlparse, zlib, httplib
+import os, urllib, urllib2, zlib, httplib
 import errno, socket
 
 def zgenerator(f):
@@ -28,13 +28,13 @@
         self.path = path
         self.caps = None
         self.handler = None
-        scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
-        if query or frag:
+        u = url.url(path)
+        if u.query or u.fragment:
             raise util.Abort(_('unsupported URL component: "%s"') %
-                             (query or frag))
+                             (u.query or u.fragment))
 
         # urllib cannot handle URLs with embedded user or passwd
-        self._url, authinfo = url.getauthinfo(path)
+        self._url, authinfo = u.authinfo()
 
         self.ui = ui
         self.ui.debug('using %s\n' % self._url)
@@ -52,10 +52,13 @@
 
     # look up capabilities only when needed
 
+    def _fetchcaps(self):
+        self.caps = set(self._call('capabilities').split())
+
     def get_caps(self):
         if self.caps is None:
             try:
-                self.caps = set(self._call('capabilities').split())
+                self._fetchcaps()
             except error.RepoError:
                 self.caps = set()
             self.ui.debug('capabilities: %s\n' %
@@ -73,8 +76,7 @@
         data = args.pop('data', None)
         headers = args.pop('headers', {})
         self.ui.debug("sending %s command\n" % cmd)
-        q = {"cmd": cmd}
-        q.update(args)
+        q = [('cmd', cmd)] + sorted(args.items())
         qs = '?%s' % urllib.urlencode(q)
         cu = "%s%s" % (self._url, qs)
         req = urllib2.Request(cu, data, headers)
@@ -196,7 +198,13 @@
             inst = httpsrepository(ui, path)
         else:
             inst = httprepository(ui, path)
-        inst.between([(nullid, nullid)])
+        try:
+            # Try to do useful work when checking compatibility.
+            # Usually saves a roundtrip since we want the caps anyway.
+            inst._fetchcaps()
+        except error.RepoError:
+            # No luck, try older compatibility check.
+            inst.between([(nullid, nullid)])
         return inst
     except error.RepoError:
         ui.note('(falling back to static-http)\n')
--- a/mercurial/localrepo.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/localrepo.py	Fri Apr 01 12:21:59 2011 +0200
@@ -20,7 +20,8 @@
 propertycache = util.propertycache
 
 class localrepository(repo.repository):
-    capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey'))
+    capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey',
+                        'known', 'getbundle'))
     supportedformats = set(('revlogv1', 'parentdelta'))
     supported = supportedformats | set(('store', 'fncache', 'shared',
                                         'dotencode'))
@@ -46,7 +47,7 @@
             if create:
                 if not os.path.exists(path):
                     util.makedirs(path)
-                os.mkdir(self.path)
+                util.makedir(self.path, notindexed=True)
                 requirements = ["revlogv1"]
                 if self.ui.configbool('format', 'usestore', True):
                     os.mkdir(os.path.join(self.path, "store"))
@@ -558,6 +559,10 @@
         repo = (remote and remote.local()) and remote or self
         return repo[key].branch()
 
+    def known(self, nodes):
+        nm = self.changelog.nodemap
+        return [(n in nm) for n in nodes]
+
     def local(self):
         return True
 
@@ -1014,10 +1019,7 @@
                 raise
 
             # update bookmarks, dirstate and mergestate
-            parents = (p1, p2)
-            if p2 == nullid:
-                parents = (p1,)
-            bookmarks.update(self, parents, ret)
+            bookmarks.update(self, p1, ret)
             for f in changes[0] + changes[1]:
                 self.dirstate.normal(f)
             for f in changes[2]:
@@ -1320,20 +1322,24 @@
     def pull(self, remote, heads=None, force=False):
         lock = self.lock()
         try:
+            usecommon = remote.capable('getbundle')
             tmp = discovery.findcommonincoming(self, remote, heads=heads,
-                                               force=force)
+                                               force=force, commononly=usecommon)
             common, fetch, rheads = tmp
             if not fetch:
                 self.ui.status(_("no changes found\n"))
                 result = 0
             else:
-                if heads is None and fetch == [nullid]:
+                if heads is None and list(common) == [nullid]:
                     self.ui.status(_("requesting all changes\n"))
                 elif heads is None and remote.capable('changegroupsubset'):
                     # issue1320, avoid a race if remote changed after discovery
                     heads = rheads
 
-                if heads is None:
+                if usecommon:
+                    cg = remote.getbundle('pull', common=common,
+                                          heads=heads or rheads)
+                elif heads is None:
                     cg = remote.changegroup(fetch, 'pull')
                 elif not remote.capable('changegroupsubset'):
                     raise util.Abort(_("partial pull cannot be done because "
@@ -1346,27 +1352,6 @@
         finally:
             lock.release()
 
-        self.ui.debug("checking for updated bookmarks\n")
-        rb = remote.listkeys('bookmarks')
-        changed = False
-        for k in rb.keys():
-            if k in self._bookmarks:
-                nr, nl = rb[k], self._bookmarks[k]
-                if nr in self:
-                    cr = self[nr]
-                    cl = self[nl]
-                    if cl.rev() >= cr.rev():
-                        continue
-                    if cr in cl.descendants():
-                        self._bookmarks[k] = cr.node()
-                        changed = True
-                        self.ui.status(_("updating bookmark %s\n") % k)
-                    else:
-                        self.ui.warn(_("not updating divergent"
-                                       " bookmark %s\n") % k)
-        if changed:
-            bookmarks.write(self)
-
         return result
 
     def checkpush(self, force, revs):
@@ -1446,7 +1431,7 @@
             for node in nodes:
                 self.ui.debug("%s\n" % hex(node))
 
-    def changegroupsubset(self, bases, heads, source, extranodes=None):
+    def changegroupsubset(self, bases, heads, source):
         """Compute a changegroup consisting of all the nodes that are
         descendents of any of the bases and ancestors of any of the heads.
         Return a chunkbuffer object whose read() method will return
@@ -1458,214 +1443,127 @@
 
         Another wrinkle is doing the reverse, figuring out which changeset in
         the changegroup a particular filenode or manifestnode belongs to.
-
-        The caller can specify some nodes that must be included in the
-        changegroup using the extranodes argument.  It should be a dict
-        where the keys are the filenames (or 1 for the manifest), and the
-        values are lists of (node, linknode) tuples, where node is a wanted
-        node and linknode is the changelog node that should be transmitted as
-        the linkrev.
         """
-
-        # Set up some initial variables
-        # Make it easy to refer to self.changelog
         cl = self.changelog
-        # Compute the list of changesets in this changegroup.
-        # Some bases may turn out to be superfluous, and some heads may be
-        # too.  nodesbetween will return the minimal set of bases and heads
-        # necessary to re-create the changegroup.
         if not bases:
             bases = [nullid]
-        msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
+        csets, bases, heads = cl.nodesbetween(bases, heads)
+        # We assume that all ancestors of bases are known
+        common = set(cl.ancestors(*[cl.rev(n) for n in bases]))
+        return self._changegroupsubset(common, csets, heads, source)
+
+    def getbundle(self, source, heads=None, common=None):
+        """Like changegroupsubset, but returns the set difference between the
+        ancestors of heads and the ancestors common.
+
+        If heads is None, use the local heads. If common is None, use [nullid].
 
-        if extranodes is None:
-            # can we go through the fast path ?
-            heads.sort()
-            allheads = self.heads()
-            allheads.sort()
-            if heads == allheads:
-                return self._changegroup(msng_cl_lst, source)
+        The nodes in common might not all be known locally due to the way the
+        current discovery protocol works.
+        """
+        cl = self.changelog
+        if common:
+            nm = cl.nodemap
+            common = [n for n in common if n in nm]
+        else:
+            common = [nullid]
+        if not heads:
+            heads = cl.heads()
+        common, missing = cl.findcommonmissing(common, heads)
+        return self._changegroupsubset(common, missing, heads, source)
+
+    def _changegroupsubset(self, commonrevs, csets, heads, source):
+
+        cl = self.changelog
+        mf = self.manifest
+        mfs = {} # needed manifests
+        fnodes = {} # needed file nodes
+        changedfiles = set()
+        fstate = ['', {}]
+        count = [0]
+
+        # can we go through the fast path ?
+        heads.sort()
+        if heads == sorted(self.heads()):
+            return self._changegroup(csets, source)
 
         # slow path
         self.hook('preoutgoing', throw=True, source=source)
-
-        self.changegroupinfo(msng_cl_lst, source)
-
-        # We assume that all ancestors of bases are known
-        commonrevs = set(cl.ancestors(*[cl.rev(n) for n in bases]))
+        self.changegroupinfo(csets, source)
 
-        # Make it easy to refer to self.manifest
-        mnfst = self.manifest
-        # We don't know which manifests are missing yet
-        msng_mnfst_set = {}
-        # Nor do we know which filenodes are missing.
-        msng_filenode_set = {}
-
-        # A changeset always belongs to itself, so the changenode lookup
-        # function for a changenode is identity.
-        def identity(x):
-            return x
+        # filter any nodes that claim to be part of the known set
+        def prune(revlog, missing):
+            for n in missing:
+                if revlog.linkrev(revlog.rev(n)) not in commonrevs:
+                    yield n
 
-        # A function generating function that sets up the initial environment
-        # the inner function.
-        def filenode_collector(changedfiles):
-            # This gathers information from each manifestnode included in the
-            # changegroup about which filenodes the manifest node references
-            # so we can include those in the changegroup too.
-            #
-            # It also remembers which changenode each filenode belongs to.  It
-            # does this by assuming the a filenode belongs to the changenode
-            # the first manifest that references it belongs to.
-            def collect_msng_filenodes(mnfstnode):
-                r = mnfst.rev(mnfstnode)
-                if mnfst.deltaparent(r) in mnfst.parentrevs(r):
-                    # If the previous rev is one of the parents,
-                    # we only need to see a diff.
-                    deltamf = mnfst.readdelta(mnfstnode)
-                    # For each line in the delta
-                    for f, fnode in deltamf.iteritems():
-                        # And if the file is in the list of files we care
-                        # about.
-                        if f in changedfiles:
-                            # Get the changenode this manifest belongs to
-                            clnode = msng_mnfst_set[mnfstnode]
-                            # Create the set of filenodes for the file if
-                            # there isn't one already.
-                            ndset = msng_filenode_set.setdefault(f, {})
-                            # And set the filenode's changelog node to the
-                            # manifest's if it hasn't been set already.
-                            ndset.setdefault(fnode, clnode)
-                else:
-                    # Otherwise we need a full manifest.
-                    m = mnfst.read(mnfstnode)
-                    # For every file in we care about.
-                    for f in changedfiles:
-                        fnode = m.get(f, None)
-                        # If it's in the manifest
-                        if fnode is not None:
-                            # See comments above.
-                            clnode = msng_mnfst_set[mnfstnode]
-                            ndset = msng_filenode_set.setdefault(f, {})
-                            ndset.setdefault(fnode, clnode)
-            return collect_msng_filenodes
+        def lookup(revlog, x):
+            if revlog == cl:
+                c = cl.read(x)
+                changedfiles.update(c[3])
+                mfs.setdefault(c[0], x)
+                count[0] += 1
+                self.ui.progress(_('bundling'), count[0], unit=_('changesets'))
+                return x
+            elif revlog == mf:
+                clnode = mfs[x]
+                mdata = mf.readfast(x)
+                for f in changedfiles:
+                    if f in mdata:
+                        fnodes.setdefault(f, {}).setdefault(mdata[f], clnode)
+                count[0] += 1
+                self.ui.progress(_('bundling'), count[0],
+                                 unit=_('manifests'), total=len(mfs))
+                return mfs[x]
+            else:
+                self.ui.progress(
+                    _('bundling'), count[0], item=fstate[0],
+                    unit=_('files'), total=len(changedfiles))
+                return fstate[1][x]
 
-        # If we determine that a particular file or manifest node must be a
-        # node that the recipient of the changegroup will already have, we can
-        # also assume the recipient will have all the parents.  This function
-        # prunes them from the set of missing nodes.
-        def prune(revlog, missingnodes):
-            hasset = set()
-            # If a 'missing' filenode thinks it belongs to a changenode we
-            # assume the recipient must have, then the recipient must have
-            # that filenode.
-            for n in missingnodes:
-                clrev = revlog.linkrev(revlog.rev(n))
-                if clrev in commonrevs:
-                    hasset.add(n)
-            for n in hasset:
-                missingnodes.pop(n, None)
-            for r in revlog.ancestors(*[revlog.rev(n) for n in hasset]):
-                missingnodes.pop(revlog.node(r), None)
+        bundler = changegroup.bundle10(lookup)
 
-        # Add the nodes that were explicitly requested.
-        def add_extra_nodes(name, nodes):
-            if not extranodes or name not in extranodes:
-                return
-
-            for node, linknode in extranodes[name]:
-                if node not in nodes:
-                    nodes[node] = linknode
-
-        # Now that we have all theses utility functions to help out and
-        # logically divide up the task, generate the group.
         def gengroup():
-            # The set of changed files starts empty.
-            changedfiles = set()
-            collect = changegroup.collector(cl, msng_mnfst_set, changedfiles)
-
             # Create a changenode group generator that will call our functions
             # back to lookup the owning changenode and collect information.
-            group = cl.group(msng_cl_lst, identity, collect)
-            for cnt, chnk in enumerate(group):
-                yield chnk
-                # 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
+            for chunk in cl.group(csets, bundler):
+                yield chunk
             self.ui.progress(_('bundling'), None)
 
-            prune(mnfst, msng_mnfst_set)
-            add_extra_nodes(1, msng_mnfst_set)
-            msng_mnfst_lst = msng_mnfst_set.keys()
-            # Sort the manifestnodes by revision number.
-            msng_mnfst_lst.sort(key=mnfst.rev)
             # Create a generator for the manifestnodes that calls our lookup
             # and data collection functions back.
-            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
-                # see above comment for why we divide by 3
-                self.ui.progress(_('bundling'), cnt / 3,
-                                 unit=_('manifests'), total=changecount)
+            count[0] = 0
+            for chunk in mf.group(prune(mf, mfs), bundler):
+                yield chunk
             self.ui.progress(_('bundling'), None)
-            efiles = len(efiles)
 
-            # These are no longer needed, dereference and toss the memory for
-            # them.
-            msng_mnfst_lst = None
-            msng_mnfst_set.clear()
+            mfs.clear()
 
-            if extranodes:
-                for fname in extranodes:
-                    if isinstance(fname, int):
-                        continue
-                    msng_filenode_set.setdefault(fname, {})
-                    changedfiles.add(fname)
             # Go through all our files in order sorted by name.
-            for idx, fname in enumerate(sorted(changedfiles)):
+            count[0] = 0
+            for fname in sorted(changedfiles):
                 filerevlog = self.file(fname)
                 if not len(filerevlog):
                     raise util.Abort(_("empty or missing revlog for %s") % fname)
-                # Toss out the filenodes that the recipient isn't really
-                # missing.
-                missingfnodes = msng_filenode_set.pop(fname, {})
-                prune(filerevlog, missingfnodes)
-                add_extra_nodes(fname, missingfnodes)
-                # If any filenodes are left, generate the group for them,
-                # otherwise don't bother.
-                if missingfnodes:
-                    yield changegroup.chunkheader(len(fname))
-                    yield fname
-                    # Sort the filenodes by their revision # (topological order)
-                    nodeiter = list(missingfnodes)
-                    nodeiter.sort(key=filerevlog.rev)
-                    # Create a group generator and only pass in a changenode
-                    # lookup function as we need to collect no information
-                    # from filenodes.
-                    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'), idx, item=fname,
-                            unit=_('files'), total=efiles)
-                        yield chnk
+                fstate[0] = fname
+                fstate[1] = fnodes.pop(fname, {})
+                first = True
+
+                for chunk in filerevlog.group(prune(filerevlog, fstate[1]),
+                                              bundler):
+                    if first:
+                        if chunk == bundler.close():
+                            break
+                        count[0] += 1
+                        yield bundler.fileheader(fname)
+                        first = False
+                    yield chunk
             # Signal that no more groups are left.
-            yield changegroup.closechunk()
+            yield bundler.close()
             self.ui.progress(_('bundling'), None)
 
-            if msng_cl_lst:
-                self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
+            if csets:
+                self.hook('outgoing', node=hex(csets[0]), source=source)
 
         return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
 
@@ -1683,75 +1581,75 @@
 
         nodes is the set of nodes to send"""
 
-        self.hook('preoutgoing', throw=True, source=source)
+        cl = self.changelog
+        mf = self.manifest
+        mfs = {}
+        changedfiles = set()
+        fstate = ['']
+        count = [0]
 
-        cl = self.changelog
-        revset = set([cl.rev(n) for n in nodes])
+        self.hook('preoutgoing', throw=True, source=source)
         self.changegroupinfo(nodes, source)
 
-        def identity(x):
-            return x
+        revset = set([cl.rev(n) for n in nodes])
 
         def gennodelst(log):
             for r in log:
                 if log.linkrev(r) in revset:
                     yield log.node(r)
 
-        def lookuplinkrev_func(revlog):
-            def lookuplinkrev(n):
-                return cl.node(revlog.linkrev(revlog.rev(n)))
-            return lookuplinkrev
+        def lookup(revlog, x):
+            if revlog == cl:
+                c = cl.read(x)
+                changedfiles.update(c[3])
+                mfs.setdefault(c[0], x)
+                count[0] += 1
+                self.ui.progress(_('bundling'), count[0], unit=_('changesets'))
+                return x
+            elif revlog == mf:
+                count[0] += 1
+                self.ui.progress(_('bundling'), count[0],
+                                 unit=_('manifests'), total=len(mfs))
+                return cl.node(revlog.linkrev(revlog.rev(x)))
+            else:
+                self.ui.progress(
+                    _('bundling'), count[0], item=fstate[0],
+                    total=len(changedfiles), unit=_('files'))
+                return cl.node(revlog.linkrev(revlog.rev(x)))
+
+        bundler = changegroup.bundle10(lookup)
 
         def gengroup():
             '''yield a sequence of changegroup chunks (strings)'''
             # construct a list of all changed files
-            changedfiles = set()
-            mmfs = {}
-            collect = changegroup.collector(cl, mmfs, changedfiles)
 
-            for cnt, chnk in enumerate(cl.group(nodes, identity, collect)):
-                # 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
-            changecount = cnt / 3
+            for chunk in cl.group(nodes, bundler):
+                yield chunk
             self.ui.progress(_('bundling'), None)
 
-            mnfst = self.manifest
-            nodeiter = gennodelst(mnfst)
-            efiles = {}
-            for cnt, chnk in enumerate(mnfst.group(nodeiter,
-                                                   lookuplinkrev_func(mnfst))):
-                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
-            efiles = len(efiles)
+            count[0] = 0
+            for chunk in mf.group(gennodelst(mf), bundler):
+                yield chunk
             self.ui.progress(_('bundling'), None)
 
-            for idx, fname in enumerate(sorted(changedfiles)):
+            count[0] = 0
+            for fname in sorted(changedfiles):
                 filerevlog = self.file(fname)
                 if not len(filerevlog):
                     raise util.Abort(_("empty or missing revlog for %s") % fname)
-                nodeiter = gennodelst(filerevlog)
-                nodeiter = list(nodeiter)
-                if nodeiter:
-                    yield changegroup.chunkheader(len(fname))
-                    yield fname
-                    lookup = lookuplinkrev_func(filerevlog)
-                    for chnk in filerevlog.group(nodeiter, lookup):
-                        self.ui.progress(
-                            _('bundling'), idx, item=fname,
-                            total=efiles, unit=_('files'))
-                        yield chnk
+                fstate[0] = fname
+                first = True
+                for chunk in filerevlog.group(gennodelst(filerevlog), bundler):
+                    if first:
+                        if chunk == bundler.close():
+                            break
+                        count[0] += 1
+                        yield bundler.fileheader(fname)
+                        first = False
+                    yield chunk
+            yield bundler.close()
             self.ui.progress(_('bundling'), None)
 
-            yield changegroup.closechunk()
-
             if nodes:
                 self.hook('outgoing', node=hex(nodes[0]), source=source)
 
@@ -1915,10 +1813,6 @@
                 self.hook("incoming", node=hex(cl.node(i)),
                           source=srctype, url=url)
 
-        # FIXME - why does this care about tip?
-        if newheads == oldheads:
-            bookmarks.update(self, self.dirstate.parents(), self['tip'].node())
-
         # never return 0 here:
         if newheads < oldheads:
             return newheads - oldheads - 1
@@ -2019,6 +1913,10 @@
     def listkeys(self, namespace):
         return pushkey.list(self, namespace)
 
+    def debugwireargs(self, one, two, three=None, four=None):
+        '''used to test argument passing over the wire'''
+        return "%s %s %s %s" % (one, two, three, four)
+
 # used to avoid circular references so destructors work
 def aftertrans(files):
     renamefiles = [tuple(t) for t in files]
@@ -2028,7 +1926,7 @@
     return a
 
 def instance(ui, path, create):
-    return localrepository(ui, util.drop_scheme('file', path), create)
+    return localrepository(ui, urlmod.localpath(path), create)
 
 def islocal(path):
     return True
--- a/mercurial/manifest.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/manifest.py	Fri Apr 01 12:21:59 2011 +0200
@@ -38,6 +38,13 @@
         r = self.rev(node)
         return self.parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
 
+    def readfast(self, node):
+        '''use the faster of readdelta or read'''
+        r = self.rev(node)
+        if self.deltaparent(r) in self.parentrevs(r):
+            return self.readdelta(node)
+        return self.read(node)
+
     def read(self, node):
         if node == revlog.nullid:
             return manifestdict() # don't upset local cache
--- a/mercurial/merge.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/merge.py	Fri Apr 01 12:21:59 2011 +0200
@@ -494,7 +494,6 @@
         p1, p2 = pl[0], repo[node]
         pa = p1.ancestor(p2)
         fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
-        fastforward = False
 
         ### check phase
         if not overwrite and len(pl) > 1:
@@ -504,9 +503,7 @@
                 raise util.Abort(_("merging with a working directory ancestor"
                                    " has no effect"))
             elif pa == p1:
-                if p1.branch() != p2.branch():
-                    fastforward = True
-                else:
+                if p1.branch() == p2.branch():
                     raise util.Abort(_("nothing to merge (use 'hg update'"
                                        " or check 'hg heads')"))
             if not force and (wc.files() or wc.deleted()):
@@ -551,7 +548,7 @@
         if not partial:
             repo.dirstate.setparents(fp1, fp2)
             recordupdates(repo, action, branchmerge)
-            if not branchmerge and not fastforward:
+            if not branchmerge:
                 repo.dirstate.setbranch(p2.branch())
     finally:
         wlock.release()
--- a/mercurial/osutil.c	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/osutil.c	Fri Apr 01 12:21:59 2011 +0200
@@ -514,6 +514,22 @@
 }
 #endif
 
+#ifdef __APPLE__
+#include <ApplicationServices/ApplicationServices.h>
+
+static PyObject *isgui(PyObject *self)
+{
+	CFDictionaryRef dict = CGSessionCopyCurrentDictionary();
+
+	if (dict != NULL) {
+		CFRelease(dict);
+		return Py_True;
+	} else {
+		return Py_False;
+	}
+}
+#endif
+
 static char osutil_doc[] = "Native operating system services.";
 
 static PyMethodDef methods[] = {
@@ -524,6 +540,12 @@
 	 "Open a file with POSIX-like semantics.\n"
 "On error, this function may raise either a WindowsError or an IOError."},
 #endif
+#ifdef __APPLE__
+	{
+		"isgui", (PyCFunction)isgui, METH_NOARGS,
+		"Is a CoreGraphics session available?"
+	},
+#endif
 	{NULL, NULL}
 };
 
--- a/mercurial/parser.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/parser.py	Fri Apr 01 12:21:59 2011 +0200
@@ -78,7 +78,9 @@
         'generate a parse tree from a message'
         self._iter = self._tokenizer(message)
         self._advance()
-        return self._parse()
+        res = self._parse()
+        token, value, pos = self.current
+        return res, pos
     def eval(self, tree):
         'recursively evaluate a parse tree using node methods'
         if not isinstance(tree, tuple):
--- a/mercurial/patch.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/patch.py	Fri Apr 01 12:21:59 2011 +0200
@@ -488,11 +488,6 @@
             cand.sort(key=lambda x: abs(x - linenum))
         return cand
 
-    def hashlines(self):
-        self.hash = {}
-        for x, s in enumerate(self.lines):
-            self.hash.setdefault(s, []).append(x)
-
     def makerejlines(self, fname):
         base = os.path.basename(fname)
         yield "--- %s\n+++ %s\n" % (base, base)
@@ -574,8 +569,10 @@
                 self.dirty = 1
             return 0
 
-        # ok, we couldn't match the hunk.  Lets look for offsets and fuzz it
-        self.hashlines()
+        # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
+        self.hash = {}
+        for x, s in enumerate(self.lines):
+            self.hash.setdefault(s, []).append(x)
         if h.hunk[-1][0] != ' ':
             # if the hunk tried to put something at the bottom of the file
             # override the start line and use eof here
@@ -613,6 +610,12 @@
         self.rej.append(horig)
         return -1
 
+    def close(self):
+        if self.dirty:
+            self.writelines(self.fname, self.lines)
+        self.write_rej()
+        return len(self.rej)
+
 class hunk(object):
     def __init__(self, desc, num, lr, context, create=False, remove=False):
         self.number = num
@@ -680,6 +683,7 @@
             del self.b[-1]
             self.lena -= 1
             self.lenb -= 1
+        self._fixnewline(lr)
 
     def read_context_hunk(self, lr):
         self.desc = lr.readline()
@@ -782,9 +786,14 @@
         self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
                                              self.startb, self.lenb)
         self.hunk[0] = self.desc
+        self._fixnewline(lr)
 
-    def fix_newline(self):
-        diffhelpers.fix_newline(self.hunk, self.a, self.b)
+    def _fixnewline(self, lr):
+        l = lr.readline()
+        if l.startswith('\ '):
+            diffhelpers.fix_newline(self.hunk, self.a, self.b)
+        else:
+            lr.push(l)
 
     def complete(self):
         return len(self.a) == self.lena and len(self.b) == self.lenb
@@ -993,7 +1002,6 @@
     maps filenames to gitpatch records. Unique event.
     """
     changed = {}
-    current_hunk = None
     afile = ""
     bfile = ""
     state = None
@@ -1011,11 +1019,6 @@
         x = lr.readline()
         if not x:
             break
-        if current_hunk:
-            if x.startswith('\ '):
-                current_hunk.fix_newline()
-            yield 'hunk', current_hunk
-            current_hunk = None
         if (state == BFILE and ((not context and x[0] == '@') or
             ((context is not False) and x.startswith('***************')))):
             if context is None and x.startswith('***************'):
@@ -1023,18 +1026,20 @@
             gpatch = changed.get(bfile)
             create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
             remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
-            current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
+            h = hunk(x, hunknum + 1, lr, context, create, remove)
             hunknum += 1
             if emitfile:
                 emitfile = False
-                yield 'file', (afile, bfile, current_hunk)
+                yield 'file', (afile, bfile, h)
+            yield 'hunk', h
         elif state == BFILE and x.startswith('GIT binary patch'):
-            current_hunk = binhunk(changed[bfile])
+            h = binhunk(changed[bfile])
             hunknum += 1
             if emitfile:
                 emitfile = False
-                yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
-            current_hunk.extract(lr)
+                yield 'file', ('a/' + afile, 'b/' + bfile, h)
+            h.extract(lr)
+            yield 'hunk', h
         elif x.startswith('diff --git'):
             # check for git diff, scanning the whole patch file if needed
             m = gitre.match(x)
@@ -1083,12 +1088,6 @@
             emitfile = True
             state = BFILE
             hunknum = 0
-    if current_hunk:
-        if current_hunk.complete():
-            yield 'hunk', current_hunk
-        else:
-            raise PatchError(_("malformed patch %s %s") % (afile,
-                             current_hunk.desc))
 
 def applydiff(ui, fp, changed, strip=1, eolmode='strict'):
     """Reads a patch from fp and tries to apply it.
@@ -1114,14 +1113,6 @@
     cwd = os.getcwd()
     opener = util.opener(cwd)
 
-    def closefile():
-        if not current_file:
-            return 0
-        if current_file.dirty:
-            current_file.writelines(current_file.fname, current_file.lines)
-        current_file.write_rej()
-        return len(current_file.rej)
-
     for state, values in iterhunks(ui, fp):
         if state == 'hunk':
             if not current_file:
@@ -1132,7 +1123,8 @@
                 if ret > 0:
                     err = 1
         elif state == 'file':
-            rejects += closefile()
+            if current_file:
+                rejects += current_file.close()
             afile, bfile, first_hunk = values
             try:
                 current_file, missing = selectfile(afile, bfile,
@@ -1157,13 +1149,14 @@
         else:
             raise util.Abort(_('unsupported parser state: %s') % state)
 
-    rejects += closefile()
+    if current_file:
+        rejects += current_file.close()
 
     if rejects:
         return -1
     return err
 
-def externalpatch(patcher, patchname, ui, strip, cwd, files):
+def _externalpatch(patcher, patchname, ui, strip, cwd, files):
     """use <patcher> to apply <patchname> to the working directory.
     returns whether patch was applied with fuzz factor."""
 
@@ -1247,7 +1240,7 @@
         files = {}
     try:
         if patcher:
-            return externalpatch(patcher, patchname, ui, strip, cwd, files)
+            return _externalpatch(patcher, patchname, ui, strip, cwd, files)
         return internalpatch(patchname, ui, strip, cwd, files, eolmode)
     except PatchError, err:
         raise util.Abort(str(err))
--- a/mercurial/repair.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/repair.py	Fri Apr 01 12:21:59 2011 +0200
@@ -11,9 +11,9 @@
 from i18n import _
 import os
 
-def _bundle(repo, bases, heads, node, suffix, extranodes=None, compress=True):
+def _bundle(repo, bases, heads, node, suffix, compress=True):
     """create a bundle with the specified revisions as a backup"""
-    cg = repo.changegroupsubset(bases, heads, 'strip', extranodes)
+    cg = repo.changegroupsubset(bases, heads, 'strip')
     backupdir = repo.join("strip-backup")
     if not os.path.isdir(backupdir):
         os.mkdir(backupdir)
@@ -33,40 +33,26 @@
 
     return sorted(files)
 
-def _collectextranodes(repo, files, link):
-    """return the nodes that have to be saved before the strip"""
-    def collectone(cl, revlog):
-        extra = []
-        startrev = count = len(revlog)
+def _collectbrokencsets(repo, files, striprev):
+    """return the changesets which will be broken by the truncation"""
+    s = set()
+    def collectone(revlog):
+        links = (revlog.linkrev(i) for i in revlog)
         # find the truncation point of the revlog
-        for i in xrange(count):
-            lrev = revlog.linkrev(i)
-            if lrev >= link:
-                startrev = i + 1
+        for lrev in links:
+            if lrev >= striprev:
                 break
+        # see if any revision after this point has a linkrev
+        # less than striprev (those will be broken by strip)
+        for lrev in links:
+            if lrev < striprev:
+                s.add(lrev)
 
-        # see if any revision after that point has a linkrev less than link
-        # (we have to manually save these guys)
-        for i in xrange(startrev, count):
-            node = revlog.node(i)
-            lrev = revlog.linkrev(i)
-            if lrev < link:
-                extra.append((node, cl.node(lrev)))
-
-        return extra
+    collectone(repo.manifest)
+    for fname in files:
+        collectone(repo.file(fname))
 
-    extranodes = {}
-    cl = repo.changelog
-    extra = collectone(cl, repo.manifest)
-    if extra:
-        extranodes[1] = extra
-    for fname in files:
-        f = repo.file(fname)
-        extra = collectone(cl, f)
-        if extra:
-            extranodes[fname] = extra
-
-    return extranodes
+    return s
 
 def strip(ui, repo, node, backup="all"):
     cl = repo.changelog
@@ -82,28 +68,26 @@
     # the list of heads and bases of the set of interesting revisions.
     # (head = revision in the set that has no descendant in the set;
     #  base = revision in the set that has no ancestor in the set)
-    tostrip = set((striprev,))
-    saveheads = set()
-    savebases = []
+    tostrip = set(cl.descendants(striprev))
+    tostrip.add(striprev)
+
+    files = _collectfiles(repo, striprev)
+    saverevs = _collectbrokencsets(repo, files, striprev)
+
+    # compute heads
+    saveheads = set(saverevs)
     for r in xrange(striprev + 1, len(cl)):
-        parents = cl.parentrevs(r)
-        if parents[0] in tostrip or parents[1] in tostrip:
-            # r is a descendant of striprev
-            tostrip.add(r)
-            # if this is a merge and one of the parents does not descend
-            # from striprev, mark that parent as a savehead.
-            if parents[1] != nullrev:
-                for p in parents:
-                    if p not in tostrip and p > striprev:
-                        saveheads.add(p)
-        else:
-            # if no parents of this revision will be stripped, mark it as
-            # a savebase
-            if parents[0] < striprev and parents[1] < striprev:
-                savebases.append(cl.node(r))
+        if r not in tostrip:
+            saverevs.add(r)
+            saveheads.difference_update(cl.parentrevs(r))
+            saveheads.add(r)
+    saveheads = [cl.node(r) for r in saveheads]
 
-            saveheads.difference_update(parents)
-            saveheads.add(r)
+    # compute base nodes
+    if saverevs:
+        descendants = set(cl.descendants(*saverevs))
+        saverevs.difference_update(descendants)
+    savebases = [cl.node(r) for r in saverevs]
 
     bm = repo._bookmarks
     updatebm = []
@@ -112,20 +96,15 @@
         if rev in tostrip:
             updatebm.append(m)
 
-    saveheads = [cl.node(r) for r in saveheads]
-    files = _collectfiles(repo, striprev)
-
-    extranodes = _collectextranodes(repo, files, striprev)
-
     # create a changegroup for all the branches we need to keep
     backupfile = None
     if backup == "all":
         backupfile = _bundle(repo, [node], cl.heads(), node, 'backup')
         repo.ui.status(_("saved backup bundle to %s\n") % backupfile)
-    if saveheads or extranodes:
+    if saveheads or savebases:
         # do not compress partial bundle if we remove it from disk later
         chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
-                            extranodes=extranodes, compress=keeppartialbundle)
+                            compress=keeppartialbundle)
 
     mfst = repo.manifest
 
@@ -149,7 +128,7 @@
             tr.abort()
             raise
 
-        if saveheads or extranodes:
+        if saveheads or savebases:
             ui.note(_("adding branch\n"))
             f = open(chgrpfile, "rb")
             gen = changegroup.readbundle(f, chgrpfile)
--- a/mercurial/revlog.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/revlog.py	Fri Apr 01 12:21:59 2011 +0200
@@ -399,11 +399,12 @@
                     yield i
                     break
 
-    def findmissing(self, common=None, heads=None):
-        """Return the ancestors of heads that are not ancestors of common.
+    def findcommonmissing(self, common=None, heads=None):
+        """Return a tuple of the ancestors of common and the ancestors of heads
+        that are not ancestors of common.
 
-        More specifically, return a list of nodes N such that every N
-        satisfies the following constraints:
+        More specifically, the second element is a list of nodes N such that
+        every N satisfies the following constraints:
 
           1. N is an ancestor of some node in 'heads'
           2. N is not an ancestor of any node in 'common'
@@ -441,7 +442,25 @@
                         visit.append(p)
         missing = list(missing)
         missing.sort()
-        return [self.node(r) for r in missing]
+        return has, [self.node(r) for r in missing]
+
+    def findmissing(self, common=None, heads=None):
+        """Return the ancestors of heads that are not ancestors of common.
+
+        More specifically, return a list of nodes N such that every N
+        satisfies the following constraints:
+
+          1. N is an ancestor of some node in 'heads'
+          2. N is not an ancestor of any node in 'common'
+
+        The list is sorted by revision number, meaning it is
+        topologically sorted.
+
+        'heads' and 'common' are both lists of node IDs.  If heads is
+        not supplied, uses all of the revlog's heads.  If common is not
+        supplied, uses nullid."""
+        _common, missing = self.findcommonmissing(common, heads)
+        return missing
 
     def nodesbetween(self, roots=None, heads=None):
         """Return a topological path from 'roots' to 'heads'.
@@ -1039,7 +1058,7 @@
             self._cache = (node, curr, text)
         return node
 
-    def group(self, nodelist, lookup, infocollect=None, fullrev=False):
+    def group(self, nodelist, bundler):
         """Calculate a delta group, yielding a sequence of changegroup chunks
         (strings).
 
@@ -1049,45 +1068,35 @@
         guaranteed to have this parent as it has all history before
         these changesets. In the case firstparent is nullrev the
         changegroup starts with a full revision.
-        fullrev forces the insertion of the full revision, necessary
-        in the case of shallow clones where the first parent might
-        not exist at the reciever.
         """
 
-        revs = [self.rev(n) for n in nodelist]
+        revs = sorted([self.rev(n) for n in nodelist])
 
         # if we don't have any revisions touched by these changesets, bail
         if not revs:
-            yield changegroup.closechunk()
+            yield bundler.close()
             return
 
         # add the parent of the first rev
         p = self.parentrevs(revs[0])[0]
         revs.insert(0, p)
-        if p == nullrev:
-            fullrev = True
 
         # build deltas
-        for d in xrange(len(revs) - 1):
-            a, b = revs[d], revs[d + 1]
+        for r in xrange(len(revs) - 1):
+            a, b = revs[r], revs[r + 1]
             nb = self.node(b)
-
-            if infocollect is not None:
-                infocollect(nb)
+            p1, p2 = self.parents(nb)
+            prefix = ''
 
-            p = self.parents(nb)
-            meta = nb + p[0] + p[1] + lookup(nb)
-            if fullrev:
+            if a == nullrev:
                 d = self.revision(nb)
-                meta += mdiff.trivialdiffheader(len(d))
-                fullrev = False
+                prefix = mdiff.trivialdiffheader(len(d))
             else:
                 d = self.revdiff(a, b)
-            yield changegroup.chunkheader(len(meta) + len(d))
-            yield meta
-            yield d
+            for c in bundler.revchunk(self, nb, p1, p2, prefix, d):
+                yield c
 
-        yield changegroup.closechunk()
+        yield bundler.close()
 
     def addgroup(self, bundle, linkmapper, transaction):
         """
--- a/mercurial/revset.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/revset.py	Fri Apr 01 12:21:59 2011 +0200
@@ -6,10 +6,10 @@
 # GNU General Public License version 2 or any later version.
 
 import re
-import parser, util, error, discovery
+import parser, util, error, discovery, help, hbisect
 import bookmarks as bookmarksmod
 import match as matchmod
-from i18n import _, gettext
+from i18n import _
 
 elements = {
     "(": (20, ("group", 1, ")"), ("func", 1, ")")),
@@ -298,9 +298,18 @@
     return [r for r in subset if r in cs]
 
 def branch(repo, subset, x):
-    """``branch(set)``
-    All changesets belonging to the branches of changesets in set.
+    """``branch(string or set)``
+    All changesets belonging to the given branch or the branches of the given
+    changesets.
     """
+    try:
+        b = getstring(x, '')
+        if b in repo.branchmap():
+            return [r for r in subset if repo[r].branch() == b]
+    except error.ParseError:
+        # not a string, but another revspec, e.g. tip()
+        pass
+
     s = getset(repo, range(len(repo)), x)
     b = set()
     for r in s:
@@ -394,7 +403,7 @@
         for e in c.files() + [c.user(), c.description()]:
             if gr.search(e):
                 l.append(r)
-                continue
+                break
     return l
 
 def author(repo, subset, x):
@@ -423,7 +432,7 @@
         for f in repo[r].files():
             if m(f):
                 s.append(r)
-                continue
+                break
     return s
 
 def contains(repo, subset, x):
@@ -438,13 +447,12 @@
         for r in subset:
             if pat in repo[r]:
                 s.append(r)
-                continue
     else:
         for r in subset:
             for f in repo[r].manifest():
                 if m(f):
                     s.append(r)
-                    continue
+                    break
     return s
 
 def checkstatus(repo, subset, pat, field):
@@ -466,12 +474,11 @@
         if fast:
             if pat in files:
                 s.append(r)
-                continue
         else:
             for f in files:
                 if m(f):
                     s.append(r)
-                    continue
+                    break
     return s
 
 def modifies(repo, subset, x):
@@ -683,12 +690,27 @@
                for r in bookmarksmod.listbookmarks(repo).values()])
     return [r for r in subset if r in bms]
 
+def bisected(repo, subset, x):
+    """``bisected(string)``
+    Changesets marked in the specified bisect state (good, bad, skip).
+    """
+    state = getstring(x, _("bisect requires a string")).lower()
+    if state not in ('good', 'bad', 'skip', 'unknown'):
+        raise ParseError(_('invalid bisect state'))
+    marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
+    l = []
+    for r in subset:
+        if r in marked:
+            l.append(r)
+    return l
+
 symbols = {
     "adds": adds,
     "all": getall,
     "ancestor": ancestor,
     "ancestors": ancestors,
     "author": author,
+    "bisected": bisected,
     "bookmark": bookmark,
     "branch": branch,
     "children": children,
@@ -808,26 +830,16 @@
 def match(spec):
     if not spec:
         raise error.ParseError(_("empty query"))
-    tree = parse(spec)
+    tree, pos = parse(spec)
+    if (pos != len(spec)):
+        raise error.ParseError("invalid token", pos)
     weight, tree = optimize(tree, True)
     def mfunc(repo, subset):
         return getset(repo, subset, tree)
     return mfunc
 
 def makedoc(topic, doc):
-    """Generate and include predicates help in revsets topic."""
-    predicates = []
-    for name in sorted(symbols):
-        text = symbols[name].__doc__
-        if not text:
-            continue
-        text = gettext(text.rstrip())
-        lines = text.splitlines()
-        lines[1:] = [('  ' + l.strip()) for l in lines[1:]]
-        predicates.append('\n'.join(lines))
-    predicates = '\n\n'.join(predicates)
-    doc = doc.replace('.. predicatesmarker', predicates)
-    return doc
+    return help.makeitemsdoc(topic, doc, '.. predicatesmarker', symbols)
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = symbols.values()
--- a/mercurial/sshrepo.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/sshrepo.py	Fri Apr 01 12:21:59 2011 +0200
@@ -6,8 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import _
-import util, error, wireproto
-import re
+import util, error, wireproto, url
 
 class remotelock(object):
     def __init__(self, repo):
@@ -24,16 +23,16 @@
         self._url = path
         self.ui = ui
 
-        m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path)
-        if not m:
+        u = url.url(path, parsequery=False, parsefragment=False)
+        if u.scheme != 'ssh' or not u.host or u.path is None:
             self._abort(error.RepoError(_("couldn't parse location %s") % path))
 
-        self.user = m.group(2)
-        if self.user and ':' in self.user:
+        self.user = u.user
+        if u.passwd is not None:
             self._abort(error.RepoError(_("password in URL not supported")))
-        self.host = m.group(3)
-        self.port = m.group(5)
-        self.path = m.group(7) or "."
+        self.host = u.host
+        self.port = u.port
+        self.path = u.path or "."
 
         sshcmd = self.ui.config("ui", "ssh", "ssh")
         remotecmd = self.ui.config("ui", "remotecmd", "hg")
@@ -119,9 +118,24 @@
     def _callstream(self, cmd, **args):
         self.ui.debug("sending %s command\n" % cmd)
         self.pipeo.write("%s\n" % cmd)
-        for k, v in sorted(args.iteritems()):
+        _func, names = wireproto.commands[cmd]
+        keys = names.split()
+        wireargs = {}
+        for k in keys:
+            if k == '*':
+                wireargs['*'] = args
+                break
+            else:
+                wireargs[k] = args[k]
+                del args[k]
+        for k, v in sorted(wireargs.iteritems()):
             self.pipeo.write("%s %d\n" % (k, len(v)))
-            self.pipeo.write(v)
+            if isinstance(v, dict):
+                for dk, dv in v.iteritems():
+                    self.pipeo.write("%s %d\n" % (dk, len(dv)))
+                    self.pipeo.write(dv)
+            else:
+                self.pipeo.write(v)
         self.pipeo.flush()
 
         return self.pipei
--- a/mercurial/sshserver.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/sshserver.py	Fri Apr 01 12:21:59 2011 +0200
@@ -27,21 +27,21 @@
     def getargs(self, args):
         data = {}
         keys = args.split()
-        count = len(keys)
         for n in xrange(len(keys)):
             argline = self.fin.readline()[:-1]
             arg, l = argline.split()
-            val = self.fin.read(int(l))
             if arg not in keys:
                 raise util.Abort("unexpected parameter %r" % arg)
             if arg == '*':
                 star = {}
-                for n in xrange(int(l)):
+                for k in xrange(int(l)):
+                    argline = self.fin.readline()[:-1]
                     arg, l = argline.split()
                     val = self.fin.read(int(l))
                     star[arg] = val
                 data['*'] = star
             else:
+                val = self.fin.read(int(l))
                 data[arg] = val
         return [data[k] for k in keys]
 
--- a/mercurial/statichttprepo.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/statichttprepo.py	Fri Apr 01 12:21:59 2011 +0200
@@ -71,7 +71,7 @@
         """return a function that opens files over http"""
         p = base
         def o(path, mode="r", atomictemp=None):
-            if 'a' in mode or 'w' in mode:
+            if mode not in ('r', 'rb'):
                 raise IOError('Permission denied')
             f = "/".join((p, urllib.quote(path)))
             return httprangereader(f, urlopener)
@@ -85,7 +85,8 @@
         self.ui = ui
 
         self.root = path
-        self.path, authinfo = url.getauthinfo(path.rstrip('/') + "/.hg")
+        u = url.url(path.rstrip('/') + "/.hg")
+        self.path, authinfo = u.authinfo()
 
         opener = build_opener(ui, authinfo)
         self.opener = opener(self.path)
--- a/mercurial/subrepo.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/subrepo.py	Fri Apr 01 12:21:59 2011 +0200
@@ -5,10 +5,10 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
+import errno, os, re, xml.dom.minidom, shutil, posixpath
 import stat, subprocess, tarfile
 from i18n import _
-import config, util, node, error, cmdutil
+import config, util, node, error, cmdutil, url, bookmarks
 hg = None
 
 nullstate = ('', '', 'empty')
@@ -193,21 +193,16 @@
     """return pull/push path of repo - either based on parent repo .hgsub info
     or on the top repo config. Abort or return None if no source found."""
     if hasattr(repo, '_subparent'):
-        source = repo._subsource
-        if source.startswith('/') or '://' in source:
-            return source
+        source = url.url(repo._subsource)
+        source.path = posixpath.normpath(source.path)
+        if posixpath.isabs(source.path) or source.scheme:
+            return str(source)
         parent = _abssource(repo._subparent, push, abort=False)
         if parent:
-            if '://' in parent:
-                if parent[-1] == '/':
-                    parent = parent[:-1]
-                r = urlparse.urlparse(parent + '/' + source)
-                r = urlparse.urlunparse((r[0], r[1],
-                                         posixpath.normpath(r[2]),
-                                         r[3], r[4], r[5]))
-                return r
-            else: # plain file system path
-                return posixpath.normpath(os.path.join(parent, repo._subsource))
+            parent = url.url(parent)
+            parent.path = posixpath.join(parent.path, source.path)
+            parent.path = posixpath.normpath(parent.path)
+            return str(parent)
     else: # recursion reached top repo
         if hasattr(repo, '_subtoppath'):
             return repo._subtoppath
@@ -432,15 +427,14 @@
 
     def _get(self, state):
         source, revision, kind = state
-        try:
-            self._repo.lookup(revision)
-        except error.RepoError:
+        if revision not in self._repo:
             self._repo._subsource = source
             srcurl = _abssource(self._repo)
             self._repo.ui.status(_('pulling subrepo %s from %s\n')
                                  % (subrelpath(self), srcurl))
             other = hg.repository(self._repo.ui, srcurl)
             self._repo.pull(other)
+            bookmarks.updatefromremote(self._repo.ui, self._repo, other)
 
     def get(self, state, overwrite=False):
         self._get(state)
@@ -714,6 +708,12 @@
             current = None
         return current
 
+    def _gitremote(self, remote):
+        out = self._gitcommand(['remote', 'show', '-n', remote])
+        line = out.split('\n')[1]
+        i = line.index('URL: ') + len('URL: ')
+        return line[i:]
+
     def _githavelocally(self, revision):
         out, code = self._gitdir(['cat-file', '-e', revision])
         return code == 0
@@ -767,11 +767,14 @@
 
     def _fetch(self, source, revision):
         if self._gitmissing():
-            self._ui.status(_('cloning subrepo %s\n') % self._relpath)
-            self._gitnodir(['clone', self._abssource(source), self._abspath])
+            source = self._abssource(source)
+            self._ui.status(_('cloning subrepo %s from %s\n') %
+                            (self._relpath, source))
+            self._gitnodir(['clone', source, self._abspath])
         if self._githavelocally(revision):
             return
-        self._ui.status(_('pulling subrepo %s\n') % self._relpath)
+        self._ui.status(_('pulling subrepo %s from %s\n') %
+                        (self._relpath, self._gitremote('origin')))
         # try only origin: the originally cloned repo
         self._gitcommand(['fetch'])
         if not self._githavelocally(revision):
--- a/mercurial/templatefilters.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/templatefilters.py	Fri Apr 01 12:21:59 2011 +0200
@@ -6,13 +6,13 @@
 # GNU General Public License version 2 or any later version.
 
 import cgi, re, os, time, urllib
-import encoding, node, util
+import encoding, node, util, help
 
-def stringify(thing):
-    '''turn nested template iterator into string.'''
-    if hasattr(thing, '__iter__') and not isinstance(thing, str):
-        return "".join([stringify(t) for t in thing if t is not None])
-    return str(thing)
+def addbreaks(text):
+    """:addbreaks: Any text. Add an XHTML "<br />" tag before the end of
+    every line except the last.
+    """
+    return text.replace('\n', '<br/>\n')
 
 agescales = [("year", 3600 * 24 * 365),
              ("month", 3600 * 24 * 30),
@@ -23,7 +23,9 @@
              ("second", 1)]
 
 def age(date):
-    '''turn a (timestamp, tzoff) tuple into an age string.'''
+    """:age: Date. Returns a human-readable date/time difference between the
+    given date/time and the current date/time.
+    """
 
     def plural(t, c):
         if c == 1:
@@ -34,18 +36,65 @@
 
     now = time.time()
     then = date[0]
+    future = False
     if then > now:
-        return 'in the future'
-
-    delta = max(1, int(now - then))
-    if delta > agescales[0][1] * 2:
-        return util.shortdate(date)
+        future = True
+        delta = max(1, int(then - now))
+        if delta > agescales[0][1] * 30:
+            return 'in the distant future'
+    else:
+        delta = max(1, int(now - then))
+        if delta > agescales[0][1] * 2:
+            return util.shortdate(date)
 
     for t, s in agescales:
         n = delta // s
         if n >= 2 or s == 1:
+            if future:
+                return '%s from now' % fmt(t, n)
             return '%s ago' % fmt(t, n)
 
+def basename(path):
+    """:basename: Any text. Treats the text as a path, and returns the last
+    component of the path after splitting by the path separator
+    (ignoring trailing separators). For example, "foo/bar/baz" becomes
+    "baz" and "foo/bar//" becomes "bar".
+    """
+    return os.path.basename(path)
+
+def datefilter(text):
+    """:date: Date. Returns a date in a Unix date format, including the
+    timezone: "Mon Sep 04 15:13:13 2006 0700".
+    """
+    return util.datestr(text)
+
+def domain(author):
+    """:domain: Any text. Finds the first string that looks like an email
+    address, and extracts just the domain component. Example: ``User
+    <user@example.com>`` becomes ``example.com``.
+    """
+    f = author.find('@')
+    if f == -1:
+        return ''
+    author = author[f + 1:]
+    f = author.find('>')
+    if f >= 0:
+        author = author[:f]
+    return author
+
+def email(text):
+    """:email: Any text. Extracts the first string that looks like an email
+    address. Example: ``User <user@example.com>`` becomes
+    ``user@example.com``.
+    """
+    return util.email(text)
+
+def escape(text):
+    """:escape: Any text. Replaces the special XML/XHTML characters "&", "<"
+    and ">" with XML entities.
+    """
+    return cgi.escape(text, True)
+
 para_re = None
 space_re = None
 
@@ -74,40 +123,45 @@
     return "".join([space_re.sub(' ', util.wrap(para, width=width)) + rest
                     for para, rest in findparas()])
 
+def fill68(text):
+    """:fill68: Any text. Wraps the text to fit in 68 columns."""
+    return fill(text, 68)
+
+def fill76(text):
+    """:fill76: Any text. Wraps the text to fit in 76 columns."""
+    return fill(text, 76)
+
 def firstline(text):
-    '''return the first line of text'''
+    """:firstline: Any text. Returns the first line of text."""
     try:
         return text.splitlines(True)[0].rstrip('\r\n')
     except IndexError:
         return ''
 
-def nl2br(text):
-    '''replace raw newlines with xhtml line breaks.'''
-    return text.replace('\n', '<br/>\n')
+def hexfilter(text):
+    """:hex: Any text. Convert a binary Mercurial node identifier into
+    its long hexadecimal representation.
+    """
+    return node.hex(text)
 
-def obfuscate(text):
-    text = unicode(text, encoding.encoding, 'replace')
-    return ''.join(['&#%d;' % ord(c) for c in text])
+def hgdate(text):
+    """:hgdate: Date. Returns the date as a pair of numbers: "1157407993
+    25200" (Unix timestamp, timezone offset).
+    """
+    return "%d %d" % text
 
-def domain(author):
-    '''get domain of author, or empty string if none.'''
-    f = author.find('@')
-    if f == -1:
-        return ''
-    author = author[f + 1:]
-    f = author.find('>')
-    if f >= 0:
-        author = author[:f]
-    return author
+def isodate(text):
+    """:isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
+    +0200".
+    """
+    return util.datestr(text, '%Y-%m-%d %H:%M %1%2')
 
-def person(author):
-    '''get name of author, or else username.'''
-    if not '@' in author:
-        return author
-    f = author.find('<')
-    if f == -1:
-        return util.shortuser(author)
-    return author[:f].rstrip()
+def isodatesec(text):
+    """:isodatesec: Date. Returns the date in ISO 8601 format, including
+    seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
+    filter.
+    """
+    return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2')
 
 def indent(text, prefix):
     '''indent each non-empty line of text after first with prefix.'''
@@ -124,38 +178,6 @@
                 yield '\n'
     return "".join(indenter())
 
-def permissions(flags):
-    if "l" in flags:
-        return "lrwxrwxrwx"
-    if "x" in flags:
-        return "-rwxr-xr-x"
-    return "-rw-r--r--"
-
-def xmlescape(text):
-    text = (text
-            .replace('&', '&amp;')
-            .replace('<', '&lt;')
-            .replace('>', '&gt;')
-            .replace('"', '&quot;')
-            .replace("'", '&#39;')) # &apos; invalid in HTML
-    return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
-
-def uescape(c):
-    if ord(c) < 0x80:
-        return c
-    else:
-        return '\\u%04x' % ord(c)
-
-_escapes = [
-    ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
-    ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
-]
-
-def jsonescape(s):
-    for k, v in _escapes:
-        s = s.replace(k, v)
-    return ''.join(uescape(c) for c in s)
-
 def json(obj):
     if obj is None or obj is False or obj is True:
         return {None: 'null', False: 'false', True: 'true'}[obj]
@@ -180,49 +202,163 @@
     else:
         raise TypeError('cannot encode type %s' % obj.__class__.__name__)
 
+def _uescape(c):
+    if ord(c) < 0x80:
+        return c
+    else:
+        return '\\u%04x' % ord(c)
+
+_escapes = [
+    ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
+    ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
+]
+
+def jsonescape(s):
+    for k, v in _escapes:
+        s = s.replace(k, v)
+    return ''.join(_uescape(c) for c in s)
+
+def localdate(text):
+    """:localdate: Date. Converts a date to local date."""
+    return (text[0], util.makedate()[1])
+
+def nonempty(str):
+    """:nonempty: Any text. Returns '(none)' if the string is empty."""
+    return str or "(none)"
+
+def obfuscate(text):
+    """:obfuscate: Any text. Returns the input text rendered as a sequence of
+    XML entities.
+    """
+    text = unicode(text, encoding.encoding, 'replace')
+    return ''.join(['&#%d;' % ord(c) for c in text])
+
+def permissions(flags):
+    if "l" in flags:
+        return "lrwxrwxrwx"
+    if "x" in flags:
+        return "-rwxr-xr-x"
+    return "-rw-r--r--"
+
+def person(author):
+    """:person: Any text. Returns the text before an email address."""
+    if not '@' in author:
+        return author
+    f = author.find('<')
+    if f == -1:
+        return util.shortuser(author)
+    return author[:f].rstrip()
+
+def rfc3339date(text):
+    """:rfc3339date: Date. Returns a date using the Internet date format
+    specified in RFC 3339: "2009-08-18T13:00:13+02:00".
+    """
+    return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
+
+def rfc822date(text):
+    """:rfc822date: Date. Returns a date using the same format used in email
+    headers: "Tue, 18 Aug 2009 13:00:13 +0200".
+    """
+    return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
+
+def short(text):
+    """:short: Changeset hash. Returns the short form of a changeset hash,
+    i.e. a 12 hexadecimal digit string.
+    """
+    return text[:12]
+
+def shortdate(text):
+    """:shortdate: Date. Returns a date like "2006-09-18"."""
+    return util.shortdate(text)
+
+def stringescape(text):
+    return text.encode('string_escape')
+
+def stringify(thing):
+    """:stringify: Any type. Turns the value into text by converting values into
+    text and concatenating them.
+    """
+    if hasattr(thing, '__iter__') and not isinstance(thing, str):
+        return "".join([stringify(t) for t in thing if t is not None])
+    return str(thing)
+
+def strip(text):
+    """:strip: Any text. Strips all leading and trailing whitespace."""
+    return text.strip()
+
 def stripdir(text):
-    '''Treat the text as path and strip a directory level, if possible.'''
+    """:stripdir: Treat the text as path and strip a directory level, if
+    possible. For example, "foo" and "foo/bar" becomes "foo".
+    """
     dir = os.path.dirname(text)
     if dir == "":
         return os.path.basename(text)
     else:
         return dir
 
-def nonempty(str):
-    return str or "(none)"
+def tabindent(text):
+    """:tabindent: Any text. Returns the text, with every line except the
+    first starting with a tab character.
+    """
+    return indent(text, '\t')
+
+def urlescape(text):
+    """:urlescape: Any text. Escapes all "special" characters. For example,
+    "foo bar" becomes "foo%20bar".
+    """
+    return urllib.quote(text)
+
+def userfilter(text):
+    """:user: Any text. Returns the user portion of an email address."""
+    return util.shortuser(text)
+
+def xmlescape(text):
+    text = (text
+            .replace('&', '&amp;')
+            .replace('<', '&lt;')
+            .replace('>', '&gt;')
+            .replace('"', '&quot;')
+            .replace("'", '&#39;')) # &apos; invalid in HTML
+    return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
 
 filters = {
-    "addbreaks": nl2br,
-    "basename": os.path.basename,
-    "stripdir": stripdir,
+    "addbreaks": addbreaks,
     "age": age,
-    "date": lambda x: util.datestr(x),
+    "basename": basename,
+    "date": datefilter,
     "domain": domain,
-    "email": util.email,
-    "escape": lambda x: cgi.escape(x, True),
-    "fill68": lambda x: fill(x, width=68),
-    "fill76": lambda x: fill(x, width=76),
+    "email": email,
+    "escape": escape,
+    "fill68": fill68,
+    "fill76": fill76,
     "firstline": firstline,
-    "tabindent": lambda x: indent(x, '\t'),
-    "hgdate": lambda x: "%d %d" % x,
-    "isodate": lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2'),
-    "isodatesec": lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2'),
+    "hex": hexfilter,
+    "hgdate": hgdate,
+    "isodate": isodate,
+    "isodatesec": isodatesec,
     "json": json,
     "jsonescape": jsonescape,
-    "localdate": lambda x: (x[0], util.makedate()[1]),
+    "localdate": localdate,
     "nonempty": nonempty,
     "obfuscate": obfuscate,
     "permissions": permissions,
     "person": person,
-    "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2"),
-    "rfc3339date": lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2"),
-    "hex": node.hex,
-    "short": lambda x: x[:12],
-    "shortdate": util.shortdate,
+    "rfc3339date": rfc3339date,
+    "rfc822date": rfc822date,
+    "short": short,
+    "shortdate": shortdate,
+    "stringescape": stringescape,
     "stringify": stringify,
-    "strip": lambda x: x.strip(),
-    "urlescape": lambda x: urllib.quote(x),
-    "user": lambda x: util.shortuser(x),
-    "stringescape": lambda x: x.encode('string_escape'),
+    "strip": strip,
+    "stripdir": stripdir,
+    "tabindent": tabindent,
+    "urlescape": urlescape,
+    "user": userfilter,
     "xmlescape": xmlescape,
 }
+
+def makedoc(topic, doc):
+    return help.makeitemsdoc(topic, doc, '.. filtersmarker', filters)
+
+# tell hggettext to extract docstrings from these functions:
+i18nfunctions = filters.values()
--- a/mercurial/templatekw.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/templatekw.py	Fri Apr 01 12:21:59 2011 +0200
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 from node import hex
-import encoding, patch, util, error
+import encoding, patch, util, error, help
 
 def showlist(name, values, plural=None, **args):
     '''expand set of values.
@@ -143,32 +143,49 @@
 
 
 def showauthor(repo, ctx, templ, **args):
+    """:author: String. The unmodified author of the changeset."""
     return ctx.user()
 
 def showbranch(**args):
+    """:branch: String. The name of the branch on which the changeset was
+    committed.
+    """
     return args['ctx'].branch()
 
 def showbranches(**args):
+    """:branches: List of strings. The name of the branch on which the
+    changeset was committed. Will be empty if the branch name was
+    default.
+    """
     branch = args['ctx'].branch()
     if branch != 'default':
         return showlist('branch', [branch], plural='branches', **args)
 
 def showbookmarks(**args):
+    """:bookmarks: List of strings. Any bookmarks associated with the
+    changeset.
+    """
     bookmarks = args['ctx'].bookmarks()
     return showlist('bookmark', bookmarks, **args)
 
 def showchildren(**args):
+    """:children: List of strings. The children of the changeset."""
     ctx = args['ctx']
     childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
     return showlist('children', childrevs, **args)
 
 def showdate(repo, ctx, templ, **args):
+    """:date: Date information. The date when the changeset was committed."""
     return ctx.date()
 
 def showdescription(repo, ctx, templ, **args):
+    """:desc: String. The text of the changeset description."""
     return ctx.description().strip()
 
 def showdiffstat(repo, ctx, templ, **args):
+    """:diffstat: String. Statistics of changes with the following format:
+    "modified files: +added/-removed lines"
+    """
     files, adds, removes = 0, 0, 0
     for i in patch.diffstatdata(util.iterlines(ctx.diff())):
         files += 1
@@ -184,10 +201,14 @@
         yield templ('extra', **args)
 
 def showfileadds(**args):
+    """:file_adds: List of strings. Files added by this changeset."""
     repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
     return showlist('file_add', getfiles(repo, ctx, revcache)[1], **args)
 
 def showfilecopies(**args):
+    """:file_copies: List of strings. Files copied in this changeset with
+    their sources.
+    """
     cache, ctx = args['cache'], args['ctx']
     copies = args['revcache'].get('copies')
     if copies is None:
@@ -207,25 +228,37 @@
 # provided before calling the templater, usually with a --copies
 # command line switch.
 def showfilecopiesswitch(**args):
+    """:file_copies_switch: List of strings. Like "file_copies" but displayed
+    only if the --copied switch is set.
+    """
     copies = args['revcache'].get('copies') or []
     c = [{'name': x[0], 'source': x[1]} for x in copies]
     return showlist('file_copy', c, plural='file_copies', **args)
 
 def showfiledels(**args):
+    """:file_dels: List of strings. Files removed by this changeset."""
     repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
     return showlist('file_del', getfiles(repo, ctx, revcache)[2], **args)
 
 def showfilemods(**args):
+    """:file_mods: List of strings. Files modified by this changeset."""
     repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
     return showlist('file_mod', getfiles(repo, ctx, revcache)[0], **args)
 
 def showfiles(**args):
+    """:files: List of strings. All files modified, added, or removed by this
+    changeset.
+    """
     return showlist('file', args['ctx'].files(), **args)
 
 def showlatesttag(repo, ctx, templ, cache, **args):
+    """:latesttag: String. Most recent global tag in the ancestors of this
+    changeset.
+    """
     return getlatesttags(repo, ctx, cache)[2]
 
 def showlatesttagdistance(repo, ctx, templ, cache, **args):
+    """:latesttagdistance: Integer. Longest path to the latest tag."""
     return getlatesttags(repo, ctx, cache)[1]
 
 def showmanifest(**args):
@@ -236,12 +269,17 @@
     return templ('manifest', **args)
 
 def shownode(repo, ctx, templ, **args):
+    """:node: String. The changeset identification hash, as a 40 hexadecimal
+    digit string.
+    """
     return ctx.hex()
 
 def showrev(repo, ctx, templ, **args):
+    """:rev: Integer. The repository-local changeset revision number."""
     return ctx.rev()
 
 def showtags(**args):
+    """:tags: List of strings. Any tags associated with the changeset."""
     return showlist('tag', args['ctx'].tags(), **args)
 
 # keywords are callables like:
@@ -276,3 +314,8 @@
     'tags': showtags,
 }
 
+def makedoc(topic, doc):
+    return help.makeitemsdoc(topic, doc, '.. keywordsmarker', keywords)
+
+# tell hggettext to extract docstrings from these functions:
+i18nfunctions = keywords.values()
--- a/mercurial/templater.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/templater.py	Fri Apr 01 12:21:59 2011 +0200
@@ -69,7 +69,6 @@
         else:
             raise error.ParseError(_("syntax error"), pos)
         pos += 1
-    data[2] = pos
     yield ('end', None, pos)
 
 def compiletemplate(tmpl, context):
@@ -91,8 +90,8 @@
             parsed.append(("string", tmpl[pos:n]))
 
         pd = [tmpl, n + 1, stop]
-        parsed.append(p.parse(pd))
-        pos = pd[2]
+        parseres, pos = p.parse(pd)
+        parsed.append(parseres)
 
     return [compileexp(e, context) for e in parsed]
 
--- a/mercurial/ui.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/ui.py	Fri Apr 01 12:21:59 2011 +0200
@@ -7,7 +7,7 @@
 
 from i18n import _
 import errno, getpass, os, socket, sys, tempfile, traceback
-import config, util, error
+import config, util, error, url
 
 class ui(object):
     def __init__(self, src=None):
@@ -111,7 +111,7 @@
                                   % (n, p, self.configsource('paths', n)))
                         p = p.replace('%%', '%')
                     p = util.expandpath(p)
-                    if '://' not in p and not os.path.isabs(p):
+                    if not url.hasscheme(p) and not os.path.isabs(p):
                         p = os.path.normpath(os.path.join(root, p))
                     c.set("paths", n, p)
 
@@ -273,7 +273,7 @@
         cfg = self._data(untrusted)
         for section in cfg.sections():
             for name, value in self.configitems(section, untrusted):
-                yield section, name, str(value).replace('\n', '\\n')
+                yield section, name, value
 
     def plain(self):
         '''is plain mode active?
@@ -325,7 +325,7 @@
 
     def expandpath(self, loc, default=None):
         """Return repository location relative to cwd or from [paths]"""
-        if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
+        if url.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
             return loc
 
         path = self.config('paths', loc)
@@ -483,7 +483,7 @@
             self.write(msg, ' ', default, "\n")
             return default
         try:
-            r = self._readline(msg + ' ')
+            r = self._readline(self.label(msg, 'ui.prompt') + ' ')
             if not r:
                 return default
             return r
--- a/mercurial/url.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/url.py	Fri Apr 01 12:21:59 2011 +0200
@@ -7,33 +7,271 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
+import urllib, urllib2, httplib, os, socket, cStringIO
 import __builtin__
 from i18n import _
 import keepalive, util
 
-def _urlunparse(scheme, netloc, path, params, query, fragment, url):
-    '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
-    result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
-    if (scheme and
-        result.startswith(scheme + ':') and
-        not result.startswith(scheme + '://') and
-        url.startswith(scheme + '://')
-       ):
-        result = scheme + '://' + result[len(scheme + ':'):]
-    return result
+class url(object):
+    """Reliable URL parser.
+
+    This parses URLs and provides attributes for the following
+    components:
+
+    <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
+
+    Missing components are set to None. The only exception is
+    fragment, which is set to '' if present but empty.
+
+    If parsefragment is False, fragment is included in query. If
+    parsequery is False, query is included in path. If both are
+    False, both fragment and query are included in path.
+
+    See http://www.ietf.org/rfc/rfc2396.txt for more information.
+
+    Note that for backward compatibility reasons, bundle URLs do not
+    take host names. That means 'bundle://../' has a path of '../'.
+
+    Examples:
+
+    >>> url('http://www.ietf.org/rfc/rfc2396.txt')
+    <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
+    >>> url('ssh://[::1]:2200//home/joe/repo')
+    <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
+    >>> url('file:///home/joe/repo')
+    <url scheme: 'file', path: '/home/joe/repo'>
+    >>> url('bundle:foo')
+    <url scheme: 'bundle', path: 'foo'>
+    >>> url('bundle://../foo')
+    <url scheme: 'bundle', path: '../foo'>
+    >>> url('c:\\\\foo\\\\bar')
+    <url path: 'c:\\\\foo\\\\bar'>
+
+    Authentication credentials:
+
+    >>> url('ssh://joe:xyz@x/repo')
+    <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
+    >>> url('ssh://joe@x/repo')
+    <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
+
+    Query strings and fragments:
+
+    >>> url('http://host/a?b#c')
+    <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
+    >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
+    <url scheme: 'http', host: 'host', path: 'a?b#c'>
+    """
+
+    _safechars = "!~*'()+"
+    _safepchars = "/!~*'()+"
+
+    def __init__(self, path, parsequery=True, parsefragment=True):
+        # We slowly chomp away at path until we have only the path left
+        self.scheme = self.user = self.passwd = self.host = None
+        self.port = self.path = self.query = self.fragment = None
+        self._localpath = True
+        self._hostport = ''
+        self._origpath = path
+
+        # special case for Windows drive letters
+        if hasdriveletter(path):
+            self.path = path
+            return
+
+        # For compatibility reasons, we can't handle bundle paths as
+        # normal URLS
+        if path.startswith('bundle:'):
+            self.scheme = 'bundle'
+            path = path[7:]
+            if path.startswith('//'):
+                path = path[2:]
+            self.path = path
+            return
+
+        if not path.startswith('/') and ':' in path:
+            parts = path.split(':', 1)
+            if parts[0]:
+                self.scheme, path = parts
+                self._localpath = False
+
+        if not path:
+            path = None
+            if self._localpath:
+                self.path = ''
+                return
+        else:
+            if parsefragment and '#' in path:
+                path, self.fragment = path.split('#', 1)
+                if not path:
+                    path = None
+            if self._localpath:
+                self.path = path
+                return
+
+            if parsequery and '?' in path:
+                path, self.query = path.split('?', 1)
+                if not path:
+                    path = None
+                if not self.query:
+                    self.query = None
+
+            # // is required to specify a host/authority
+            if path and path.startswith('//'):
+                parts = path[2:].split('/', 1)
+                if len(parts) > 1:
+                    self.host, path = parts
+                    path = path
+                else:
+                    self.host = parts[0]
+                    path = None
+                if not self.host:
+                    self.host = None
+                    if path:
+                        path = '/' + path
+
+            if self.host and '@' in self.host:
+                self.user, self.host = self.host.rsplit('@', 1)
+                if ':' in self.user:
+                    self.user, self.passwd = self.user.split(':', 1)
+                if not self.host:
+                    self.host = None
 
-def hidepassword(url):
-    '''hide user credential in a url string'''
-    scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
-    netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
-    return _urlunparse(scheme, netloc, path, params, query, fragment, url)
+            # Don't split on colons in IPv6 addresses without ports
+            if (self.host and ':' in self.host and
+                not (self.host.startswith('[') and self.host.endswith(']'))):
+                self._hostport = self.host
+                self.host, self.port = self.host.rsplit(':', 1)
+                if not self.host:
+                    self.host = None
+
+            if (self.host and self.scheme == 'file' and
+                self.host not in ('localhost', '127.0.0.1', '[::1]')):
+                raise util.Abort(_('file:// URLs can only refer to localhost'))
+
+        self.path = path
+
+        for a in ('user', 'passwd', 'host', 'port',
+                  'path', 'query', 'fragment'):
+            v = getattr(self, a)
+            if v is not None:
+                setattr(self, a, urllib.unquote(v))
+
+    def __repr__(self):
+        attrs = []
+        for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
+                  'query', 'fragment'):
+            v = getattr(self, a)
+            if v is not None:
+                attrs.append('%s: %r' % (a, v))
+        return '<url %s>' % ', '.join(attrs)
+
+    def __str__(self):
+        """Join the URL's components back into a URL string.
+
+        Examples:
+
+        >>> str(url('http://user:pw@host:80/?foo#bar'))
+        'http://user:pw@host:80/?foo#bar'
+        >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
+        'ssh://user:pw@[::1]:2200//home/joe#'
+        >>> str(url('http://localhost:80//'))
+        'http://localhost:80//'
+        >>> str(url('http://localhost:80/'))
+        'http://localhost:80/'
+        >>> str(url('http://localhost:80'))
+        'http://localhost:80/'
+        >>> str(url('bundle:foo'))
+        'bundle:foo'
+        >>> str(url('bundle://../foo'))
+        'bundle:../foo'
+        >>> str(url('path'))
+        'path'
+        """
+        if self._localpath:
+            s = self.path
+            if self.scheme == 'bundle':
+                s = 'bundle:' + s
+            if self.fragment:
+                s += '#' + self.fragment
+            return s
 
-def removeauth(url):
+        s = self.scheme + ':'
+        if (self.user or self.passwd or self.host or
+            self.scheme and not self.path):
+            s += '//'
+        if self.user:
+            s += urllib.quote(self.user, safe=self._safechars)
+        if self.passwd:
+            s += ':' + urllib.quote(self.passwd, safe=self._safechars)
+        if self.user or self.passwd:
+            s += '@'
+        if self.host:
+            if not (self.host.startswith('[') and self.host.endswith(']')):
+                s += urllib.quote(self.host)
+            else:
+                s += self.host
+        if self.port:
+            s += ':' + urllib.quote(self.port)
+        if self.host:
+            s += '/'
+        if self.path:
+            s += urllib.quote(self.path, safe=self._safepchars)
+        if self.query:
+            s += '?' + urllib.quote(self.query, safe=self._safepchars)
+        if self.fragment is not None:
+            s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
+        return s
+
+    def authinfo(self):
+        user, passwd = self.user, self.passwd
+        try:
+            self.user, self.passwd = None, None
+            s = str(self)
+        finally:
+            self.user, self.passwd = user, passwd
+        if not self.user:
+            return (s, None)
+        return (s, (None, (str(self), self.host),
+                    self.user, self.passwd or ''))
+
+    def localpath(self):
+        if self.scheme == 'file' or self.scheme == 'bundle':
+            path = self.path or '/'
+            # For Windows, we need to promote hosts containing drive
+            # letters to paths with drive letters.
+            if hasdriveletter(self._hostport):
+                path = self._hostport + '/' + self.path
+            elif self.host is not None and self.path:
+                path = '/' + path
+            # We also need to handle the case of file:///C:/, which
+            # should return C:/, not /C:/.
+            elif hasdriveletter(path):
+                # Strip leading slash from paths with drive names
+                return path[1:]
+            return path
+        return self._origpath
+
+def hasscheme(path):
+    return bool(url(path).scheme)
+
+def hasdriveletter(path):
+    return path[1:2] == ':' and path[0:1].isalpha()
+
+def localpath(path):
+    return url(path, parsequery=False, parsefragment=False).localpath()
+
+def hidepassword(u):
+    '''hide user credential in a url string'''
+    u = url(u)
+    if u.passwd:
+        u.passwd = '***'
+    return str(u)
+
+def removeauth(u):
     '''remove all authentication information from a url string'''
-    scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
-    netloc = netloc[netloc.find('@')+1:]
-    return _urlunparse(scheme, netloc, path, params, query, fragment, url)
+    u = url(u)
+    u.user = u.passwd = None
+    return str(u)
 
 def netlocsplit(netloc):
     '''split [user[:passwd]@]host[:port] into 4-tuple.'''
@@ -192,14 +430,10 @@
             if not (proxyurl.startswith('http:') or
                     proxyurl.startswith('https:')):
                 proxyurl = 'http://' + proxyurl + '/'
-            snpqf = urlparse.urlsplit(proxyurl)
-            proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
-            hpup = netlocsplit(proxynetloc)
-
-            proxyhost, proxyport, proxyuser, proxypasswd = hpup
-            if not proxyuser:
-                proxyuser = ui.config("http_proxy", "user")
-                proxypasswd = ui.config("http_proxy", "passwd")
+            proxy = url(proxyurl)
+            if not proxy.user:
+                proxy.user = ui.config("http_proxy", "user")
+                proxy.passwd = ui.config("http_proxy", "passwd")
 
             # see if we should use a proxy for this url
             no_list = ["localhost", "127.0.0.1"]
@@ -214,13 +448,10 @@
             else:
                 self.no_list = no_list
 
-            proxyurl = urlparse.urlunsplit((
-                proxyscheme, netlocunsplit(proxyhost, proxyport,
-                                                proxyuser, proxypasswd or ''),
-                proxypath, proxyquery, proxyfrag))
+            proxyurl = str(proxy)
             proxies = {'http': proxyurl, 'https': proxyurl}
             ui.debug('proxying through http://%s:%s\n' %
-                      (proxyhost, proxyport))
+                      (proxy.host, proxy.port))
         else:
             proxies = {}
 
@@ -387,13 +618,9 @@
         new_tunnel = False
 
     if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
-        urlparts = urlparse.urlparse(tunnel_host)
-        if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
-            realhostport = urlparts[1]
-            if realhostport[-1] == ']' or ':' not in realhostport:
-                realhostport += ':443'
-
-            h.realhostport = realhostport
+        u = url(tunnel_host)
+        if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
+            h.realhostport = ':'.join([u.host, (u.port or '443')])
             h.headers = req.headers.copy()
             h.headers.update(handler.parent.addheaders)
             return
@@ -691,31 +918,6 @@
         return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
                         self, auth_header, host, req, headers)
 
-def getauthinfo(path):
-    scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
-    if not urlpath:
-        urlpath = '/'
-    if scheme != 'file':
-        # XXX: why are we quoting the path again with some smart
-        # heuristic here? Anyway, it cannot be done with file://
-        # urls since path encoding is os/fs dependent (see
-        # urllib.pathname2url() for details).
-        urlpath = quotepath(urlpath)
-    host, port, user, passwd = netlocsplit(netloc)
-
-    # urllib cannot handle URLs with embedded user or passwd
-    url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
-                              urlpath, query, frag))
-    if user:
-        netloc = host
-        if port:
-            netloc += ':' + port
-        # Python < 2.4.3 uses only the netloc to search for a password
-        authinfo = (None, (url, netloc), user, passwd or '')
-    else:
-        authinfo = None
-    return url, authinfo
-
 handlerfuncs = []
 
 def opener(ui, authinfo=None):
@@ -746,17 +948,13 @@
     opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
     return opener
 
-scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
-
-def open(ui, url, data=None):
-    scheme = None
-    m = scheme_re.search(url)
-    if m:
-        scheme = m.group(1).lower()
-    if not scheme:
-        path = util.normpath(os.path.abspath(url))
-        url = 'file://' + urllib.pathname2url(path)
+def open(ui, url_, data=None):
+    u = url(url_)
+    if u.scheme:
+        u.scheme = u.scheme.lower()
+        url_, authinfo = u.authinfo()
+    else:
+        path = util.normpath(os.path.abspath(url_))
+        url_ = 'file://' + urllib.pathname2url(path)
         authinfo = None
-    else:
-        url, authinfo = getauthinfo(url)
-    return opener(ui, authinfo).open(url, data)
+    return opener(ui, authinfo).open(url_, data)
--- a/mercurial/util.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/util.py	Fri Apr 01 12:21:59 2011 +0200
@@ -438,6 +438,9 @@
 
     return check
 
+def makedir(path, notindexed):
+    os.mkdir(path)
+
 def unlinkpath(f):
     """unlink and remove the directory if it is empty"""
     os.unlink(f)
@@ -769,7 +772,18 @@
 
 def gui():
     '''Are we running in a GUI?'''
-    return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
+    if sys.platform == 'darwin':
+        if 'SSH_CONNECTION' in os.environ:
+            # handle SSH access to a box where the user is logged in
+            return False
+        elif getattr(osutil, 'isgui', None):
+            # check if a CoreGraphics session is available
+            return osutil.isgui()
+        else:
+            # pure build; use a safe default
+            return True
+    else:
+        return os.name == "nt" or os.environ.get("DISPLAY")
 
 def mktempcopy(name, emptyok=False, createmode=None):
     """Create a temporary file with the same contents from name
@@ -1204,7 +1218,10 @@
         return parsedate(date, extendeddateformats, d)[0]
 
     date = date.strip()
-    if date[0] == "<":
+
+    if not date:
+        raise Abort(_("dates cannot consist entirely of whitespace"))
+    elif date[0] == "<":
         when = upper(date[1:])
         return lambda x: x <= when
     elif date[0] == ">":
@@ -1367,26 +1384,6 @@
             return format % (nbytes / float(divisor))
     return units[-1][2] % nbytes
 
-def drop_scheme(scheme, path):
-    sc = scheme + ':'
-    if path.startswith(sc):
-        path = path[len(sc):]
-        if path.startswith('//'):
-            if scheme == 'file':
-                i = path.find('/', 2)
-                if i == -1:
-                    return ''
-                # On Windows, absolute paths are rooted at the current drive
-                # root. On POSIX they are rooted at the file system root.
-                if os.name == 'nt':
-                    droot = os.path.splitdrive(os.getcwd())[0] + '/'
-                    path = os.path.join(droot, path[i + 1:])
-                else:
-                    path = path[i:]
-            else:
-                path = path[2:]
-    return path
-
 def uirepr(s):
     # Avoid double backslash in Windows path repr()
     return repr(s).replace('\\\\', '\\')
--- a/mercurial/win32.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/win32.py	Fri Apr 01 12:21:59 2011 +0200
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 import encoding
-import ctypes, errno, os, struct, subprocess
+import ctypes, errno, os, struct, subprocess, random
 
 _kernel32 = ctypes.windll.kernel32
 
@@ -56,6 +56,10 @@
 
 _OPEN_EXISTING = 3
 
+# SetFileAttributes
+_FILE_ATTRIBUTE_NORMAL = 0x80
+_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000
+
 # Process Security and Access Rights
 _PROCESS_QUERY_INFORMATION = 0x0400
 
@@ -316,3 +320,54 @@
         raise ctypes.WinError()
 
     return pi.dwProcessId
+
+def unlink(f):
+    '''try to implement POSIX' unlink semantics on Windows'''
+
+    # POSIX allows to unlink and rename open files. Windows has serious
+    # problems with doing that:
+    # - Calling os.unlink (or os.rename) on a file f fails if f or any
+    #   hardlinked copy of f has been opened with Python's open(). There is no
+    #   way such a file can be deleted or renamed on Windows (other than
+    #   scheduling the delete or rename for the next reboot).
+    # - Calling os.unlink on a file that has been opened with Mercurial's
+    #   posixfile (or comparable methods) will delay the actual deletion of
+    #   the file for as long as the file is held open. The filename is blocked
+    #   during that time and cannot be used for recreating a new file under
+    #   that same name ("zombie file"). Directories containing such zombie files
+    #   cannot be removed or moved.
+    # A file that has been opened with posixfile can be renamed, so we rename
+    # f to a random temporary name before calling os.unlink on it. This allows
+    # callers to recreate f immediately while having other readers do their
+    # implicit zombie filename blocking on a temporary name.
+
+    for tries in xrange(10):
+        temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
+        try:
+            os.rename(f, temp)  # raises OSError EEXIST if temp exists
+            break
+        except OSError, e:
+            if e.errno != errno.EEXIST:
+                raise
+    else:
+        raise IOError, (errno.EEXIST, "No usable temporary filename found")
+
+    try:
+        os.unlink(temp)
+    except OSError:
+        # The unlink might have failed because the READONLY attribute may heave
+        # been set on the original file. Rename works fine with READONLY set,
+        # but not os.unlink. Reset all attributes and try again.
+        _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL)
+        try:
+            os.unlink(temp)
+        except OSError:
+            # The unlink might have failed due to some very rude AV-Scanners.
+            # Leaking a tempfile is the lesser evil than aborting here and
+            # leaving some potentially serious inconsistencies.
+            pass
+
+def makedir(path, notindexed):
+    os.mkdir(path)
+    if notindexed:
+        _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
--- a/mercurial/windows.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/windows.py	Fri Apr 01 12:21:59 2011 +0200
@@ -7,7 +7,7 @@
 
 from i18n import _
 import osutil, error
-import errno, msvcrt, os, re, sys, random, subprocess
+import errno, msvcrt, os, re, sys, subprocess
 
 nulldev = 'NUL:'
 umask = 002
@@ -286,53 +286,13 @@
 
 def unlinkpath(f):
     """unlink and remove the directory if it is empty"""
-    os.unlink(f)
+    unlink(f)
     # try removing directories that might now be empty
     try:
         _removedirs(os.path.dirname(f))
     except OSError:
         pass
 
-def unlink(f):
-    '''try to implement POSIX' unlink semantics on Windows'''
-
-    # POSIX allows to unlink and rename open files. Windows has serious
-    # problems with doing that:
-    # - Calling os.unlink (or os.rename) on a file f fails if f or any
-    #   hardlinked copy of f has been opened with Python's open(). There is no
-    #   way such a file can be deleted or renamed on Windows (other than
-    #   scheduling the delete or rename for the next reboot).
-    # - Calling os.unlink on a file that has been opened with Mercurial's
-    #   posixfile (or comparable methods) will delay the actual deletion of
-    #   the file for as long as the file is held open. The filename is blocked
-    #   during that time and cannot be used for recreating a new file under
-    #   that same name ("zombie file"). Directories containing such zombie files
-    #   cannot be removed or moved.
-    # A file that has been opened with posixfile can be renamed, so we rename
-    # f to a random temporary name before calling os.unlink on it. This allows
-    # callers to recreate f immediately while having other readers do their
-    # implicit zombie filename blocking on a temporary name.
-
-    for tries in xrange(10):
-        temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
-        try:
-            os.rename(f, temp)  # raises OSError EEXIST if temp exists
-            break
-        except OSError, e:
-            if e.errno != errno.EEXIST:
-                raise
-    else:
-        raise IOError, (errno.EEXIST, "No usable temporary filename found")
-
-    try:
-        os.unlink(temp)
-    except:
-        # Some very rude AV-scanners on Windows may cause this unlink to fail.
-        # Not aborting here just leaks the temp file, whereas aborting at this
-        # point may leave serious inconsistencies. Ideally, we would notify
-        # the user in this case here.
-        pass
-
 def rename(src, dst):
     '''atomically rename file src to dst, replacing dst if it exists'''
     try:
--- a/mercurial/wireproto.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/mercurial/wireproto.py	Fri Apr 01 12:21:59 2011 +0200
@@ -15,7 +15,9 @@
 # list of nodes encoding / decoding
 
 def decodelist(l, sep=' '):
-    return map(bin, l.split(sep))
+    if l:
+        return map(bin, l.split(sep))
+    return []
 
 def encodelist(l, sep=' '):
     return sep.join(map(hex, l))
@@ -35,7 +37,15 @@
         d = self._call("heads")
         try:
             return decodelist(d[:-1])
-        except:
+        except ValueError:
+            self._abort(error.ResponseError(_("unexpected response:"), d))
+
+    def known(self, nodes):
+        n = encodelist(nodes)
+        d = self._call("known", nodes=n)
+        try:
+            return [bool(int(f)) for f in d]
+        except ValueError:
             self._abort(error.ResponseError(_("unexpected response:"), d))
 
     def branchmap(self):
@@ -57,7 +67,7 @@
         try:
             br = [tuple(decodelist(b)) for b in d.splitlines()]
             return br
-        except:
+        except ValueError:
             self._abort(error.ResponseError(_("unexpected response:"), d))
 
     def between(self, pairs):
@@ -68,7 +78,7 @@
             d = self._call("between", pairs=n)
             try:
                 r.extend(l and decodelist(l) or [] for l in d.splitlines())
-            except:
+            except ValueError:
                 self._abort(error.ResponseError(_("unexpected response:"), d))
         return r
 
@@ -113,6 +123,16 @@
                              bases=bases, heads=heads)
         return changegroupmod.unbundle10(self._decompress(f), 'UN')
 
+    def getbundle(self, source, heads=None, common=None):
+        self.requirecap('getbundle', _('look up remote changes'))
+        opts = {}
+        if heads is not None:
+            opts['heads'] = encodelist(heads)
+        if common is not None:
+            opts['common'] = encodelist(common)
+        f = self._callstream("getbundle", **opts)
+        return changegroupmod.unbundle10(self._decompress(f), 'UN')
+
     def unbundle(self, cg, heads, source):
         '''Send cg (a readable file-like object representing the
         changegroup to push, typically a chunkbuffer object) to the
@@ -133,6 +153,15 @@
             self.ui.status(_('remote: '), l)
         return ret
 
+    def debugwireargs(self, one, two, three=None, four=None):
+        # don't pass optional arguments left at their default value
+        opts = {}
+        if three is not None:
+            opts['three'] = three
+        if four is not None:
+            opts['four'] = four
+        return self._call('debugwireargs', one=one, two=two, **opts)
+
 # server side
 
 class streamres(object):
@@ -152,6 +181,17 @@
     args = proto.getargs(spec)
     return func(repo, proto, *args)
 
+def options(cmd, keys, others):
+    opts = {}
+    for k in keys:
+        if k in others:
+            opts[k] = others[k]
+            del others[k]
+    if others:
+        sys.stderr.write("abort: %s got unexpected arguments %s\n"
+                         % (cmd, ",".join(others)))
+    return opts
+
 def between(repo, proto, pairs):
     pairs = [decodelist(p, '-') for p in pairs.split(" ")]
     r = []
@@ -176,7 +216,7 @@
     return "".join(r)
 
 def capabilities(repo, proto):
-    caps = 'lookup changegroupsubset branchmap pushkey'.split()
+    caps = 'lookup changegroupsubset branchmap pushkey known getbundle'.split()
     if _allowstream(repo.ui):
         requiredformats = repo.requirements & repo.supportedformats
         # if our local revlogs are just revlogv1, add 'stream' cap
@@ -199,6 +239,18 @@
     cg = repo.changegroupsubset(bases, heads, 'serve')
     return streamres(proto.groupchunks(cg))
 
+def debugwireargs(repo, proto, one, two, others):
+    # only accept optional args from the known set
+    opts = options('debugwireargs', ['three', 'four'], others)
+    return repo.debugwireargs(one, two, **opts)
+
+def getbundle(repo, proto, others):
+    opts = options('getbundle', ['heads', 'common'], others)
+    for k, v in opts.iteritems():
+        opts[k] = decodelist(v)
+    cg = repo.getbundle('serve', **opts)
+    return streamres(proto.groupchunks(cg))
+
 def heads(repo, proto):
     h = repo.heads()
     return encodelist(h) + "\n"
@@ -228,6 +280,9 @@
         success = 0
     return "%s %s\n" % (success, r)
 
+def known(repo, proto, nodes):
+    return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
+
 def pushkey(repo, proto, namespace, key, old, new):
     # compatibility with pre-1.8 clients which were accidentally
     # sending raw binary nodes rather than utf-8-encoded hex
@@ -343,8 +398,11 @@
     'capabilities': (capabilities, ''),
     'changegroup': (changegroup, 'roots'),
     'changegroupsubset': (changegroupsubset, 'bases heads'),
+    'debugwireargs': (debugwireargs, 'one two *'),
+    'getbundle': (getbundle, '*'),
     'heads': (heads, ''),
     'hello': (hello, ''),
+    'known': (known, 'nodes'),
     'listkeys': (listkeys, 'namespace'),
     'lookup': (lookup, 'key'),
     'pushkey': (pushkey, 'namespace key old new'),
--- a/setup.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/setup.py	Fri Apr 01 12:21:59 2011 +0200
@@ -98,24 +98,8 @@
 try:
     import py2exe
     py2exeloaded = True
-
-    # Help py2exe to find win32com.shell
-    try:
-        import modulefinder
-        import win32com
-        for p in win32com.__path__[1:]: # Take the path to win32comext
-            modulefinder.AddPackagePath("win32com", p)
-        pn = "win32com.shell"
-        __import__(pn)
-        m = sys.modules[pn]
-        for p in m.__path__[1:]:
-            modulefinder.AddPackagePath(pn, p)
-    except ImportError:
-        pass
-
 except ImportError:
     py2exeloaded = False
-    pass
 
 def runcmd(cmd, env):
     p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
@@ -330,11 +314,17 @@
     Extension('mercurial.parsers', ['mercurial/parsers.c']),
     ]
 
+osutil_ldflags = []
+
+if sys.platform == 'darwin':
+    osutil_ldflags += ['-framework', 'ApplicationServices']
+
 # disable osutil.c under windows + python 2.4 (issue1364)
 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
     pymodules.append('mercurial.pure.osutil')
 else:
-    extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
+    extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
+                                extra_link_args=osutil_ldflags))
 
 if sys.platform == 'linux2' and os.uname()[2] > '2.6':
     # The inotify extension is only usable with Linux 2.6 kernels.
--- a/tests/run-tests.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/run-tests.py	Fri Apr 01 12:21:59 2011 +0200
@@ -227,8 +227,8 @@
                 continue
 
             for line in f.readlines():
-                line = line.strip()
-                if line and not line.startswith('#'):
+                line = line.split('#', 1)[0].strip()
+                if line:
                     blacklist[line] = filename
 
             f.close()
@@ -694,7 +694,9 @@
         runner = shtest
 
     # Make a tmp subdirectory to work in
-    testtmp = os.environ["TESTTMP"] = os.path.join(HGTMP, test)
+    testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
+        os.path.join(HGTMP, test)
+
     os.mkdir(testtmp)
     os.chdir(testtmp)
 
--- a/tests/test-586.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-586.t	Fri Apr 01 12:21:59 2011 +0200
@@ -17,6 +17,7 @@
   pulling from ../a
   searching for changes
   warning: repository is unrelated
+  requesting all changes
   adding changesets
   adding manifests
   adding file changes
@@ -66,6 +67,7 @@
   pulling from ../repob
   searching for changes
   warning: repository is unrelated
+  requesting all changes
   adding changesets
   adding manifests
   adding file changes
--- a/tests/test-acl.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-acl.t	Fri Apr 01 12:21:59 2011 +0200
@@ -90,37 +90,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -168,37 +145,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -249,37 +203,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -339,37 +270,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -426,37 +334,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -518,37 +403,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -607,37 +469,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -701,37 +540,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -792,37 +608,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -885,37 +678,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -982,37 +752,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -1082,37 +829,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -1176,37 +900,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -1282,37 +983,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -1380,37 +1058,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -1474,37 +1129,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -1570,37 +1202,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
@@ -1665,37 +1274,14 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   adding changesets
-  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
--- a/tests/test-annotate.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-annotate.t	Fri Apr 01 12:21:59 2011 +0200
@@ -228,3 +228,8 @@
   $ hg annotate --follow foo
   foo: foo
 
+missing file
+
+  $ hg ann nosuchfile
+  abort: nosuchfile: no such file in rev c8abddb41a00
+  [255]
--- a/tests/test-basic.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-basic.t	Fri Apr 01 12:21:59 2011 +0200
@@ -20,6 +20,22 @@
   summary:     test
   
 
+Verify that updating to revision 0 via commands.update() works properly
+
+  $ cat <<EOF > update_to_rev0.py
+  > from mercurial import ui, hg, commands
+  > myui = ui.ui()
+  > repo = hg.repository(myui, path='.')
+  > commands.update(myui, repo, rev=0)
+  > EOF
+  $ hg up null
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ python ./update_to_rev0.py
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg identify -n
+  0
+ 
+
 Poke around at hashes:
 
   $ hg manifest --debug
--- a/tests/test-bisect.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-bisect.t	Fri Apr 01 12:21:59 2011 +0200
@@ -377,6 +377,44 @@
   date:        Thu Jan 01 00:00:06 1970 +0000
   summary:     msg 6
   
+  $ hg log -r "bisected(good)"
+  changeset:   0:b99c7b9c8e11
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     msg 0
+  
+  changeset:   5:7874a09ea728
+  user:        test
+  date:        Thu Jan 01 00:00:05 1970 +0000
+  summary:     msg 5
+  
+  $ hg log -r "bisected(bad)"
+  changeset:   6:a3d5c6fdf0d3
+  user:        test
+  date:        Thu Jan 01 00:00:06 1970 +0000
+  summary:     msg 6
+  
+  $ hg log -r "bisected(skip)"
+  changeset:   1:5cd978ea5149
+  user:        test
+  date:        Thu Jan 01 00:00:01 1970 +0000
+  summary:     msg 1
+  
+  changeset:   2:db07c04beaca
+  user:        test
+  date:        Thu Jan 01 00:00:02 1970 +0000
+  summary:     msg 2
+  
+  changeset:   3:b53bea5e2fcb
+  user:        test
+  date:        Thu Jan 01 00:00:03 1970 +0000
+  summary:     msg 3
+  
+  changeset:   4:9b2ba8336a65
+  user:        test
+  date:        Thu Jan 01 00:00:04 1970 +0000
+  summary:     msg 4
+  
 
   $ set +e
 
--- a/tests/test-bisect2.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-bisect2.t	Fri Apr 01 12:21:59 2011 +0200
@@ -416,10 +416,14 @@
   summary:     merge 10,13
   
   Not all ancestors of this changeset have been checked.
-  To check the other ancestors, start from the common ancestor, dab8161ac8fc.
-  $ hg bisect -g 8 # dab8161ac8fc
+  Use bisect --extend to continue the bisection from
+  the common ancestor, dab8161ac8fc.
+  $ hg bisect --extend
+  Extending search to changeset 8:dab8161ac8fc
+  2 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg bisect -g # dab8161ac8fc
   Testing changeset 9:3c77083deb4a (3 changesets remaining, ~1 tests)
-  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg bisect -b
   The first bad revision is:
   changeset:   9:3c77083deb4a
--- a/tests/test-bookmarks-pushpull.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-bookmarks-pushpull.t	Fri Apr 01 12:21:59 2011 +0200
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  updating bookmark Y
   (run 'hg update' to get a working copy)
   $ hg bookmarks
      Y                         0:4e3505fd9583
@@ -176,5 +177,19 @@
   no changes found
   not updating divergent bookmark X
   importing bookmark Z
+  $ hg clone http://localhost:$HGPORT/ cloned-bookmarks
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files (+1 heads)
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R cloned-bookmarks bookmarks
+     X                         1:9b140be10808
+     Y                         0:4e3505fd9583
+     Z                         2:0d2164f0ce0d
+     foo                       -1:000000000000
+     foobar                    -1:000000000000
 
   $ kill `cat ../hg.pid`
--- a/tests/test-bookmarks.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-bookmarks.t	Fri Apr 01 12:21:59 2011 +0200
@@ -244,3 +244,80 @@
 
   $ hg id
   db815d6d32e6 tip Y/Z/x  y
+
+test clone
+
+  $ hg bookmarks
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+   * Z                         2:db815d6d32e6
+     x  y                      2:db815d6d32e6
+  $ hg clone . cloned-bookmarks
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R cloned-bookmarks bookmarks
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         2:db815d6d32e6
+     x  y                      2:db815d6d32e6
+
+test clone with pull protocol
+
+  $ hg clone --pull . cloned-bookmarks-pull
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files (+1 heads)
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R cloned-bookmarks-pull bookmarks
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         2:db815d6d32e6
+     x  y                      2:db815d6d32e6
+
+test clone with a specific revision
+
+  $ hg clone -r 925d80 . cloned-bookmarks-rev
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R cloned-bookmarks-rev bookmarks
+     X2                        1:925d80f479bb
+
+create bundle with two heads
+
+  $ hg clone . tobundle
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo x > tobundle/x
+  $ hg -R tobundle add tobundle/x
+  $ hg -R tobundle commit -m'x'
+  $ hg -R tobundle update -r -2
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo y > tobundle/y
+  $ hg -R tobundle branch test
+  marked working directory as branch test
+  $ hg -R tobundle add tobundle/y
+  $ hg -R tobundle commit -m'y'
+  $ hg -R tobundle bundle tobundle.hg
+  searching for changes
+  2 changesets found
+  $ hg unbundle tobundle.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg update
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg bookmarks
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+   * Z                         3:125c9a1d6df6
+     x  y                      2:db815d6d32e6
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-branch-tag-confict.t	Fri Apr 01 12:21:59 2011 +0200
@@ -0,0 +1,62 @@
+Initial setup.
+
+  $ hg init repo
+  $ cd repo
+  $ touch thefile
+  $ hg ci -A -m 'Initial commit.'
+  adding thefile
+
+Create a tag.
+
+  $ hg tag branchortag
+
+Create a branch with the same name as the tag.
+
+  $ hg branch branchortag
+  marked working directory as branch branchortag
+  $ hg ci -m 'Create a branch with the same name as a tag.'
+
+This is what we have:
+
+  $ hg log
+  changeset:   2:02b1af9b58c2
+  branch:      branchortag
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Create a branch with the same name as a tag.
+  
+  changeset:   1:2635c45ca99b
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Added tag branchortag for changeset f57387372b5d
+  
+  changeset:   0:f57387372b5d
+  tag:         branchortag
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Initial commit.
+  
+Update to the tag:
+
+  $ hg up 'tag(branchortag)'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg parents
+  changeset:   0:f57387372b5d
+  tag:         branchortag
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Initial commit.
+  
+Updating to the branch:
+
+  $ hg up 'branch(branchortag)'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg parents
+  changeset:   2:02b1af9b58c2
+  branch:      branchortag
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Create a branch with the same name as a tag.
+  
--- a/tests/test-bundle.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-bundle.t	Fri Apr 01 12:21:59 2011 +0200
@@ -206,7 +206,7 @@
 hg -R bundle://../full.hg verify
 
   $ hg pull bundle://../full.hg
-  pulling from bundle://../full.hg
+  pulling from bundle:../full.hg
   requesting all changes
   adding changesets
   adding manifests
@@ -310,7 +310,7 @@
 Incoming full.hg in partial
 
   $ hg incoming bundle://../full.hg
-  comparing with bundle://../full.hg
+  comparing with bundle:../full.hg
   searching for changes
   changeset:   4:095197eb4973
   parent:      0:f9ee2f85a263
@@ -470,6 +470,22 @@
   
   $ cd ..
 
+test bundle with # in the filename (issue2154):
+
+  $ cp bundle.hg 'test#bundle.hg'
+  $ cd orig
+  $ hg incoming '../test#bundle.hg'
+  comparing with ../test
+  abort: unknown revision 'bundle.hg'!
+  [255]
+
+note that percent encoding is not handled:
+
+  $ hg incoming ../test%23bundle.hg
+  abort: repository ../test%23bundle.hg not found!
+  [255]
+  $ cd ..
+
 test for http://mercurial.selenic.com/bts/issue1144
 
 test that verify bundle does not traceback
@@ -551,26 +567,10 @@
   list of changesets:
   d2ae7f538514cd87c17547b0de4cea71fe1af9fb
   5ece8e77363e2b5269e27c66828b72da29e4341a
-  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-command-template.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-command-template.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1115,7 +1115,7 @@
   $ hg log --template '{date|age}\n' > /dev/null || exit 1
 
   $ hg log -l1 --template '{date|age}\n' 
-  in the future
+  8 years from now
   $ hg log --template '{date|date}\n'
   Wed Jan 01 10:01:00 2020 +0000
   Mon Jan 12 13:46:40 1970 +0000
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-commit-multiple.t	Fri Apr 01 12:21:59 2011 +0200
@@ -0,0 +1,129 @@
+# reproduce issue2264, issue2516
+
+create test repo
+  $ cat <<EOF >> $HGRCPATH
+  > [extensions]
+  > transplant =
+  > graphlog =
+  > EOF
+  $ hg init repo
+  $ cd repo
+  $ template="{rev}  {desc|firstline}  [{branch}]\n"
+
+# we need to start out with two changesets on the default branch
+# in order to avoid the cute little optimization where transplant
+# pulls rather than transplants
+add initial changesets
+  $ echo feature1 > file1
+  $ hg ci -Am"feature 1"
+  adding file1
+  $ echo feature2 >> file2
+  $ hg ci -Am"feature 2"
+  adding file2
+
+# The changes to 'bugfix' are enough to show the bug: in fact, with only
+# those changes, it's a very noisy crash ("RuntimeError: nothing
+# committed after transplant").  But if we modify a second file in the
+# transplanted changesets, the bug is much more subtle: transplant
+# silently drops the second change to 'bugfix' on the floor, and we only
+# see it when we run 'hg status' after transplanting.  Subtle data loss
+# bugs are worse than crashes, so reproduce the subtle case here.
+commit bug fixes on bug fix branch
+  $ hg branch fixes
+  marked working directory as branch fixes
+  $ echo fix1 > bugfix
+  $ echo fix1 >> file1
+  $ hg ci -Am"fix 1"
+  adding bugfix
+  $ echo fix2 > bugfix
+  $ echo fix2 >> file1
+  $ hg ci -Am"fix 2"
+  $ hg glog --template="$template"
+  @  3  fix 2  [fixes]
+  |
+  o  2  fix 1  [fixes]
+  |
+  o  1  feature 2  [default]
+  |
+  o  0  feature 1  [default]
+  
+transplant bug fixes onto release branch
+  $ hg update 0
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg branch release
+  marked working directory as branch release
+  $ hg transplant 2 3
+  applying [0-9a-f]{12} (re)
+  [0-9a-f]{12} transplanted to [0-9a-f]{12} (re)
+  applying [0-9a-f]{12} (re)
+  [0-9a-f]{12} transplanted to [0-9a-f]{12} (re)
+  $ hg glog --template="$template"
+  @  5  fix 2  [release]
+  |
+  o  4  fix 1  [release]
+  |
+  | o  3  fix 2  [fixes]
+  | |
+  | o  2  fix 1  [fixes]
+  | |
+  | o  1  feature 2  [default]
+  |/
+  o  0  feature 1  [default]
+  
+  $ hg status
+  $ hg status --rev 0:4
+  M file1
+  A bugfix
+  $ hg status --rev 4:5
+  M bugfix
+  M file1
+
+now test that we fixed the bug for all scripts/extensions
+  $ cat > $TESTTMP/committwice.py <<__EOF__
+  > from mercurial import ui, hg, match, node
+  > from time import sleep
+  > 
+  > def replacebyte(fn, b):
+  >     f = open(fn, "rb+")
+  >     f.seek(0, 0)
+  >     f.write(b)
+  >     f.close()
+  > 
+  > def printfiles(repo, rev):
+  >     print "revision %s files: %s" % (rev, repo[rev].files())
+  > 
+  > repo = hg.repository(ui.ui(), '.')
+  > assert len(repo) == 6, \
+  >        "initial: len(repo): %d, expected: 6" % len(repo)
+  > 
+  > replacebyte("bugfix", "u")
+  > sleep(2)
+  > try:
+  >     print "PRE: len(repo): %d" % len(repo)
+  >     wlock = repo.wlock()
+  >     lock = repo.lock()
+  >     replacebyte("file1", "x")
+  >     repo.commit(text="x", user="test", date=(0, 0))
+  >     replacebyte("file1", "y")
+  >     repo.commit(text="y", user="test", date=(0, 0))
+  >     print "POST: len(repo): %d" % len(repo)
+  > finally:
+  >     lock.release()
+  >     wlock.release()
+  > printfiles(repo, 6)
+  > printfiles(repo, 7)
+  > __EOF__
+  $ $PYTHON $TESTTMP/committwice.py
+  PRE: len(repo): 6
+  POST: len(repo): 8
+  revision 6 files: ['bugfix', 'file1']
+  revision 7 files: ['file1']
+
+Do a size-preserving modification outside of that process
+  $ echo abcd > bugfix
+  $ hg status
+  M bugfix
+  $ hg log --template "{rev}  {desc}  {files}\n" -r5:
+  5  fix 2  bugfix file1
+  6  x  bugfix file1
+  7  y  file1
--- a/tests/test-convert-baz	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-baz	Fri Apr 01 12:21:59 2011 +0200
@@ -2,10 +2,6 @@
 
 "$TESTDIR/hghave" baz || exit 80
 
-mkdir do_not_use_HOME_baz
-cd do_not_use_HOME_baz
-HOME=`pwd`; export HOME
-cd ..
 baz my-id "mercurial <mercurial@selenic.com>"
 
 echo "[extensions]" >> $HGRCPATH
--- a/tests/test-convert-darcs.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-darcs.t	Fri Apr 01 12:21:59 2011 +0200
@@ -4,7 +4,6 @@
   $ echo "convert=" >> $HGRCPATH
   $ echo 'graphlog =' >> $HGRCPATH
   $ DARCS_EMAIL='test@example.org'; export DARCS_EMAIL
-  $ HOME=`pwd`/do_not_use_HOME_darcs; export HOME
 
 skip if we can't import elementtree
 
--- a/tests/test-convert-git.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-git.t	Fri Apr 01 12:21:59 2011 +0200
@@ -57,9 +57,11 @@
   2 t4.1
   1 t4.2
   0 Merge branch other
+  updating bookmarks
   $ hg up -q -R git-repo-hg
   $ hg -R git-repo-hg tip -v
   changeset:   5:c78094926be2
+  bookmark:    master
   tag:         tip
   parent:      3:f5f5cb45432b
   parent:      4:4e174f80c67c
@@ -217,6 +219,7 @@
   sorting...
   converting...
   0 addbinary
+  updating bookmarks
   $ cd git-repo3-hg
   $ hg up -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -248,8 +251,10 @@
   converting...
   1 addfoo
   0 addfoo2
+  updating bookmarks
   $ hg -R git-repo4-hg log -v
   changeset:   1:d63e967f93da
+  bookmark:    master
   tag:         tip
   user:        nottest <test@example.org>
   date:        Mon Jan 01 00:00:21 2007 +0000
--- a/tests/test-convert-hg-startrev.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-hg-startrev.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,5 +1,5 @@
 
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > graphlog =
   > convert =
--- a/tests/test-convert-mtn.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-mtn.t	Fri Apr 01 12:21:59 2011 +0200
@@ -8,7 +8,6 @@
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert=" >> $HGRCPATH
   $ echo 'graphlog =' >> $HGRCPATH
-  $ HOME=`pwd`/do_not_use_HOME_mtn; export HOME
 
 Windows version of monotone home
 
@@ -208,6 +207,21 @@
   $ mtn ci -m divergentdirmove2
   mtn: beginning commit on branch 'com.selenic.test'
   mtn: committed revision 4a736634505795f17786fffdf2c9cbf5b11df6f6
+
+test large file support (> 32kB)
+
+  $ python -c 'for x in range(10000): print x' > large-file
+  $ $TESTDIR/md5sum.py large-file
+  5d6de8a95c3b6bf9e0ffb808ba5299c1  large-file
+  $ mtn add large-file
+  mtn: adding large-file to workspace manifest
+  $ mtn ci -m largefile
+  mtn: beginning commit on branch 'com.selenic.test'
+  mtn: committed revision f0a20fecd10dc4392d18fe69a03f1f4919d3387b
+
+test suspending (closing a branch)
+
+  $ mtn suspend f0a20fecd10dc4392d18fe69a03f1f4919d3387b 2> /dev/null
   $ cd ..
 
 convert incrementally
@@ -217,27 +231,30 @@
   scanning source...
   sorting...
   converting...
-  11 update2 "with" quotes
-  10 createdir1
-  9 movedir1
-  8 movedir
-  7 emptydir
-  6 dropdirectory
-  5 dirfilemove
-  4 dirfilemove2
-  3 dirdirmove
-  2 dirdirmove2
-  1 divergentdirmove
-  0 divergentdirmove2
+  12 update2 "with" quotes
+  11 createdir1
+  10 movedir1
+  9 movedir
+  8 emptydir
+  7 dropdirectory
+  6 dirfilemove
+  5 dirfilemove2
+  4 dirdirmove
+  3 dirdirmove2
+  2 divergentdirmove
+  1 divergentdirmove2
+  0 largefile
   $ glog()
   > {
   >     hg glog --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
   > }
   $ cd repo.mtn-hg
   $ hg up -C
-  11 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  12 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ glog
-  @  13 "divergentdirmove2" files: dir7-2/c dir7/c dir7/dir9/b dir7/dir9/dir8/a dir8-2/a dir9-2/b
+  @  14 "largefile" files: large-file
+  |
+  o  13 "divergentdirmove2" files: dir7-2/c dir7/c dir7/dir9/b dir7/dir9/dir8/a dir8-2/a dir9-2/b
   |
   o  12 "divergentdirmove" files: dir7/c dir7/dir9/b dir7/dir9/dir8/a
   |
@@ -280,6 +297,7 @@
   dir8-2/a
   dir9-2/b
   e
+  large-file
 
 contents
 
@@ -356,3 +374,15 @@
   dir8-2/a
   dir9-2/b
   e
+
+test large file support (> 32kB)
+
+  $ $TESTDIR/md5sum.py large-file
+  5d6de8a95c3b6bf9e0ffb808ba5299c1  large-file
+
+check branch closing
+
+  $ hg branches -a
+  $ hg branches -c
+  com.selenic.test              14:* (closed) (glob)
+
--- a/tests/test-convert-svn-branches.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-svn-branches.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,7 +1,7 @@
 
   $ "$TESTDIR/hghave" svn svn-bindings || exit 80
 
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > convert = 
   > graphlog =
@@ -32,6 +32,21 @@
   1 move back to old
   0 last change to a
 
+Test template keywords
+
+  $ hg -R A-hg log --template '{rev} {svnuuid}{svnpath}@{svnrev}\n'
+  10 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@10
+  9 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@9
+  8 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old2@8
+  7 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@7
+  6 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@6
+  5 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@6
+  4 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@5
+  3 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@4
+  2 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@3
+  1 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@2
+  0 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@1
+
 Convert again
 
   $ hg convert --branchmap=branchmap --datesort svn-repo A-hg
--- a/tests/test-convert-svn-encoding.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-svn-encoding.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,7 +1,7 @@
 
   $ "$TESTDIR/hghave" svn svn-bindings || exit 80
 
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > convert = 
   > graphlog =
--- a/tests/test-convert-svn-move.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-svn-move.t	Fri Apr 01 12:21:59 2011 +0200
@@ -5,7 +5,7 @@
   > {
   >     tr '\\' /
   > }
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > convert = 
   > graphlog =
--- a/tests/test-convert-svn-sink.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-svn-sink.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,5 +1,5 @@
 
-  $ "$TESTDIR/hghave" svn svn-bindings no-outer-repo || exit 80
+  $ "$TESTDIR/hghave" svn no-outer-repo || exit 80
 
   $ fixpath()
   > {
@@ -22,7 +22,7 @@
   >     )
   > }
 
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > convert = 
   > graphlog =
--- a/tests/test-convert-svn-source.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-svn-source.t	Fri Apr 01 12:21:59 2011 +0200
@@ -5,7 +5,7 @@
   > {
   >     tr '\\' /
   > }
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > convert = 
   > graphlog =
--- a/tests/test-convert-svn-startrev.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-svn-startrev.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,7 +1,7 @@
 
   $ "$TESTDIR/hghave" svn svn-bindings || exit 80
 
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > convert = 
   > graphlog =
--- a/tests/test-convert-svn-tags.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-svn-tags.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,7 +1,7 @@
 
   $ "$TESTDIR/hghave" svn svn-bindings || exit 80
 
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > convert = 
   > graphlog =
--- a/tests/test-convert-tagsbranch-topology.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-tagsbranch-topology.t	Fri Apr 01 12:21:59 2011 +0200
@@ -49,6 +49,7 @@
   converting...
   0 rev1
   updating tags
+  updating bookmarks
 
 Simulate upstream  updates after first conversion
 
@@ -67,6 +68,7 @@
   converting...
   0 rev2
   updating tags
+  updating bookmarks
 
 Print the log
 
--- a/tests/test-convert-tla.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-convert-tla.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,9 +1,5 @@
 
   $ "$TESTDIR/hghave" tla || exit 80
-  $ mkdir do_not_use_HOME_tla
-  $ cd do_not_use_HOME_tla
-  $ HOME=`pwd`; export HOME
-  $ cd ..
   $ tla my-id "mercurial <mercurial@selenic.com>"
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert=" >> $HGRCPATH
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-debugbundle.t	Fri Apr 01 12:21:59 2011 +0200
@@ -0,0 +1,36 @@
+
+Create a test repository:
+
+  $ hg init repo
+  $ cd repo
+  $ touch a ; hg add a ; hg ci -ma
+  $ touch b ; hg add b ; hg ci -mb
+  $ touch c ; hg add c ; hg ci -mc
+  $ hg bundle --base 0 --rev tip bundle.hg
+  2 changesets found
+
+Terse output:
+
+  $ hg debugbundle bundle.hg
+  0e067c57feba1a5694ca4844f05588bb1bf82342
+  991a3460af53952d10ec8a295d3d2cc2e5fa9690
+
+Verbose output:
+
+  $ hg debugbundle --all bundle.hg
+  format: id, p1, p2, cset, len(delta)
+  
+  changelog
+  0e067c57feba1a5694ca4844f05588bb1bf82342 3903775176ed42b1458a6281db4a0ccf4d9f287a 0000000000000000000000000000000000000000 0e067c57feba1a5694ca4844f05588bb1bf82342 80
+  991a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 0000000000000000000000000000000000000000 991a3460af53952d10ec8a295d3d2cc2e5fa9690 80
+  
+  manifest
+  686dbf0aeca417636fa26a9121c681eabbb15a20 8515d4bfda768e04af4c13a69a72e28c7effbea7 0000000000000000000000000000000000000000 0e067c57feba1a5694ca4844f05588bb1bf82342 55
+  ae25a31b30b3490a981e7b96a3238cc69583fda1 686dbf0aeca417636fa26a9121c681eabbb15a20 0000000000000000000000000000000000000000 991a3460af53952d10ec8a295d3d2cc2e5fa9690 55
+  
+  b
+  b80de5d138758541c5f05265ad144ab9fa86d1db 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0e067c57feba1a5694ca4844f05588bb1bf82342 12
+  
+  c
+  b80de5d138758541c5f05265ad144ab9fa86d1db 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 991a3460af53952d10ec8a295d3d2cc2e5fa9690 12
+
--- a/tests/test-debugcomplete.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-debugcomplete.t	Fri Apr 01 12:21:59 2011 +0200
@@ -67,6 +67,7 @@
   $ hg debugcomplete debug
   debugancestor
   debugbuilddag
+  debugbundle
   debugcheckstate
   debugcommands
   debugcomplete
@@ -75,10 +76,12 @@
   debugdata
   debugdate
   debugfsinfo
+  debuggetbundle
   debugignore
   debugindex
   debugindexdot
   debuginstall
+  debugknown
   debugpushkey
   debugrebuildstate
   debugrename
@@ -87,6 +90,7 @@
   debugstate
   debugsub
   debugwalk
+  debugwireargs
 
 Do not show the alias of a debug command if there are other candidates
 (this should hide rawcommit)
@@ -199,7 +203,7 @@
   addremove: similarity, include, exclude, dry-run
   archive: no-decode, prefix, rev, type, subrepos, include, exclude
   backout: merge, parent, tool, rev, include, exclude, message, logfile, date, user
-  bisect: reset, good, bad, skip, command, noupdate
+  bisect: reset, good, bad, skip, extend, command, noupdate
   bookmarks: force, rev, delete, rename
   branch: force, clean
   branches: active, closed
@@ -208,6 +212,7 @@
   copy: after, force, include, exclude, dry-run
   debugancestor: 
   debugbuilddag: mergeable-file, appended-file, overwritten-file, new-file
+  debugbundle: all
   debugcheckstate: 
   debugcommands: 
   debugcomplete: options
@@ -215,18 +220,21 @@
   debugdata: 
   debugdate: extended
   debugfsinfo: 
+  debuggetbundle: head, common, type
   debugignore: 
   debugindex: format
   debugindexdot: 
   debuginstall: 
+  debugknown: 
   debugpushkey: 
   debugrebuildstate: rev
   debugrename: rev
   debugrevspec: 
   debugsetparents: 
-  debugstate: nodates
+  debugstate: nodates, datesort
   debugsub: rev
   debugwalk: include, exclude
+  debugwireargs: three, four, ssh, remotecmd, insecure
   grep: print0, all, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
   heads: rev, topo, active, closed, style, template
   help: 
--- a/tests/test-diff-color.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-diff-color.t	Fri Apr 01 12:21:59 2011 +0200
@@ -81,7 +81,7 @@
   \x1b[0;36;1mold mode 100644\x1b[0m (esc)
   \x1b[0;36;1mnew mode 100755\x1b[0m (esc)
   1 hunks, 1 lines changed
-  examine changes to 'a'? [Ynsfdaq?] 
+  \x1b[0;33mexamine changes to 'a'? [Ynsfdaq?]\x1b[0m  (esc)
   \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
    c
    a
@@ -91,7 +91,7 @@
    a
    a
    c
-  record this change to 'a'? [Ynsfdaq?] 
+  \x1b[0;33mrecord this change to 'a'? [Ynsfdaq?]\x1b[0m  (esc)
   $ echo
   
   $ echo "[extensions]" >> $HGRCPATH
@@ -110,7 +110,7 @@
   \x1b[0;36;1mold mode 100644\x1b[0m (esc)
   \x1b[0;36;1mnew mode 100755\x1b[0m (esc)
   1 hunks, 1 lines changed
-  examine changes to 'a'? [Ynsfdaq?] 
+  \x1b[0;33mexamine changes to 'a'? [Ynsfdaq?]\x1b[0m  (esc)
   \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
    c
    a
@@ -120,6 +120,6 @@
    a
    a
    c
-  record this change to 'a'? [Ynsfdaq?] 
+  \x1b[0;33mrecord this change to 'a'? [Ynsfdaq?]\x1b[0m  (esc)
   $ echo
   
--- a/tests/test-eol-add.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-eol-add.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,6 +1,6 @@
 Test adding .hgeol
 
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [diff]
   > git = 1
   > EOF
--- a/tests/test-eol-clone.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-eol-clone.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,9 +1,6 @@
 Testing cloning with the EOL extension
 
-  $ cat > $HGRCPATH <<EOF
-  > [diff]
-  > git = True
-  > 
+  $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > eol =
   > 
--- a/tests/test-eol-hook.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-eol-hook.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,14 +1,7 @@
 Test the EOL hook
 
-  $ cat > $HGRCPATH <<EOF
-  > [diff]
-  > git = True
-  > EOF
   $ hg init main
   $ cat > main/.hg/hgrc <<EOF
-  > [extensions]
-  > eol =
-  > 
   > [hooks]
   > pretxnchangegroup = python:hgext.eol.hook
   > EOF
@@ -47,10 +40,12 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  error: pretxnchangegroup hook failed: a.txt should not have CRLF line endings
+  error: pretxnchangegroup hook failed: end-of-line check failed:
+    a.txt in a8ee6548cd86 should not have CRLF line endings
   transaction abort!
   rollback completed
-  abort: a.txt should not have CRLF line endings
+  abort: end-of-line check failed:
+    a.txt in a8ee6548cd86 should not have CRLF line endings
   [255]
 
   $ printf "first\nsecond\nthird\n" > a.txt
@@ -73,10 +68,12 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  error: pretxnchangegroup hook failed: crlf.txt should not have LF line endings
+  error: pretxnchangegroup hook failed: end-of-line check failed:
+    crlf.txt in 004ba2132725 should not have LF line endings
   transaction abort!
   rollback completed
-  abort: crlf.txt should not have LF line endings
+  abort: end-of-line check failed:
+    crlf.txt in 004ba2132725 should not have LF line endings
   [255]
 
   $ printf "first\r\nsecond\r\nthird\r\n" > crlf.txt
@@ -88,3 +85,133 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+
+  $ printf "first\r\nsecond" > b.txt
+  $ hg add b.txt
+  $ hg commit -m 'CRLF b.txt'
+  $ hg push ../main
+  pushing to ../main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  error: pretxnchangegroup hook failed: end-of-line check failed:
+    b.txt in fbcf9b1025f5 should not have CRLF line endings
+  transaction abort!
+  rollback completed
+  abort: end-of-line check failed:
+    b.txt in fbcf9b1025f5 should not have CRLF line endings
+  [255]
+
+  $ hg up -r -2
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ printf "some\nother\nfile" > c.txt
+  $ hg add c.txt
+  $ hg commit -m "LF c.txt, b.txt doesn't exist here"
+  created new head
+  $ hg push -f ../main
+  pushing to ../main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  error: pretxnchangegroup hook failed: end-of-line check failed:
+    b.txt in fbcf9b1025f5 should not have CRLF line endings
+  transaction abort!
+  rollback completed
+  abort: end-of-line check failed:
+    b.txt in fbcf9b1025f5 should not have CRLF line endings
+  [255]
+
+Test checkheadshook alias
+
+  $ cat > ../main/.hg/hgrc <<EOF
+  > [hooks]
+  > pretxnchangegroup = python:hgext.eol.checkheadshook
+  > EOF
+  $ hg push -f ../main
+  pushing to ../main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  error: pretxnchangegroup hook failed: end-of-line check failed:
+    b.txt in fbcf9b1025f5 should not have CRLF line endings
+  transaction abort!
+  rollback completed
+  abort: end-of-line check failed:
+    b.txt in fbcf9b1025f5 should not have CRLF line endings
+  [255]
+
+We can fix the head and push again
+
+  $ hg up 6
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ printf "first\nsecond" > b.txt
+  $ hg ci -m "remove CRLF from b.txt"
+  $ hg push -f ../main
+  pushing to ../main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 2 files (+1 heads)
+  $ hg -R ../main rollback
+  repository tip rolled back to revision 5 (undo push)
+  working directory now based on revision -1
+
+Test it still fails with checkallhook
+
+  $ cat > ../main/.hg/hgrc <<EOF
+  > [hooks]
+  > pretxnchangegroup = python:hgext.eol.checkallhook
+  > EOF
+  $ hg push -f ../main
+  pushing to ../main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 2 files (+1 heads)
+  error: pretxnchangegroup hook failed: end-of-line check failed:
+    b.txt in fbcf9b1025f5 should not have CRLF line endings
+  transaction abort!
+  rollback completed
+  abort: end-of-line check failed:
+    b.txt in fbcf9b1025f5 should not have CRLF line endings
+  [255]
+
+But we can push the clean head
+
+  $ hg push -r7 -f ../main
+  pushing to ../main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+Test multiple files/revisions output
+
+  $ printf "another\r\nbad\r\none" > d.txt
+  $ hg add d.txt
+  $ hg ci -m "add d.txt"
+  $ hg push -f ../main
+  pushing to ../main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 2 files (+1 heads)
+  error: pretxnchangegroup hook failed: end-of-line check failed:
+    d.txt in a7040e68714f should not have CRLF line endings
+    b.txt in fbcf9b1025f5 should not have CRLF line endings
+  transaction abort!
+  rollback completed
+  abort: end-of-line check failed:
+    d.txt in a7040e68714f should not have CRLF line endings
+    b.txt in fbcf9b1025f5 should not have CRLF line endings
+  [255]
--- a/tests/test-eol-patch.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-eol-patch.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,6 +1,6 @@
 Test EOL patching
 
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [diff]
   > git = 1
   > EOF
--- a/tests/test-eol-tag.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-eol-tag.t	Fri Apr 01 12:21:59 2011 +0200
@@ -2,10 +2,7 @@
 
 Testing tagging with the EOL extension
 
-  $ cat > $HGRCPATH <<EOF
-  > [diff]
-  > git = True
-  > 
+  $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > eol =
   > 
--- a/tests/test-eol-update.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-eol-update.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,6 +1,6 @@
 Test EOL update
 
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [diff]
   > git = 1
   > EOF
--- a/tests/test-eol.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-eol.t	Fri Apr 01 12:21:59 2011 +0200
@@ -1,6 +1,6 @@
 Test EOL extension
 
-  $ cat > $HGRCPATH <<EOF
+  $ cat >> $HGRCPATH <<EOF
   > [diff]
   > git = True
   > EOF
--- a/tests/test-extdiff.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-extdiff.t	Fri Apr 01 12:21:59 2011 +0200
@@ -57,7 +57,7 @@
 Should diff cloned files directly:
 
   $ hg falabala -r 0:1
-  diffing a.8a5febb7f867/a a.34eed99112ab/a
+  diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
   [1]
 
 Test diff during merge:
@@ -75,7 +75,7 @@
 Should diff cloned file against wc file:
 
   $ hg falabala
-  diffing a.2a13a4d2da36/a $TESTTMP/a/a
+  diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob)
   [1]
 
 
@@ -83,13 +83,13 @@
 
   $ hg ci -d '2 0' -mtest3
   $ hg falabala -c 1
-  diffing a.8a5febb7f867/a a.34eed99112ab/a
+  diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
   [1]
 
 Check diff are made from the first parent:
 
   $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
-  diffing a.2a13a4d2da36/a a.46c0e4daeb72/a
+  diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob)
   diff-like tools yield a non-zero exit code
 
 Test extdiff of multiple files in tmp dir:
@@ -161,10 +161,19 @@
 Test extdiff with --option:
 
   $ hg extdiff -p echo -o this -c 1
-  this a.8a5febb7f867/a a.34eed99112ab/a
+  this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
   [1]
 
   $ hg falabala -o this -c 1
-  diffing this a.8a5febb7f867/a a.34eed99112ab/a
+  diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
   [1]
 
+Test with revsets:
+
+  $ hg extdif -p echo -c "rev(1)"
+  */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
+  [1]
+
+  $ hg extdif -p echo -r "0::1"
+  */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
+  [1]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-getbundle.t	Fri Apr 01 12:21:59 2011 +0200
@@ -0,0 +1,253 @@
+
+= Test the getbundle() protocol function =
+
+Enable graphlog extension:
+
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "graphlog=" >> $HGRCPATH
+
+Create a test repository:
+
+  $ hg init repo
+  $ cd repo
+  $ hg debugbuilddag -n -m '+2 :fork +5 :p1 *fork +6 :p2 /p1 :m1 +3' > /dev/null
+  $ hg glog --template '{node}\n'
+  @  2bba2f40f321484159b395a43f20101d4bb7ead0
+  |
+  o  d9e5488323c782fe684573f3043369d199038b6f
+  |
+  o  6e9a5adf5437e49c746288cf95c5ac34fa8f2f72
+  |
+  o    733bf0910832b26b768a09172f325f995b5476e1
+  |\
+  | o  b5af5d6ea56d73ce24c40bc3cd19a862f74888ac
+  | |
+  | o  6b57ee934bb2996050540f84cdfc8dcad1e7267d
+  | |
+  | o  2c0ec49482e8abe888b7bd090b5827acfc22b3d7
+  | |
+  | o  c1818a9f5977dd4139a48f93f5425c67d44a9368
+  | |
+  | o  6c725a58ad10aea441540bfd06c507f63e8b9cdd
+  | |
+  | o  18063366a155bd56b5618229ae2ac3e91849aa5e
+  | |
+  | o  a21d913c992197a2eb60b298521ec0f045a04799
+  | |
+  o |  b6b2b682253df2ffedc10e9415e4114202b303c5
+  | |
+  o |  2114148793524fd045998f71a45b0aaf139f752b
+  | |
+  o |  74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc
+  | |
+  o |  ea919464b16e003894c48b6cb68df3cd9411b544
+  | |
+  o |  0f82d97ec2778746743fbc996740d409558fda22
+  |/
+  o  6e23b016bc0f0e79c7bd9dd372ccee07055d7fd4
+  |
+  o  10e64d654571f11577745b4d8372e859d9e4df63
+  
+  $ cd ..
+
+
+= Test locally =
+
+Get everything:
+
+  $ hg debuggetbundle repo bundle
+  $ hg debugbundle bundle
+  10e64d654571f11577745b4d8372e859d9e4df63
+  6e23b016bc0f0e79c7bd9dd372ccee07055d7fd4
+  0f82d97ec2778746743fbc996740d409558fda22
+  ea919464b16e003894c48b6cb68df3cd9411b544
+  74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc
+  2114148793524fd045998f71a45b0aaf139f752b
+  b6b2b682253df2ffedc10e9415e4114202b303c5
+  a21d913c992197a2eb60b298521ec0f045a04799
+  18063366a155bd56b5618229ae2ac3e91849aa5e
+  6c725a58ad10aea441540bfd06c507f63e8b9cdd
+  c1818a9f5977dd4139a48f93f5425c67d44a9368
+  2c0ec49482e8abe888b7bd090b5827acfc22b3d7
+  6b57ee934bb2996050540f84cdfc8dcad1e7267d
+  b5af5d6ea56d73ce24c40bc3cd19a862f74888ac
+  733bf0910832b26b768a09172f325f995b5476e1
+  6e9a5adf5437e49c746288cf95c5ac34fa8f2f72
+  d9e5488323c782fe684573f3043369d199038b6f
+  2bba2f40f321484159b395a43f20101d4bb7ead0
+
+Get part of linear run:
+
+  $ hg debuggetbundle repo bundle -H d9e5488323c782fe684573f3043369d199038b6f -C 733bf0910832b26b768a09172f325f995b5476e1
+  $ hg debugbundle bundle
+  6e9a5adf5437e49c746288cf95c5ac34fa8f2f72
+  d9e5488323c782fe684573f3043369d199038b6f
+
+Get missing branch and merge:
+
+  $ hg debuggetbundle repo bundle -H d9e5488323c782fe684573f3043369d199038b6f -C 6b57ee934bb2996050540f84cdfc8dcad1e7267d
+  $ hg debugbundle bundle
+  0f82d97ec2778746743fbc996740d409558fda22
+  ea919464b16e003894c48b6cb68df3cd9411b544
+  74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc
+  2114148793524fd045998f71a45b0aaf139f752b
+  b6b2b682253df2ffedc10e9415e4114202b303c5
+  b5af5d6ea56d73ce24c40bc3cd19a862f74888ac
+  733bf0910832b26b768a09172f325f995b5476e1
+  6e9a5adf5437e49c746288cf95c5ac34fa8f2f72
+  d9e5488323c782fe684573f3043369d199038b6f
+
+Get from only one head:
+
+  $ hg debuggetbundle repo bundle -H 6c725a58ad10aea441540bfd06c507f63e8b9cdd -C 6e23b016bc0f0e79c7bd9dd372ccee07055d7fd4
+  $ hg debugbundle bundle
+  a21d913c992197a2eb60b298521ec0f045a04799
+  18063366a155bd56b5618229ae2ac3e91849aa5e
+  6c725a58ad10aea441540bfd06c507f63e8b9cdd
+
+Get parts of two branches:
+
+  $ hg debuggetbundle repo bundle -H 6b57ee934bb2996050540f84cdfc8dcad1e7267d -C c1818a9f5977dd4139a48f93f5425c67d44a9368 -H 2114148793524fd045998f71a45b0aaf139f752b -C ea919464b16e003894c48b6cb68df3cd9411b544
+  $ hg debugbundle bundle
+  74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc
+  2114148793524fd045998f71a45b0aaf139f752b
+  2c0ec49482e8abe888b7bd090b5827acfc22b3d7
+  6b57ee934bb2996050540f84cdfc8dcad1e7267d
+
+Check that we get all needed file changes:
+
+  $ hg debugbundle bundle --all
+  format: id, p1, p2, cset, len(delta)
+  
+  changelog
+  74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc ea919464b16e003894c48b6cb68df3cd9411b544 0000000000000000000000000000000000000000 74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc 99
+  2114148793524fd045998f71a45b0aaf139f752b 74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc 0000000000000000000000000000000000000000 2114148793524fd045998f71a45b0aaf139f752b 99
+  2c0ec49482e8abe888b7bd090b5827acfc22b3d7 c1818a9f5977dd4139a48f93f5425c67d44a9368 0000000000000000000000000000000000000000 2c0ec49482e8abe888b7bd090b5827acfc22b3d7 102
+  6b57ee934bb2996050540f84cdfc8dcad1e7267d 2c0ec49482e8abe888b7bd090b5827acfc22b3d7 0000000000000000000000000000000000000000 6b57ee934bb2996050540f84cdfc8dcad1e7267d 102
+  
+  manifest
+  dac7984588fc4eea7acbf39693a9c1b06f5b175d 591f732a3faf1fb903815273f3c199a514a61ccb 0000000000000000000000000000000000000000 74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc 113
+  0772616e6b48a76afb6c1458e193cbb3dae2e4ff dac7984588fc4eea7acbf39693a9c1b06f5b175d 0000000000000000000000000000000000000000 2114148793524fd045998f71a45b0aaf139f752b 113
+  eb498cd9af6c44108e43041e951ce829e29f6c80 bff2f4817ced57b386caf7c4e3e36a4bc9af7e93 0000000000000000000000000000000000000000 2c0ec49482e8abe888b7bd090b5827acfc22b3d7 295
+  b15709c071ddd2d93188508ba156196ab4f19620 eb498cd9af6c44108e43041e951ce829e29f6c80 0000000000000000000000000000000000000000 6b57ee934bb2996050540f84cdfc8dcad1e7267d 114
+  
+  mf
+  4f73f97080266ab8e0c0561ca8d0da3eaf65b695 301ca08d026bb72cb4258a9d211bdf7ca0bcd810 0000000000000000000000000000000000000000 74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc 17
+  c7b583de053293870e145f45bd2d61643563fd06 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 0000000000000000000000000000000000000000 2114148793524fd045998f71a45b0aaf139f752b 18
+  266ee3c0302a5a18f1cf96817ac79a51836179e9 edc0f6b8db80d68ae6aff2b19f7e5347ab68fa63 0000000000000000000000000000000000000000 2c0ec49482e8abe888b7bd090b5827acfc22b3d7 149
+  698c6a36220548cd3903ca7dada27c59aa500c52 266ee3c0302a5a18f1cf96817ac79a51836179e9 0000000000000000000000000000000000000000 6b57ee934bb2996050540f84cdfc8dcad1e7267d 19
+  
+  nf11
+  33fbc651630ffa7ccbebfe4eb91320a873e7291c 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 2c0ec49482e8abe888b7bd090b5827acfc22b3d7 16
+  
+  nf12
+  ddce0544363f037e9fb889faca058f52dc01c0a5 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 6b57ee934bb2996050540f84cdfc8dcad1e7267d 16
+  
+  nf4
+  3c1407305701051cbed9f9cb9a68bdfb5997c235 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc 15
+  
+  nf5
+  0dbd89c185f53a1727c54cd1ce256482fa23968e 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 2114148793524fd045998f71a45b0aaf139f752b 15
+
+Get branch and merge:
+
+  $ hg debuggetbundle repo bundle -C 10e64d654571f11577745b4d8372e859d9e4df63 -H 6e9a5adf5437e49c746288cf95c5ac34fa8f2f72
+  $ hg debugbundle bundle
+  6e23b016bc0f0e79c7bd9dd372ccee07055d7fd4
+  0f82d97ec2778746743fbc996740d409558fda22
+  ea919464b16e003894c48b6cb68df3cd9411b544
+  74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc
+  2114148793524fd045998f71a45b0aaf139f752b
+  b6b2b682253df2ffedc10e9415e4114202b303c5
+  a21d913c992197a2eb60b298521ec0f045a04799
+  18063366a155bd56b5618229ae2ac3e91849aa5e
+  6c725a58ad10aea441540bfd06c507f63e8b9cdd
+  c1818a9f5977dd4139a48f93f5425c67d44a9368
+  2c0ec49482e8abe888b7bd090b5827acfc22b3d7
+  6b57ee934bb2996050540f84cdfc8dcad1e7267d
+  b5af5d6ea56d73ce24c40bc3cd19a862f74888ac
+  733bf0910832b26b768a09172f325f995b5476e1
+  6e9a5adf5437e49c746288cf95c5ac34fa8f2f72
+
+
+= Test via HTTP =
+
+Get everything:
+
+  $ hg serve -R repo -p $HGPORT -d --pid-file=hg.pid -E error.log -A access.log
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ hg debuggetbundle http://localhost:$HGPORT/ bundle
+  $ hg debugbundle bundle
+  10e64d654571f11577745b4d8372e859d9e4df63
+  6e23b016bc0f0e79c7bd9dd372ccee07055d7fd4
+  0f82d97ec2778746743fbc996740d409558fda22
+  ea919464b16e003894c48b6cb68df3cd9411b544
+  74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc
+  2114148793524fd045998f71a45b0aaf139f752b
+  b6b2b682253df2ffedc10e9415e4114202b303c5
+  a21d913c992197a2eb60b298521ec0f045a04799
+  18063366a155bd56b5618229ae2ac3e91849aa5e
+  6c725a58ad10aea441540bfd06c507f63e8b9cdd
+  c1818a9f5977dd4139a48f93f5425c67d44a9368
+  2c0ec49482e8abe888b7bd090b5827acfc22b3d7
+  6b57ee934bb2996050540f84cdfc8dcad1e7267d
+  b5af5d6ea56d73ce24c40bc3cd19a862f74888ac
+  733bf0910832b26b768a09172f325f995b5476e1
+  6e9a5adf5437e49c746288cf95c5ac34fa8f2f72
+  d9e5488323c782fe684573f3043369d199038b6f
+  2bba2f40f321484159b395a43f20101d4bb7ead0
+
+Get parts of two branches:
+
+  $ hg debuggetbundle http://localhost:$HGPORT/ bundle -H 6b57ee934bb2996050540f84cdfc8dcad1e7267d -C c1818a9f5977dd4139a48f93f5425c67d44a9368 -H 2114148793524fd045998f71a45b0aaf139f752b -C ea919464b16e003894c48b6cb68df3cd9411b544
+  $ hg debugbundle bundle
+  74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc
+  2114148793524fd045998f71a45b0aaf139f752b
+  2c0ec49482e8abe888b7bd090b5827acfc22b3d7
+  6b57ee934bb2996050540f84cdfc8dcad1e7267d
+
+Check that we get all needed file changes:
+
+  $ hg debugbundle bundle --all
+  format: id, p1, p2, cset, len(delta)
+  
+  changelog
+  74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc ea919464b16e003894c48b6cb68df3cd9411b544 0000000000000000000000000000000000000000 74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc 99
+  2114148793524fd045998f71a45b0aaf139f752b 74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc 0000000000000000000000000000000000000000 2114148793524fd045998f71a45b0aaf139f752b 99
+  2c0ec49482e8abe888b7bd090b5827acfc22b3d7 c1818a9f5977dd4139a48f93f5425c67d44a9368 0000000000000000000000000000000000000000 2c0ec49482e8abe888b7bd090b5827acfc22b3d7 102
+  6b57ee934bb2996050540f84cdfc8dcad1e7267d 2c0ec49482e8abe888b7bd090b5827acfc22b3d7 0000000000000000000000000000000000000000 6b57ee934bb2996050540f84cdfc8dcad1e7267d 102
+  
+  manifest
+  dac7984588fc4eea7acbf39693a9c1b06f5b175d 591f732a3faf1fb903815273f3c199a514a61ccb 0000000000000000000000000000000000000000 74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc 113
+  0772616e6b48a76afb6c1458e193cbb3dae2e4ff dac7984588fc4eea7acbf39693a9c1b06f5b175d 0000000000000000000000000000000000000000 2114148793524fd045998f71a45b0aaf139f752b 113
+  eb498cd9af6c44108e43041e951ce829e29f6c80 bff2f4817ced57b386caf7c4e3e36a4bc9af7e93 0000000000000000000000000000000000000000 2c0ec49482e8abe888b7bd090b5827acfc22b3d7 295
+  b15709c071ddd2d93188508ba156196ab4f19620 eb498cd9af6c44108e43041e951ce829e29f6c80 0000000000000000000000000000000000000000 6b57ee934bb2996050540f84cdfc8dcad1e7267d 114
+  
+  mf
+  4f73f97080266ab8e0c0561ca8d0da3eaf65b695 301ca08d026bb72cb4258a9d211bdf7ca0bcd810 0000000000000000000000000000000000000000 74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc 17
+  c7b583de053293870e145f45bd2d61643563fd06 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 0000000000000000000000000000000000000000 2114148793524fd045998f71a45b0aaf139f752b 18
+  266ee3c0302a5a18f1cf96817ac79a51836179e9 edc0f6b8db80d68ae6aff2b19f7e5347ab68fa63 0000000000000000000000000000000000000000 2c0ec49482e8abe888b7bd090b5827acfc22b3d7 149
+  698c6a36220548cd3903ca7dada27c59aa500c52 266ee3c0302a5a18f1cf96817ac79a51836179e9 0000000000000000000000000000000000000000 6b57ee934bb2996050540f84cdfc8dcad1e7267d 19
+  
+  nf11
+  33fbc651630ffa7ccbebfe4eb91320a873e7291c 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 2c0ec49482e8abe888b7bd090b5827acfc22b3d7 16
+  
+  nf12
+  ddce0544363f037e9fb889faca058f52dc01c0a5 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 6b57ee934bb2996050540f84cdfc8dcad1e7267d 16
+  
+  nf4
+  3c1407305701051cbed9f9cb9a68bdfb5997c235 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 74a573f2ae100f1cedfad9aa7b96f8eaab1dabfc 15
+  
+  nf5
+  0dbd89c185f53a1727c54cd1ce256482fa23968e 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 2114148793524fd045998f71a45b0aaf139f752b 15
+
+Verify we hit the HTTP server:
+
+  $ cat access.log
+  * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=getbundle&common=c1818a9f5977dd4139a48f93f5425c67d44a9368+ea919464b16e003894c48b6cb68df3cd9411b544&heads=6b57ee934bb2996050540f84cdfc8dcad1e7267d+2114148793524fd045998f71a45b0aaf139f752b HTTP/1.1" 200 - (glob)
+
+  $ cat error.log
+
--- a/tests/test-globalopts.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-globalopts.t	Fri Apr 01 12:21:59 2011 +0200
@@ -28,6 +28,7 @@
   pulling from ../b
   searching for changes
   warning: repository is unrelated
+  requesting all changes
   adding changesets
   adding manifests
   adding file changes
--- a/tests/test-help.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-help.t	Fri Apr 01 12:21:59 2011 +0200
@@ -765,6 +765,14 @@
       working directory is checked out, it is equivalent to null. If an
       uncommitted merge is in progress, "." is the revision of the first parent.
 
+Test templating help
+
+  $ hg help templating | egrep '(desc|diffstat|firstline|nonempty)  '
+      desc        String. The text of the changeset description.
+      diffstat    String. Statistics of changes with the following format:
+      firstline   Any text. Returns the first line of text.
+      nonempty    Any text. Returns '(none)' if the string is empty.
+
 Test help hooks
 
   $ cat > helphook1.py <<EOF
--- a/tests/test-hgrc.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-hgrc.t	Fri Apr 01 12:21:59 2011 +0200
@@ -20,12 +20,12 @@
   $ cd foobar
   $ cat .hg/hgrc
   [paths]
-  default = */foo%bar (glob)
+  default = $TESTTMP/foo%bar
   $ hg paths
-  default = */foo%bar (glob)
+  default = $TESTTMP/foo%bar
   $ hg showconfig
-  bundle.mainreporoot=*/foobar (glob)
-  paths.default=*/foo%bar (glob)
+  bundle.mainreporoot=$TESTTMP/foobar
+  paths.default=$TESTTMP/foo%bar
   $ cd ..
 
 issue1829: wrong indentation
--- a/tests/test-hgweb-commands.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-hgweb-commands.t	Fri Apr 01 12:21:59 2011 +0200
@@ -905,7 +905,7 @@
   $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'; echo
   200 Script output follows
   
-  lookup changegroupsubset branchmap pushkey unbundle=HG10GZ,HG10BZ,HG10UN
+  lookup changegroupsubset branchmap pushkey known getbundle unbundle=HG10GZ,HG10BZ,HG10UN
 
 heads
 
--- a/tests/test-http-branchmap.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-http-branchmap.t	Fri Apr 01 12:21:59 2011 +0200
@@ -30,7 +30,7 @@
   $ echo bar >> b/foo
   $ hg -R b ci -m bar
   $ hg --encoding utf-8 -R b push
-  pushing to http://localhost:$HGPORT1
+  pushing to http://localhost:$HGPORT1/
   searching for changes
   remote: adding changesets
   remote: adding manifests
--- a/tests/test-http-clone-r.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-http-clone-r.t	Fri Apr 01 12:21:59 2011 +0200
@@ -214,7 +214,7 @@
   adding changesets
   adding manifests
   adding file changes
-  added 1 changesets with 0 changes to 1 files (+1 heads)
+  added 1 changesets with 0 changes to 0 files (+1 heads)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -238,7 +238,7 @@
   adding changesets
   adding manifests
   adding file changes
-  added 2 changesets with 0 changes to 1 files (+1 heads)
+  added 2 changesets with 0 changes to 0 files (+1 heads)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
--- a/tests/test-http-proxy.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-http-proxy.t	Fri Apr 01 12:21:59 2011 +0200
@@ -98,27 +98,23 @@
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat proxy.log
-  * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
 
--- a/tests/test-https.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-https.t	Fri Apr 01 12:21:59 2011 +0200
@@ -118,9 +118,9 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
-  warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
   $ hg verify -R copy-pull
   checking changesets
   checking manifests
--- a/tests/test-identify.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-identify.t	Fri Apr 01 12:21:59 2011 +0200
@@ -65,26 +65,43 @@
 remote with rev number?
 
   $ hg id -n http://localhost:$HGPORT1/
-  abort: can't query remote revision number, branch, tags, or bookmarks
+  abort: can't query remote revision number, branch, or tags
   [255]
 
 remote with tags?
 
   $ hg id -t http://localhost:$HGPORT1/
-  abort: can't query remote revision number, branch, tags, or bookmarks
+  abort: can't query remote revision number, branch, or tags
   [255]
 
 remote with branch?
 
   $ hg id -b http://localhost:$HGPORT1/
-  abort: can't query remote revision number, branch, tags, or bookmarks
+  abort: can't query remote revision number, branch, or tags
   [255]
 
-remote with bookmarks?
+test bookmark support
 
-  $ hg id -B http://localhost:$HGPORT1/
-  abort: can't query remote revision number, branch, tags, or bookmarks
-  [255]
+  $ hg bookmark Y
+  $ hg bookmark Z
+  $ hg bookmarks
+     Y                         0:cb9a9f314b8b
+   * Z                         0:cb9a9f314b8b
+  $ hg id
+  cb9a9f314b8b+ tip Y/Z
+  $ hg id --bookmarks
+  Y Z
+
+test remote identify with bookmarks
+
+  $ hg id http://localhost:$HGPORT1/
+  cb9a9f314b8b Y/Z
+  $ hg id --bookmarks http://localhost:$HGPORT1/
+  Y Z
+  $ hg id -r . http://localhost:$HGPORT1/
+  cb9a9f314b8b Y/Z
+  $ hg id --bookmarks -r . http://localhost:$HGPORT1/
+  Y Z
 
 Make sure we do not obscure unknown requires file entries (issue2649)
 
--- a/tests/test-init.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-init.t	Fri Apr 01 12:21:59 2011 +0200
@@ -199,3 +199,17 @@
   store
   fncache
   dotencode
+
+clone bookmarks
+
+  $ hg -R local bookmark test
+  $ hg -R local bookmarks
+   * test                      0:08b9e9f63b32
+  $ hg clone -e "python ./dummyssh" local ssh://user@dummy/remote-bookmarks
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  $ hg -R remote-bookmarks bookmarks
+     test                      0:08b9e9f63b32
--- a/tests/test-install.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-install.t	Fri Apr 01 12:21:59 2011 +0200
@@ -3,7 +3,6 @@
   Checking encoding (ascii)...
   Checking installed modules (*/mercurial)... (glob)
   Checking templates...
-  Checking patch...
   Checking commit editor...
   Checking username...
   No problems detected
@@ -13,7 +12,6 @@
   Checking encoding (ascii)...
   Checking installed modules (*/mercurial)... (glob)
   Checking templates...
-  Checking patch...
   Checking commit editor...
   Checking username...
    no username supplied (see "hg help config")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-issue1877.t	Fri Apr 01 12:21:59 2011 +0200
@@ -0,0 +1,45 @@
+http://mercurial.selenic.com/bts/issue1877
+
+  $ hg init a
+  $ cd a
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m 'a'
+  $ echo b > a
+  $ hg ci -m'b'
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg book main
+  $ hg book
+   * main                      0:cb9a9f314b8b
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m'c'
+  created new head
+  $ hg book
+   * main                      2:d36c0562f908
+  $ hg heads
+  changeset:   2:d36c0562f908
+  bookmark:    main
+  tag:         tip
+  parent:      0:cb9a9f314b8b
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     c
+  
+  changeset:   1:1e6c11564562
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     b
+  
+  $ hg up 1e6c11564562
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg merge main
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg book
+     main                      2:d36c0562f908
+  $ hg ci -m'merge'
+  $ hg book
+     main                      2:d36c0562f908
+
--- a/tests/test-keyword.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-keyword.t	Fri Apr 01 12:21:59 2011 +0200
@@ -209,7 +209,7 @@
   To: Test
   
   changeset a2392c293916 in $TESTTMP/Test
-  details: *cmd=changeset;node=a2392c293916 (glob)
+  details: $TESTTMP/Test?cmd=changeset;node=a2392c293916
   description:
   	addsym
   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-known.t	Fri Apr 01 12:21:59 2011 +0200
@@ -0,0 +1,37 @@
+
+= Test the known() protocol function =
+
+Create a test repository:
+
+  $ hg init repo
+  $ cd repo
+  $ touch a ; hg add a ; hg ci -ma
+  $ touch b ; hg add b ; hg ci -mb
+  $ touch c ; hg add c ; hg ci -mc
+  $ hg log --template '{node}\n'
+  991a3460af53952d10ec8a295d3d2cc2e5fa9690
+  0e067c57feba1a5694ca4844f05588bb1bf82342
+  3903775176ed42b1458a6281db4a0ccf4d9f287a
+  $ cd ..
+
+Test locally:
+
+  $ hg debugknown repo 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 3903775176ed42b1458a6281db4a0ccf4d9f287a
+  111
+  $ hg debugknown repo 000a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 0003775176ed42b1458a6281db4a0ccf4d9f287a
+  010
+  $ hg debugknown repo
+  
+
+Test via HTTP:
+
+  $ hg serve -R repo -p $HGPORT -d --pid-file=hg.pid -E error.log -A access.log
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ hg debugknown http://localhost:$HGPORT/ 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 3903775176ed42b1458a6281db4a0ccf4d9f287a
+  111
+  $ hg debugknown http://localhost:$HGPORT/ 000a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 0003775176ed42b1458a6281db4a0ccf4d9f287a
+  010
+  $ hg debugknown http://localhost:$HGPORT/
+  
+  $ cat error.log
+
--- a/tests/test-log.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-log.t	Fri Apr 01 12:21:59 2011 +0200
@@ -512,7 +512,11 @@
   date:        Thu Jan 01 00:00:01 1970 +0000
   summary:     r1
   
+log -d " " (whitespaces only)
 
+  $ hg log -d " "
+  abort: dates cannot consist entirely of whitespace
+  [255]
 
 log -d -1
 
--- a/tests/test-mq-strip.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-mq-strip.t	Fri Apr 01 12:21:59 2011 +0200
@@ -410,7 +410,7 @@
   abort: local changes found
   [255]
   $ hg strip tip --keep
-  saved backup bundle to * (glob)
+  saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
   $ hg log --graph
   @  changeset:   0:9ab35a2d17cb
      tag:         tip
--- a/tests/test-paths.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-paths.t	Fri Apr 01 12:21:59 2011 +0200
@@ -25,3 +25,23 @@
   $ SOMETHING=/foo hg paths
   dupe = $TESTTMP/b
   expand = /foo/bar
+  $ cd ..
+
+'file:' disables [paths] entries for clone destination
+
+  $ cat >> $HGRCPATH <<EOF
+  > [paths]
+  > gpath1 = http://hg.example.com
+  > EOF
+
+  $ hg clone a gpath1
+  abort: cannot create new http repository
+  [255]
+
+  $ hg clone a file:gpath1
+  updating to branch default
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd gpath1
+  $ hg -q id
+  000000000000
+
--- a/tests/test-pull-branch.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-pull-branch.t	Fri Apr 01 12:21:59 2011 +0200
@@ -134,3 +134,77 @@
   not updating, since new heads added
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
+Make changes on new branch on tt
+
+  $ hg branch branchC
+  marked working directory as branch branchC
+  $ echo b1 > bar 
+  $ hg ci -Am "commit on branchC on tt"
+  adding bar
+
+Make changes on default branch on t
+
+  $ cd ../t
+  $ hg up -C default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo a1 > bar 
+  $ hg ci -Am "commit on default on t"
+  adding bar
+
+Pull branchC from tt
+
+  $ hg pull ../tt
+  pulling from ../tt
+  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)
+
+Make changes on default and branchC on tt
+
+  $ cd ../tt
+  $ hg pull ../t
+  pulling from ../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 up -C default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo a1 > bar1
+  $ hg ci -Am "commit on default on tt"
+  adding bar1
+  $ hg up branchC
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo a1 > bar2
+  $ hg ci -Am "commit on branchC on tt"
+  adding bar2
+
+Make changes on default and branchC on t
+
+  $ cd ../t
+  $ hg up default
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo a1 > bar3
+  $ hg ci -Am "commit on default on t"
+  adding bar3
+  $ hg up branchC
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo a1 > bar4
+  $ hg ci -Am "commit on branchC on tt"
+  adding bar4
+
+Pull from tt
+
+  $ hg pull ../tt
+  pulling from ../tt
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+2 heads)
+  (run 'hg heads .' to see heads, 'hg merge' to merge)
--- a/tests/test-pull.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-pull.t	Fri Apr 01 12:21:59 2011 +0200
@@ -68,7 +68,11 @@
 Test 'file:' uri handling:
 
   $ hg pull -q file://../test-doesnt-exist
-  abort: repository /test-doesnt-exist not found!
+  abort: file:// URLs can only refer to localhost
+  [255]
+
+  $ hg pull -q file://../test
+  abort: file:// URLs can only refer to localhost
   [255]
 
   $ hg pull -q file:../test
@@ -78,4 +82,8 @@
 
   $ URL=`python -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
   $ hg pull -q "$URL"
+  abort: file:// URLs can only refer to localhost
+  [255]
 
+  $ URL=`python -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
+  $ hg pull -q "$URL"
--- a/tests/test-rebase-collapse.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-rebase-collapse.t	Fri Apr 01 12:21:59 2011 +0200
@@ -137,6 +137,40 @@
 
   $ cd ..
 
+Rebasing G onto H with custom message:
+
+  $ hg clone -q -u . a a3
+  $ cd a3
+
+  $ hg rebase --base 6 -m 'custom message'
+  abort: message can only be specified with collapse
+  [255]
+
+  $ hg rebase --base 6 --collapse -m 'custom message'
+  saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
+
+  $ hg tglog
+  @  6: 'custom message'
+  |
+  o  5: 'H'
+  |
+  o  4: 'F'
+  |
+  | o  3: 'D'
+  | |
+  | o  2: 'C'
+  | |
+  | o  1: 'B'
+  |/
+  o  0: 'A'
+  
+  $ hg manifest
+  A
+  E
+  F
+  H
+
+  $ cd ..
 
 Create repo b:
 
--- a/tests/test-rebase-mq.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-rebase-mq.t	Fri Apr 01 12:21:59 2011 +0200
@@ -235,3 +235,73 @@
   -mq1
   +mq2
 
+
+Rebase with guards
+
+  $ hg init foo
+  $ cd foo
+  $ echo a > a
+  $ hg ci -Am a
+  adding a
+
+Create mq repo with guarded patches foo and bar:
+
+  $ hg qinit
+  $ hg qnew foo
+  $ hg qguard foo +baz
+  $ echo foo > foo
+  $ hg qref
+  $ hg qpop
+  popping foo
+  patch queue now empty
+
+  $ hg qnew bar
+  $ hg qguard bar +baz
+  $ echo bar > bar
+  $ hg qref
+
+  $ hg qguard -l
+  bar: +baz
+  foo: +baz
+
+  $ hg tglog
+  @  1:* '[mq]: bar' tags: bar qbase qtip tip (glob)
+  |
+  o  0:* 'a' tags: qparent (glob)
+  
+Create new head to rebase bar onto:
+
+  $ hg up -C 0
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo b > b
+  $ hg add b
+  $ hg ci -m b
+  created new head
+  $ hg up -C 1
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo a >> a
+  $ hg qref
+
+  $ hg tglog
+  @  2:* '[mq]: bar' tags: bar qbase qtip tip (glob)
+  |
+  | o  1:* 'b' tags: (glob)
+  |/
+  o  0:* 'a' tags: qparent (glob)
+  
+
+Rebase bar:
+
+  $ hg -q rebase -d 1
+
+  $ hg qguard -l
+  foo: +baz
+  bar: +baz
+
+  $ hg tglog
+  @  2:* '[mq]: bar' tags: bar qbase qtip tip (glob)
+  |
+  o  1:* 'b' tags: qparent (glob)
+  |
+  o  0:* 'a' tags: (glob)
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-named-branches.t	Fri Apr 01 12:21:59 2011 +0200
@@ -0,0 +1,171 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > graphlog=
+  > rebase=
+  > 
+  > [alias]
+  > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
+  > EOF
+
+
+  $ hg init a
+  $ cd a
+
+  $ echo A > A
+  $ hg ci -Am A
+  adding A
+
+  $ echo B > B
+  $ hg ci -Am B
+  adding B
+
+  $ hg up -q -C 0
+
+  $ echo C > C
+  $ hg ci -Am C
+  adding C
+  created new head
+
+  $ hg up -q -C 0
+
+  $ echo D > D
+  $ hg ci -Am D
+  adding D
+  created new head
+
+  $ hg merge -r 2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg ci -m E
+
+  $ hg up -q -C 3
+
+  $ echo F > F
+  $ hg ci -Am F
+  adding F
+  created new head
+
+  $ cd ..
+
+
+Rebasing descendant onto ancestor across different named branches
+
+  $ hg clone -q -u . a a1
+
+  $ cd a1
+
+  $ hg branch dev
+  marked working directory as branch dev
+
+  $ echo x > x
+
+  $ hg add x
+
+  $ hg ci -m 'extra named branch'
+
+  $ hg tglog
+  @  6: 'extra named branch' dev
+  |
+  o  5: 'F'
+  |
+  | o  4: 'E'
+  |/|
+  o |  3: 'D'
+  | |
+  | o  2: 'C'
+  |/
+  | o  1: 'B'
+  |/
+  o  0: 'A'
+  
+  $ hg rebase -s 6 -d 5
+  saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
+
+  $ hg tglog
+  @  6: 'extra named branch'
+  |
+  o  5: 'F'
+  |
+  | o  4: 'E'
+  |/|
+  o |  3: 'D'
+  | |
+  | o  2: 'C'
+  |/
+  | o  1: 'B'
+  |/
+  o  0: 'A'
+  
+  $ cd ..
+ 
+Rebasing descendant onto ancestor across the same named branches
+
+  $ hg clone -q -u . a a2
+
+  $ cd a2
+
+  $ echo x > x
+
+  $ hg add x
+
+  $ hg ci -m 'G'
+
+  $ hg tglog
+  @  6: 'G'
+  |
+  o  5: 'F'
+  |
+  | o  4: 'E'
+  |/|
+  o |  3: 'D'
+  | |
+  | o  2: 'C'
+  |/
+  | o  1: 'B'
+  |/
+  o  0: 'A'
+  
+  $ hg rebase -s 6 -d 5
+  abort: source is descendant of destination
+  [255]
+
+  $ cd ..
+ 
+Rebasing ancestor onto descendant across different named branches
+
+  $ hg clone -q -u . a a3
+
+  $ cd a3
+
+  $ hg branch dev
+  marked working directory as branch dev
+
+  $ echo x > x
+
+  $ hg add x
+
+  $ hg ci -m 'extra named branch'
+
+  $ hg tglog
+  @  6: 'extra named branch' dev
+  |
+  o  5: 'F'
+  |
+  | o  4: 'E'
+  |/|
+  o |  3: 'D'
+  | |
+  | o  2: 'C'
+  |/
+  | o  1: 'B'
+  |/
+  o  0: 'A'
+  
+  $ hg rebase -s 5 -d 6
+  abort: source is ancestor of destination
+  [255]
+
+  $ cd ..
+ 
+
--- a/tests/test-rebase-rename.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-rebase-rename.t	Fri Apr 01 12:21:59 2011 +0200
@@ -119,3 +119,52 @@
   copy from a
   copy to a-copied
   
+  $ cd ..
+
+
+Test rebase across repeating renames:
+
+  $ hg init repo
+
+  $ cd repo
+
+  $ echo testing > file1.txt
+  $ hg add file1.txt
+  $ hg ci -m "Adding file1"
+
+  $ hg rename file1.txt file2.txt
+  $ hg ci -m "Rename file1 to file2"
+
+  $ echo Unrelated change > unrelated.txt
+  $ hg add unrelated.txt
+  $ hg ci -m "Unrelated change"
+
+  $ hg rename file2.txt file1.txt
+  $ hg ci -m "Rename file2 back to file1"
+
+  $ hg update -r -2
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+  $ echo Another unrelated change >> unrelated.txt
+  $ hg ci -m "Another unrelated change"
+  created new head
+
+  $ hg tglog
+  @  4: 'Another unrelated change'
+  |
+  | o  3: 'Rename file2 back to file1'
+  |/
+  o  2: 'Unrelated change'
+  |
+  o  1: 'Rename file1 to file2'
+  |
+  o  0: 'Adding file1'
+  
+
+  $ hg rebase -s 4 -d 3
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-backup.hg (glob)
+
+  $ hg diff --stat -c .
+   unrelated.txt |  1 +
+   1 files changed, 1 insertions(+), 0 deletions(-)
+
--- a/tests/test-relink.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-relink.t	Fri Apr 01 12:21:59 2011 +0200
@@ -20,23 +20,29 @@
 
   $ hg init repo
   $ cd repo
-  $ echo '[ui]' > .hg/hgrc
-  $ echo 'username= A. Foo <a.foo@bar.com>' >> .hg/hgrc
   $ echo a > a
   $ echo b > b
   $ hg ci -Am addfile
   adding a
   adding b
-  $ echo a >> a
-  $ echo a >> b
+  $ cat $TESTDIR/binfile.bin >> a
+  $ cat $TESTDIR/binfile.bin >> b
   $ hg ci -Am changefiles
 
+make another commit to create files larger than 1 KB to test
+formatting of final byte count
+
+  $ cat $TESTDIR/binfile.bin >> a
+  $ cat $TESTDIR/binfile.bin >> b
+  $ hg ci -m anotherchange
+
 don't sit forever trying to double-lock the source repo
 
   $ hg relink .
   relinking $TESTTMP/repo/.hg/store to $TESTTMP/repo/.hg/store
   there is nothing to relink
 
+
 Test files are read in binary mode
 
   $ python -c "file('.hg/store/data/dummy.i', 'wb').write('a\r\nb\n')"
@@ -53,8 +59,6 @@
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd clone
-  $ echo '[ui]' >> .hg/hgrc
-  $ echo 'username= A. Baz <a.baz@bar.com>' >> .hg/hgrc
   $ hg pull -q
   $ echo b >> b
   $ hg ci -m changeb
@@ -81,7 +85,7 @@
   pruned down to 2 probably relinkable files
   relinking: data/a.i 1/2 files (50.00%)
   not linkable: data/dummy.i
-  relinked 1 files (136 bytes reclaimed)
+  relinked 1 files (1.37 KB reclaimed)
   $ cd ..
 
 
--- a/tests/test-rename-merge1.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-rename-merge1.t	Fri Apr 01 12:21:59 2011 +0200
@@ -131,27 +131,27 @@
   $ hg init repo2089
   $ cd repo2089
 
-  $ echo 0 > A
-  $ hg -q ci -Am 0
+  $ echo c0 > f1
+  $ hg ci -Aqm0
 
-  $ hg -q up -C null
-  $ echo 1 > A
-  $ hg -q ci -Am 1
+  $ hg up null -q
+  $ echo c1 > f1
+  $ hg ci -Aqm1
 
-  $ hg -q up -C 0
+  $ hg up 0 -q
   $ hg merge 1 -q --tool internal:local
-  $ echo 2 > A
-  $ hg -q ci -m 2
+  $ echo c2 > f1
+  $ hg ci -qm2
 
-  $ hg -q up -C 1
-  $ hg mv A a
-  $ hg -q ci -Am 3
+  $ hg up 1 -q
+  $ hg mv f1 f2
+  $ hg ci -Aqm3
 
-  $ hg -q up -C 2
+  $ hg up 2 -q
   $ hg merge 3
-  merging A and a to a
+  merging f1 and f2 to f2
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
-  $ cat a
-  2
+  $ cat f2
+  c2
--- a/tests/test-revset-outgoing.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-revset-outgoing.t	Fri Apr 01 12:21:59 2011 +0200
@@ -39,7 +39,7 @@
   $ cd b
   $ cat .hg/hgrc
   [paths]
-  default = */a#stable (glob)
+  default = $TESTTMP/a#stable
 
   $ echo red >> a
   $ hg ci -qm3
@@ -60,7 +60,7 @@
   
 
   $ hg tout
-  comparing with */a (glob)
+  comparing with $TESTTMP/a
   searching for changes
   2:1d4099801a4e: '3' stable
 
@@ -79,11 +79,11 @@
 
   $ cat .hg/hgrc
   [paths]
-  default = */a#stable (glob)
+  default = $TESTTMP/a#stable
   green = ../a#default
 
   $ hg tout green
-  comparing with */a (glob)
+  comparing with $TESTTMP/a
   searching for changes
   3:f0461977a3db: '4' 
 
--- a/tests/test-revset.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-revset.t	Fri Apr 01 12:21:59 2011 +0200
@@ -356,3 +356,10 @@
   9
   $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(é)))'
   4
+
+issue2654: report a parse error if the revset was not completely parsed
+
+  $ log '1 OR 2'
+  hg: parse error at 2: invalid token
+  [255]
+
--- a/tests/test-schemes.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-schemes.t	Fri Apr 01 12:21:59 2011 +0200
@@ -25,10 +25,11 @@
 
   $ hg incoming --debug parts://localhost
   using http://localhost:$HGPORT/
-  sending between command
-  comparing with parts://localhost
+  sending capabilities command
+  comparing with parts://localhost/
   sending heads command
   searching for changes
+  sending known command
   no changes found
   [1]
 
--- a/tests/test-serve	Fri Apr 01 12:18:37 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-#!/bin/sh
-
-hgserve()
-{
-    hg serve -a localhost -d --pid-file=hg.pid -E errors.log -v $@ \
-        | sed -e "s/:$HGPORT1\\([^0-9]\\)/:HGPORT1\1/g" \
-              -e "s/:$HGPORT2\\([^0-9]\\)/:HGPORT2\1/g" \
-              -e 's/http:\/\/[^/]*\//http:\/\/localhost\//'
-    cat hg.pid >> "$DAEMON_PIDS"
-    echo % errors
-    cat errors.log
-    sleep 1
-    if [ "$KILLQUIETLY" = "Y" ]; then
-        kill `cat hg.pid` 2>/dev/null
-    else
-        kill `cat hg.pid`
-    fi
-    sleep 1
-}
-
-hg init test
-cd test
-
-echo '[web]' > .hg/hgrc
-echo 'accesslog = access.log' >> .hg/hgrc
-echo "port = $HGPORT1" >> .hg/hgrc
-
-echo % Without -v
-hg serve -a localhost -p $HGPORT -d --pid-file=hg.pid -E errors.log
-cat hg.pid >> "$DAEMON_PIDS"
-if [ -f access.log ]; then
-    echo 'access log created - .hg/hgrc respected'
-fi
-echo % errors
-cat errors.log
-
-echo % With -v
-hgserve
-
-echo % With -v and -p HGPORT2
-hgserve -p "$HGPORT2"
-
-echo '% With -v and -p daytime (should fail because low port)'
-KILLQUIETLY=Y
-hgserve -p daytime
-KILLQUIETLY=N
-
-echo % With --prefix foo
-hgserve --prefix foo
-
-echo % With --prefix /foo
-hgserve --prefix /foo
-
-echo % With --prefix foo/
-hgserve --prefix foo/
-
-echo % With --prefix /foo/
-hgserve --prefix /foo/
--- a/tests/test-serve.out	Fri Apr 01 12:18:37 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-% Without -v
-access log created - .hg/hgrc respected
-% errors
-% With -v
-listening at http://localhost/ (bound to 127.0.0.1:HGPORT1)
-% errors
-% With -v and -p HGPORT2
-listening at http://localhost/ (bound to 127.0.0.1:HGPORT2)
-% errors
-% With -v and -p daytime (should fail because low port)
-abort: cannot start server at 'localhost:13': Permission denied
-abort: child process failed to start
-% errors
-% With --prefix foo
-listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1)
-% errors
-% With --prefix /foo
-listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1)
-% errors
-% With --prefix foo/
-listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1)
-% errors
-% With --prefix /foo/
-listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1)
-% errors
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-serve.t	Fri Apr 01 12:21:59 2011 +0200
@@ -0,0 +1,82 @@
+
+  $ hgserve()
+  > {
+  >    hg serve -a localhost -d --pid-file=hg.pid -E errors.log -v $@ \
+  >        | sed -e "s/:$HGPORT1\\([^0-9]\\)/:HGPORT1\1/g" \
+  >              -e "s/:$HGPORT2\\([^0-9]\\)/:HGPORT2\1/g" \
+  >              -e 's/http:\/\/[^/]*\//http:\/\/localhost\//'
+  >    cat hg.pid >> "$DAEMON_PIDS"
+  >    echo % errors
+  >    cat errors.log
+  >    sleep 1
+  >    if [ "$KILLQUIETLY" = "Y" ]; then
+  >        kill `cat hg.pid` 2>/dev/null
+  >    else
+  >        kill `cat hg.pid`
+  >    fi
+  >    sleep 1
+  > }
+
+  $ hg init test
+  $ cd test
+  $ echo '[web]' > .hg/hgrc
+  $ echo 'accesslog = access.log' >> .hg/hgrc
+  $ echo "port = $HGPORT1" >> .hg/hgrc
+
+Without -v
+
+  $ hg serve -a localhost -p $HGPORT -d --pid-file=hg.pid -E errors.log
+  $ cat hg.pid >> "$DAEMON_PIDS"
+  $ if [ -f access.log ]; then
+  $     echo 'access log created - .hg/hgrc respected'
+  access log created - .hg/hgrc respected
+  $ fi
+
+errors
+
+  $ cat errors.log
+
+With -v
+
+  $ hgserve
+  listening at http://localhost/ (bound to 127.0.0.1:HGPORT1)
+  % errors
+
+With -v and -p HGPORT2
+
+  $ hgserve -p "$HGPORT2"
+  listening at http://localhost/ (bound to 127.0.0.1:HGPORT2)
+  % errors
+
+With -v and -p daytime (should fail because low port)
+
+  $ KILLQUIETLY=Y
+  $ hgserve -p daytime
+  abort: cannot start server at 'localhost:13': Permission denied
+  abort: child process failed to start
+  % errors
+  $ KILLQUIETLY=N
+
+With --prefix foo
+
+  $ hgserve --prefix foo
+  listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1)
+  % errors
+
+With --prefix /foo
+
+  $ hgserve --prefix /foo
+  listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1)
+  % errors
+
+With --prefix foo/
+
+  $ hgserve --prefix foo/
+  listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1)
+  % errors
+
+With --prefix /foo/
+
+  $ hgserve --prefix /foo/
+  listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1)
+  % errors
--- a/tests/test-ssh-clone-r.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-ssh-clone-r.t	Fri Apr 01 12:21:59 2011 +0200
@@ -232,7 +232,7 @@
   adding changesets
   adding manifests
   adding file changes
-  added 1 changesets with 0 changes to 1 files (+1 heads)
+  added 1 changesets with 0 changes to 0 files (+1 heads)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -256,7 +256,7 @@
   adding changesets
   adding manifests
   adding file changes
-  added 2 changesets with 0 changes to 1 files (+1 heads)
+  added 2 changesets with 0 changes to 0 files (+1 heads)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
--- a/tests/test-ssh.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-ssh.t	Fri Apr 01 12:21:59 2011 +0200
@@ -263,10 +263,28 @@
   summary:     z
   
 
+clone bookmarks
+
+  $ hg -R ../remote bookmark test
+  $ hg -R ../remote bookmarks
+   * test                      2:6c0482d977a3
+  $ hg clone -e "python ../dummyssh" ssh://user@dummy/remote local-bookmarks
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 5 changes to 4 files (+1 heads)
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R local-bookmarks bookmarks
+     test                      2:6c0482d977a3
+
 passwords in ssh urls are not supported
+(we use a glob here because different Python versions give different
+results here)
 
   $ hg push ssh://user:erroneouspwd@dummy/remote
-  pushing to ssh://user:***@dummy/remote
+  pushing to ssh://user:*@dummy/remote (glob)
   abort: password in URL not supported!
   [255]
 
@@ -290,3 +308,4 @@
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
--- a/tests/test-subrepo-git.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-subrepo-git.t	Fri Apr 01 12:21:59 2011 +0200
@@ -73,7 +73,7 @@
   $ cd t
   $ hg clone . ../tc
   updating to branch default
-  cloning subrepo s
+  cloning subrepo s from $TESTTMP/gitroot
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../tc
   $ hg debugsub
@@ -96,7 +96,7 @@
   $ cd ../t
   $ hg clone . ../ta
   updating to branch default
-  cloning subrepo s
+  cloning subrepo s from $TESTTMP/gitroot
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ cd ../ta
@@ -115,7 +115,7 @@
   $ cd ../t
   $ hg clone . ../tb
   updating to branch default
-  cloning subrepo s
+  cloning subrepo s from $TESTTMP/gitroot
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ cd ../tb/s
@@ -155,7 +155,7 @@
   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
+  pulling subrepo s from $TESTTMP/gitroot
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ cat s/f
@@ -199,7 +199,7 @@
   $ cd ../t
   $ hg clone . ../td
   updating to branch default
-  cloning subrepo s
+  cloning subrepo s from $TESTTMP/gitroot
   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
@@ -232,7 +232,7 @@
   $ cd ../tb
   $ hg pull -q
   $ hg update 2>/dev/null
-  pulling subrepo s
+  pulling subrepo s from $TESTTMP/gitroot
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg debugsub
   path s
@@ -262,7 +262,7 @@
   $ cd ../tc
   $ hg pull -q
   $ hg archive --subrepos -r 5 ../archive 2>/dev/null
-  pulling subrepo s
+  pulling subrepo s from $TESTTMP/gitroot
   $ cd ../archive
   $ cat s/f
   f
@@ -282,7 +282,7 @@
 
   $ hg clone ../t inner
   updating to branch default
-  cloning subrepo s
+  cloning subrepo s from $TESTTMP/gitroot
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo inner = inner > .hgsub
   $ hg add .hgsub
@@ -311,7 +311,7 @@
   $ mkdir d
   $ hg clone t d/t
   updating to branch default
-  cloning subrepo s
+  cloning subrepo s from $TESTTMP/gitroot
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Don't crash if the subrepo is missing
@@ -329,7 +329,7 @@
   abort: subrepo s is missing
   [255]
   $ hg update -C
-  cloning subrepo s
+  cloning subrepo s from $TESTTMP/gitroot
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg sum | grep commit
   commit: (clean)
--- a/tests/test-subrepo-paths.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-subrepo-paths.t	Fri Apr 01 12:21:59 2011 +0200
@@ -21,6 +21,15 @@
    source   C:\libs\foo-lib\
    revision 
 
+test cumulative remapping, the $HGRCPATH file is loaded first
+
+  $ echo '[subpaths]' >> $HGRCPATH
+  $ echo 'libfoo = libbar' >> $HGRCPATH
+  $ hg debugsub
+  path sub
+   source   C:\libs\bar-lib\
+   revision 
+
 test bad subpaths pattern
 
   $ cat > .hg/hgrc <<EOF
--- a/tests/test-transplant.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-transplant.t	Fri Apr 01 12:21:59 2011 +0200
@@ -68,6 +68,18 @@
   $ hg help revsets | grep transplanted
       "transplanted(set)"
 
+test tranplanted keyword
+
+  $ hg log --template '{rev} {transplanted}\n'
+  7 a53251cdf717679d1907b289f991534be05c997a
+  6 722f4667af767100cb15b6a79324bf8abbfe1ef4
+  5 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21
+  4 
+  3 
+  2 
+  1 
+  0 
+
   $ hg clone ../t ../prune
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -330,6 +342,40 @@
   [255]
   $ cd ..
 
+test environment passed to filter
+
+  $ hg init filter-environment
+  $ cd filter-environment
+  $ cat <<'EOF' >test-filter-environment
+  > #!/bin/sh
+  > echo "Transplant by $HGUSER" >> $1
+  > echo "Transplant from rev $HGREVISION" >> $1
+  > EOF
+  $ chmod +x test-filter-environment
+  $ hg transplant -s ../t --filter ./test-filter-environment 0
+  filtering * (glob)
+  applying 17ab29e464c6
+  17ab29e464c6 transplanted to 5190e68026a0
+
+  $ hg log --template '{rev} {parents} {desc}\n'
+  0  r1
+  Transplant by test
+  Transplant from rev 17ab29e464c6ca53e329470efe2a9918ac617a6f
+  $ cd ..
+
+test transplant with filter handles invalid changelog
+
+  $ hg init filter-invalid-log
+  $ cd filter-invalid-log
+  $ cat <<'EOF' >test-filter-invalid-log
+  > #!/bin/sh
+  > echo "" > $1
+  > EOF
+  $ chmod +x test-filter-invalid-log
+  $ hg transplant -s ../t --filter ./test-filter-invalid-log 0
+  filtering * (glob)
+  abort: filter corrupted changeset (no user or date)
+  [255]
 
 test with a win32ext like setup (differing EOLs)
 
--- a/tests/test-unrelated-pull.t	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-unrelated-pull.t	Fri Apr 01 12:21:59 2011 +0200
@@ -23,6 +23,7 @@
   pulling from ../a
   searching for changes
   warning: repository is unrelated
+  requesting all changes
   adding changesets
   adding manifests
   adding file changes
--- a/tests/test-url.py	Fri Apr 01 12:18:37 2011 +0200
+++ b/tests/test-url.py	Fri Apr 01 12:21:59 2011 +0200
@@ -49,6 +49,151 @@
 check(_verifycert(None, 'example.com'),
       'no certificate received')
 
+import doctest
+
+def test_url():
+    """
+    >>> from mercurial.url import url
+
+    This tests for edge cases in url.URL's parsing algorithm. Most of
+    these aren't useful for documentation purposes, so they aren't
+    part of the class's doc tests.
+
+    Query strings and fragments:
+
+    >>> url('http://host/a?b#c')
+    <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
+    >>> url('http://host/a?')
+    <url scheme: 'http', host: 'host', path: 'a'>
+    >>> url('http://host/a#b#c')
+    <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
+    >>> url('http://host/a#b?c')
+    <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
+    >>> url('http://host/?a#b')
+    <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
+    >>> url('http://host/?a#b', parsequery=False)
+    <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
+    >>> url('http://host/?a#b', parsefragment=False)
+    <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
+    >>> url('http://host/?a#b', parsequery=False, parsefragment=False)
+    <url scheme: 'http', host: 'host', path: '?a#b'>
+
+    IPv6 addresses:
+
+    >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
+    <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
+         query: 'objectClass?one'>
+    >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
+    <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
+         port: '80', path: 'c=GB', query: 'objectClass?one'>
+
+    Missing scheme, host, etc.:
+
+    >>> url('://192.0.2.16:80/')
+    <url path: '://192.0.2.16:80/'>
+    >>> url('http://mercurial.selenic.com')
+    <url scheme: 'http', host: 'mercurial.selenic.com'>
+    >>> url('/foo')
+    <url path: '/foo'>
+    >>> url('bundle:/foo')
+    <url scheme: 'bundle', path: '/foo'>
+    >>> url('a?b#c')
+    <url path: 'a?b', fragment: 'c'>
+    >>> url('http://x.com?arg=/foo')
+    <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
+    >>> url('http://joe:xxx@/foo')
+    <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
+
+    Just a scheme and a path:
+
+    >>> url('mailto:John.Doe@example.com')
+    <url scheme: 'mailto', path: 'John.Doe@example.com'>
+    >>> url('a:b:c:d')
+    <url path: 'a:b:c:d'>
+    >>> url('aa:bb:cc:dd')
+    <url scheme: 'aa', path: 'bb:cc:dd'>
+
+    SSH examples:
+
+    >>> url('ssh://joe@host//home/joe')
+    <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
+    >>> url('ssh://joe:xxx@host/src')
+    <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
+    >>> url('ssh://joe:xxx@host')
+    <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
+    >>> url('ssh://joe@host')
+    <url scheme: 'ssh', user: 'joe', host: 'host'>
+    >>> url('ssh://host')
+    <url scheme: 'ssh', host: 'host'>
+    >>> url('ssh://')
+    <url scheme: 'ssh'>
+    >>> url('ssh:')
+    <url scheme: 'ssh'>
+
+    Non-numeric port:
+
+    >>> url('http://example.com:dd')
+    <url scheme: 'http', host: 'example.com', port: 'dd'>
+    >>> url('ssh://joe:xxx@host:ssh/foo')
+    <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
+         path: 'foo'>
+
+    Bad authentication credentials:
+
+    >>> url('http://joe@joeville:123@4:@host/a?b#c')
+    <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
+         host: 'host', path: 'a', query: 'b', fragment: 'c'>
+    >>> url('http://!*#?/@!*#?/:@host/a?b#c')
+    <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
+    >>> url('http://!*#?@!*#?:@host/a?b#c')
+    <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
+    >>> url('http://!*@:!*@@host/a?b#c')
+    <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
+         path: 'a', query: 'b', fragment: 'c'>
+
+    File paths:
+
+    >>> url('a/b/c/d.g.f')
+    <url path: 'a/b/c/d.g.f'>
+    >>> url('/x///z/y/')
+    <url path: '/x///z/y/'>
+
+    Non-localhost file URL:
+
+    >>> u = url('file://mercurial.selenic.com/foo')
+    Traceback (most recent call last):
+      File "<stdin>", line 1, in ?
+    Abort: file:// URLs can only refer to localhost
+
+    Empty URL:
+
+    >>> u = url('')
+    >>> u
+    <url path: ''>
+    >>> str(u)
+    ''
+
+    Empty path with query string:
+
+    >>> str(url('http://foo/?bar'))
+    'http://foo/?bar'
+
+    Invalid path:
+
+    >>> u = url('http://foo/bar')
+    >>> u.path = 'bar'
+    >>> str(u)
+    'http://foo/bar'
+
+    >>> u = url('file:///foo/bar/baz')
+    >>> u
+    <url scheme: 'file', path: '/foo/bar/baz'>
+    >>> str(u)
+    'file:/foo/bar/baz'
+    """
+
+doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
+
 # Unicode (IDN) certname isn't supported
 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
       'IDN in certificate not supported')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-wireproto.t	Fri Apr 01 12:21:59 2011 +0200
@@ -0,0 +1,60 @@
+
+Test wire protocol argument passing
+
+Setup repo:
+
+  $ hg init repo
+
+Local:
+
+  $ hg debugwireargs repo eins zwei --three drei --four vier
+  eins zwei drei vier
+  $ hg debugwireargs repo eins zwei --four vier
+  eins zwei None vier
+  $ hg debugwireargs repo eins zwei
+  eins zwei None None
+
+HTTP:
+
+  $ hg serve -R repo -p $HGPORT -d --pid-file=hg1.pid -E error.log -A access.log
+  $ cat hg1.pid >> $DAEMON_PIDS
+
+  $ hg debugwireargs http://localhost:$HGPORT/ un deux trois quatre
+  un deux trois quatre
+  $ hg debugwireargs http://localhost:$HGPORT/ eins zwei --four vier
+  eins zwei None vier
+  $ hg debugwireargs http://localhost:$HGPORT/ eins zwei
+  eins zwei None None
+  $ cat access.log
+  * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
+  * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
+
+SSH (try to exercise the ssh functionality with a dummy script):
+
+  $ cat <<EOF > dummyssh
+  > import sys
+  > import os
+  > os.chdir(os.path.dirname(sys.argv[0]))
+  > if sys.argv[1] != "user@dummy":
+  >     sys.exit(-1)
+  > if not os.path.exists("dummyssh"):
+  >     sys.exit(-1)
+  > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
+  > r = os.system(sys.argv[2])
+  > sys.exit(bool(r))
+  > EOF
+
+  $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo uno due tre quattro
+  uno due tre quattro
+  $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei --four vier
+  eins zwei None vier
+  $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei
+  eins zwei None None
+