--- a/.hgignore Sun Mar 27 13:34:20 2011 +0200
+++ b/.hgignore Tue Apr 05 16:11:40 2011 -0500
@@ -7,6 +7,7 @@
*.mergebackup
*.o
*.so
+*.dll
*.pyd
*.pyc
*.pyo
--- a/contrib/check-code.py Sun Mar 27 13:34:20 2011 +0200
+++ b/contrib/check-code.py Tue Apr 05 16:11:40 2011 -0500
@@ -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/python-hook-examples.py Sun Mar 27 13:34:20 2011 +0200
+++ b/contrib/python-hook-examples.py Tue Apr 05 16:11:40 2011 -0500
@@ -13,7 +13,7 @@
if kwargs.get('parent2'):
return
node = kwargs['node']
- first = repo[node].parents()[0].node()
+ first = repo[node].p1().node()
if 'url' in kwargs:
last = repo['tip'].node()
else:
--- a/contrib/shrink-revlog.py Sun Mar 27 13:34:20 2011 +0200
+++ b/contrib/shrink-revlog.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/contrib/zsh_completion Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/doc/hgrc.5.txt Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/bugzilla.py Tue Apr 05 16:11:40 2011 -0500
@@ -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,97 +9,161 @@
'''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.
+
+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.
+
+2. Writing directly to the Bugzilla database. Only Bugzilla installations
+ using MySQL are supported. Requires Python MySQLdb.
-The hook updates the Bugzilla database directly. Only Bugzilla
-installations using MySQL are supported.
+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.
+
+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.
-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.
+Access via XMLRPC/email 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 email associated with the Bugzilla username used to log into
+Bugzilla is used instead as the source of the comment.
+
+Configuration items common to all access modes:
+
+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.
+
+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.
+
+bugzilla.style
+ The style file to use when formatting comments.
+
+bugzilla.template
+ Template to use when formatting comments. Overrides style if
+ specified. In addition to the usual Mercurial keywords, the
+ extension specifies:
-The extension is configured through three different configuration
-sections. These keys are recognized in the [bugzilla] section:
+ :``{bug}``: The Bugzilla bug ID.
+ :``{root}``: The full pathname of the Mercurial repository.
+ :``{webroot}``: Stripped pathname of the Mercurial repository.
+ :``{hgweb}``: Base URL for browsing Mercurial repositories.
+
+ Default ``changeset {node|short} in repo {root} refers to bug
+ {bug}.\\ndetails:\\n\\t{desc|tabindent}``
+
+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.
-host
- Hostname of the MySQL server holding the Bugzilla database.
+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::
+
+ 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``.
-db
- Name of the Bugzilla database in MySQL. Default 'bugs'.
+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.
+
+XMLRPC+email access mode uses the XMLRPC access mode configuration items,
+and also:
-user
- Username to use to access MySQL server. Default 'bugs'.
+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:
-password
+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``.
+
+bugzilla.password
Password to use to access MySQL server.
-timeout
+bugzilla.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.
-
-bzuser
+bugzilla.bzuser
Fallback Bugzilla user name to record comments with, if changeset
committer cannot be found as a Bugzilla user.
-bzdir
+bugzilla.bzdir
Bugzilla install directory. Used by default notify. Default
- '/var/www/html/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".
-
-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
- The style file to use when formatting comments.
-
-template
- Template to use when formatting comments. Overrides style if
- specified. In addition to the usual Mercurial keywords, the
- extension specifies::
+ ``/var/www/html/bugzilla``.
- {bug} The Bugzilla bug ID.
- {root} The full pathname of the Mercurial repository.
- {webroot} Stripped pathname of the Mercurial repository.
- {hgweb} Base URL for browsing Mercurial repositories.
-
- 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.
-
-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.
-
-The [usermap] section is used to specify mappings of Mercurial
-committer ID to Bugzilla user ID. See also [bugzilla].usermap.
-"committer"="Bugzilla user"
-
-Finally, the [web] section supports one entry:
-
-baseurl
- Base URL for browsing Mercurial repositories. Reference from
- templates as {hgweb}.
+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 +174,58 @@
# 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`` with password ``plugh``. It is used with a
+collection of Mercurial repositories in ``/var/local/hg/repos/``,
+with a web interface at ``http://my-project.org/hg``. ::
+
+ [bugzilla]
+ bzurl=http://my-project.org/bugzilla
+ user=bugmail@my-project.org
+ password=plugh
+ version=xmlrpc
+ template=Changeset {node|short} in {root|basename}.
+ {hgweb}/{webroot}/rev/{node|short}\\n
+ {desc}\\n
+ strip=5
+
+ [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/``,
+with a web interface at ``http://my-project.org/hg``. Bug comments
+are sent to the Bugzilla email address
+``bugzilla@my-project.org``. ::
+
+ [bugzilla]
+ bzurl=http://my-project.org/bugzilla
+ user=bugmail@my-project.org
+ password=plugh
+ version=xmlrpc
+ bzemail=bugzilla@my-project.org
+ template=Changeset {node|short} in {root|basename}.
+ {hgweb}/{webroot}/rev/{node|short}\\n
+ {desc}\\n
+ strip=5
+
+ [web]
+ baseurl=http://my-project.org/hg
+
+ [usermap]
+ user@emaildomain.com=user.name@bugzilladomain.com
+
+MySQL example configuration. This has 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``. It is used
+with a collection of Mercurial repositories in ``/var/local/hg/repos/``,
+with a web interface at ``http://my-project.org/hg``. ::
[bugzilla]
host=localhost
@@ -127,46 +239,98 @@
strip=5
[web]
- baseurl=http://dev.domain.com/hg
+ baseurl=http://my-project.org/hg
[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
+ http://my-project.org/hg/repository-name/rev/3b16791d6642
Changeset commit comment. Bug 1234.
'''
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 +341,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 +354,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 +415,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 +449,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 +471,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 +673,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 +698,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 +740,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 +751,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/color.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/convert/__init__.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/convert/common.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/convert/convcmd.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/convert/git.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/convert/hg.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/convert/monotone.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/convert/subversion.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/eol.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/extdiff.py Tue Apr 05 16:11:40 2011 -0500
@@ -121,12 +121,12 @@
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)
if not revs:
- node1b = repo.dirstate.parents()[1]
+ node1b = repo.dirstate.p2()
else:
node1b = nullid
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/graphlog.py Tue Apr 05 16:11:40 2011 -0500
@@ -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/hgcia.py Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/hgcia.py Tue Apr 05 16:11:40 2011 -0500
@@ -75,7 +75,7 @@
def fileelems(self):
n = self.ctx.node()
- f = self.cia.repo.status(self.ctx.parents()[0].node(), n)
+ f = self.cia.repo.status(self.ctx.p1().node(), n)
url = self.url or ''
elems = []
for path in f[0]:
--- a/hgext/keyword.py Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/keyword.py Tue Apr 05 16:11:40 2011 -0500
@@ -60,11 +60,11 @@
control run :hg:`kwdemo`. See :hg:`help templates` for a list of
available templates and filters.
-Three additional date template filters are provided::
+Three additional date template filters are provided:
- utcdate "2006/09/18 15:13:13"
- svnutcdate "2006-09-18 15:13:13Z"
- svnisodate "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
+:``utcdate``: "2006/09/18 15:13:13"
+:``svnutcdate``: "2006-09-18 15:13:13Z"
+:``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
The default template mappings (view with :hg:`kwdemo -d`) can be
replaced with customized keywords and templates. Again, run
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/mq.py Tue Apr 05 16:11:40 2011 -0500
@@ -899,7 +899,7 @@
else:
p.write("# HG changeset patch\n")
p.write("# Parent "
- + hex(repo[None].parents()[0].node()) + "\n")
+ + hex(repo[None].p1().node()) + "\n")
if user:
p.write("# User " + user + "\n")
if date:
@@ -1054,7 +1054,7 @@
heads += ls
if not heads:
heads = [nullid]
- if repo.dirstate.parents()[0] not in heads and not exact:
+ if repo.dirstate.p1() not in heads and not exact:
self.ui.status(_("(working directory not at a head)\n"))
if not self.series:
@@ -1148,7 +1148,7 @@
ret = self.apply(repo, s, list, all_files=all_files)
except:
self.ui.warn(_('cleaning up working directory...'))
- node = repo.dirstate.parents()[0]
+ node = repo.dirstate.p1()
hg.revert(repo, node, None)
# only remove unknown files that we know we touched or
# created while patching
@@ -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/notify.py Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/notify.py Tue Apr 05 16:11:40 2011 -0500
@@ -249,7 +249,7 @@
def diff(self, ctx, ref=None):
maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
- prev = ctx.parents()[0].node()
+ prev = ctx.p1().node()
ref = ref and ref.node() or ctx.node()
chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
difflines = ''.join(chunks).splitlines()
--- a/hgext/rebase.py Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/rebase.py Tue Apr 05 16:11:40 2011 -0500
@@ -15,7 +15,7 @@
'''
from mercurial import hg, util, repair, merge, cmdutil, commands
-from mercurial import extensions, ancestor, copies, patch
+from mercurial import extensions, copies, patch
from mercurial.commands import templateopts
from mercurial.node import nullrev
from mercurial.lock import release
@@ -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'))
@@ -109,6 +114,8 @@
if srcf or basef or destf:
raise util.Abort(
_('abort and continue do not allow specifying revisions'))
+ if opts.get('tool', False):
+ ui.warn(_('tool option will be ignored\n'))
(originalwd, target, state, skipped, collapsef, keepf,
keepbranchesf, external) = restorestatus(repo)
@@ -138,8 +145,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()
@@ -163,10 +169,14 @@
if len(repo.parents()) == 2:
repo.ui.debug('resuming interrupted rebase\n')
else:
- stats = rebasenode(repo, rev, p1, p2, state)
- if stats and stats[3] > 0:
- raise util.Abort(_('unresolved conflicts (see hg '
- 'resolve, then hg rebase --continue)'))
+ try:
+ ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
+ stats = rebasenode(repo, rev, p1, state)
+ if stats and stats[3] > 0:
+ raise util.Abort(_('unresolved conflicts (see hg '
+ 'resolve, then hg rebase --continue)'))
+ finally:
+ ui.setconfig('ui', 'forcemerge', '')
updatedirstate(repo, rev, target, p2)
if not collapsef:
newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
@@ -190,11 +200,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)
@@ -221,25 +234,6 @@
finally:
release(lock, wlock)
-def rebasemerge(repo, rev, first=False):
- 'return the correct ancestor'
- oldancestor = ancestor.ancestor
-
- def newancestor(a, b, pfunc):
- if b == rev:
- return repo[rev].parents()[0].rev()
- return oldancestor(a, b, pfunc)
-
- if not first:
- ancestor.ancestor = newancestor
- else:
- repo.ui.debug("first revision, do not change ancestor\n")
- try:
- stats = merge.update(repo, rev, True, True, False)
- return stats
- finally:
- ancestor.ancestor = oldancestor
-
def checkexternal(repo, state, targetancestors):
"""Check whether one or more external revisions need to be taken in
consideration. In the latter case, abort.
@@ -293,7 +287,7 @@
repo.dirstate.invalidate()
raise
-def rebasenode(repo, rev, p1, p2, state):
+def rebasenode(repo, rev, p1, state):
'Rebase a single revision'
# Merge phase
# Update to target and merge it with local
@@ -304,9 +298,10 @@
repo.ui.debug(" already in target\n")
repo.dirstate.write()
repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
- first = repo[rev].rev() == repo[min(state)].rev()
- stats = rebasemerge(repo, rev, first)
- return stats
+ base = None
+ if repo[rev].rev() != repo[min(state)].rev():
+ base = repo[rev].p1().node()
+ return merge.update(repo, rev, True, True, False, base)
def defineparents(repo, rev, target, state, targetancestors):
'Return the new parent relationship of the revision that will be rebased'
@@ -354,6 +349,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 +368,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 +481,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,10 +572,15 @@
('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 '
'branch')),
+ ('t', 'tool', '', _('specify merge tool')),
('c', 'continue', False, _('continue an interrupted rebase')),
('a', 'abort', False, _('abort an interrupted rebase'))] +
templateopts,
--- a/hgext/record.py Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/record.py Tue Apr 05 16:11:40 2011 -0500
@@ -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:
@@ -467,7 +469,7 @@
# 3a. apply filtered patch to clean repo (clean)
if backups:
- hg.revert(repo, repo.dirstate.parents()[0],
+ hg.revert(repo, repo.dirstate.p1(),
lambda key: key in backups)
# 3b. (apply)
--- a/hgext/relink.py Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/relink.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/schemes.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/hgext/transplant.py Tue Apr 05 16:11:40 2011 -0500
@@ -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
@@ -236,7 +237,7 @@
seriespath = os.path.join(self.path, 'series')
if os.path.exists(seriespath):
os.unlink(seriespath)
- p1 = repo.dirstate.parents()[0]
+ p1 = repo.dirstate.p1()
p2 = node
self.log(user, date, message, p1, p2, merge=merge)
self.ui.write(str(inst) + '\n')
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/ancestor.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/archival.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/bdiff.c Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/bookmarks.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/bundlerepo.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/changegroup.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/cmdutil.py Tue Apr 05 16:11:40 2011 -0500
@@ -72,7 +72,7 @@
return p
def bail_if_changed(repo):
- if repo.dirstate.parents()[1] != nullid:
+ if repo.dirstate.p2() != nullid:
raise util.Abort(_('outstanding uncommitted merge'))
modified, added, removed, deleted = repo.status()[:4]
if modified or added or removed or deleted:
@@ -122,12 +122,12 @@
def revpair(repo, revs):
if not revs:
- return repo.dirstate.parents()[0], None
+ return repo.dirstate.p1(), None
l = revrange(repo, revs)
if len(l) == 0:
- return repo.dirstate.parents()[0], None
+ return repo.dirstate.p1(), None
if len(l) == 1:
return repo.lookup(l[0]), None
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/commands.py Tue Apr 05 16:11:40 2011 -0500
@@ -5,7 +5,7 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-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:
@@ -570,7 +596,7 @@
"""
if opts.get('clean'):
- label = repo[None].parents()[0].branch()
+ label = repo[None].p1().branch()
repo.dirstate.setbranch(label)
ui.status(_('reset working directory to branch %s\n') % label)
elif label:
@@ -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,8 +1677,8 @@
msg = _('cannot specify --rev and --change at the same time')
raise util.Abort(msg)
elif change:
- node2 = repo.lookup(change)
- node1 = repo[node2].parents()[0].node()
+ node2 = cmdutil.revsingle(repo, change, None).node()
+ node1 = repo[node2].p1().node()
else:
node1, node2 = cmdutil.revpair(repo, revs)
@@ -1823,7 +1905,7 @@
def prep(ctx, fns):
rev = ctx.rev()
- pctx = ctx.parents()[0]
+ pctx = ctx.p1()
parent = pctx.rev()
matches.setdefault(rev, {})
matches.setdefault(parent, {})
@@ -1858,7 +1940,7 @@
for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
rev = ctx.rev()
- parent = ctx.parents()[0].rev()
+ parent = ctx.p1().rev()
for fn in sorted(revfiles.get(rev, [])):
states = matches[rev][fn]
copy = copies.get(rev, {}).get(fn)
@@ -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))
@@ -2711,7 +2809,7 @@
``--tool`` can be used to specify the merge tool used for file
merges. It overrides the HGMERGE environment variable and your
- configuration files.
+ configuration files. See :hg:`help merge-tools` for options.
If no revision is specified, the working directory's parent is a
head revision, and the current branch contains exactly one other
@@ -2742,7 +2840,7 @@
'(run \'hg heads .\' to see heads)')
% (branch, len(bheads)))
- parent = repo.dirstate.parents()[0]
+ parent = repo.dirstate.p1()
if len(bheads) == 1:
if len(repo.heads()) > 1:
raise util.Abort(_(
@@ -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
@@ -3265,8 +3370,9 @@
directory, the reverted files will thus appear modified
afterwards.
- If a file has been deleted, it is restored. If the executable mode
- of a file was changed, it is reset.
+ If a file has been deleted, it is restored. Files scheduled for
+ addition are just unscheduled and left as they are. If the
+ executable mode of a file was changed, it is reset.
If names are given, all files matching the names are reverted.
If no arguments are given, no files are reverted.
@@ -3645,7 +3751,7 @@
raise util.Abort(msg)
elif change:
node2 = repo.lookup(change)
- node1 = repo[node2].parents()[0].node()
+ node1 = repo[node2].p1().node()
else:
node1, node2 = cmdutil.revpair(repo, revs)
@@ -3955,19 +4061,16 @@
ui.write("%s\n" % t)
continue
- try:
- hn = hexfunc(n)
- r = "%5d:%s" % (repo.changelog.rev(n), hn)
- except error.LookupError:
- r = " ?:%s" % hn
- else:
- spaces = " " * (30 - encoding.colwidth(t))
- if ui.verbose:
- if repo.tagtype(t) == 'local':
- tagtype = " local"
- else:
- tagtype = ""
- ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
+ hn = hexfunc(n)
+ r = "%5d:%s" % (repo.changelog.rev(n), hn)
+ spaces = " " * (30 - encoding.colwidth(t))
+
+ if ui.verbose:
+ if repo.tagtype(t) == 'local':
+ tagtype = " local"
+ else:
+ tagtype = ""
+ ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
def tip(ui, repo, **opts):
"""show the tip revision
@@ -3998,15 +4101,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 +4157,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 +4373,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 +4464,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 +4489,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 +4520,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 +4529,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 +4868,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/config.py Tue Apr 05 16:11:40 2011 -0500
@@ -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/context.py Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/context.py Tue Apr 05 16:11:40 2011 -0500
@@ -402,6 +402,15 @@
return [filectx(self._repo, p, fileid=n, filelog=l)
for p, n, l in pl if n != nullid]
+ def p1(self):
+ return self.parents()[0]
+
+ def p2(self):
+ p = self.parents()
+ if len(p) == 2:
+ return p[1]
+ return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
+
def children(self):
# hard for renames
c = self._filelog.children(self._filenode)
--- a/mercurial/copies.py Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/copies.py Tue Apr 05 16:11:40 2011 -0500
@@ -93,7 +93,7 @@
return {}, {}
# avoid silly behavior for parent -> working dir
- if c2.node() is None and c1.node() == repo.dirstate.parents()[0]:
+ if c2.node() is None and c1.node() == repo.dirstate.p1():
return repo.dirstate.copies(), {}
limit = _findlimit(repo, c1.rev(), c2.rev())
--- a/mercurial/dirstate.py Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/dirstate.py Tue Apr 05 16:11:40 2011 -0500
@@ -49,6 +49,7 @@
self._rootdir = os.path.join(root, '')
self._dirty = False
self._dirtypl = False
+ self._lastnormaltime = None
self._ui = ui
@propertycache
@@ -202,6 +203,12 @@
def parents(self):
return [self._validate(p) for p in self._pl]
+ def p1(self):
+ return self._validate(self._pl[0])
+
+ def p2(self):
+ return self._validate(self._pl[1])
+
def branch(self):
return encoding.tolocal(self._branch)
@@ -236,6 +243,7 @@
"_ignore"):
if a in self.__dict__:
delattr(self, a)
+ self._lastnormaltime = None
self._dirty = False
def copy(self, source, dest):
@@ -281,9 +289,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 +411,7 @@
delattr(self, "_dirs")
self._copymap = {}
self._pl = [nullid, nullid]
+ self._lastnormaltime = None
self._dirty = True
def rebuild(self, parent, files):
@@ -444,6 +459,7 @@
write(f)
st.write(cs.getvalue())
st.rename()
+ self._lastnormaltime = None
self._dirty = self._dirtypl = False
def _dirignore(self, f):
@@ -680,6 +696,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 +704,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/discovery.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/help.py Tue Apr 05 16:11:40 2011 -0500
@@ -86,7 +86,7 @@
return loader
-helptable = [
+helptable = sorted([
(["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
(["dates"], _("Date Formats"), loaddoc('dates')),
(["patterns"], _("File Name Patterns"), loaddoc('patterns')),
@@ -106,7 +106,7 @@
(["subrepo", "subrepos"], _("Subrepositories"), loaddoc('subrepos')),
(["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
(["glossary"], _("Glossary"), loaddoc('glossary')),
-]
+])
# Map topics to lists of callable taking the current topic help and
# returning the updated version
@@ -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/dates.txt Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/help/dates.txt Tue Apr 05 16:11:40 2011 -0500
@@ -23,14 +23,14 @@
- ``1165432709 0`` (Wed Dec 6 13:18:29 2006 UTC)
-This is the internal representation format for dates. unixtime is the
-number of seconds since the epoch (1970-01-01 00:00 UTC). offset is
-the offset of the local timezone, in seconds west of UTC (negative if
-the timezone is east of UTC).
+This is the internal representation format for dates. The first number
+is the number of seconds since the epoch (1970-01-01 00:00 UTC). The
+second is the offset of the local timezone, in seconds west of UTC
+(negative if the timezone is east of UTC).
The log command also accepts date ranges:
-- ``<{datetime}`` - at or before a given date/time
-- ``>{datetime}`` - on or after a given date/time
-- ``{datetime} to {datetime}`` - a date range, inclusive
-- ``-{days}`` - within a given number of days of today
+- ``<DATE`` - at or before a given date/time
+- ``>DATE`` - on or after a given date/time
+- ``DATE to DATE`` - a date range, inclusive
+- ``-DAYS`` - within a given number of days of today
--- a/mercurial/help/environment.txt Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/help/environment.txt Tue Apr 05 16:11:40 2011 -0500
@@ -59,6 +59,14 @@
Equivalent options set via command line flags or environment
variables are not overridden.
+HGPLAINEXCEPT
+ This is a comma-separated list of features to preserve when
+ HGPLAIN is enabled. Currently the only value supported is "i18n",
+ which preserves internationalization in plain mode.
+
+ Setting HGPLAINEXCEPT to anything (even an empty string) will
+ enable plain mode.
+
HGUSER
This is the string used as the author of a commit. If not set,
available values will be considered in this order:
--- a/mercurial/help/templates.txt Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/help/templates.txt Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/hg.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/hgweb/common.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/hgweb/hgweb_mod.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/hgweb/hgwebdir_mod.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/hgweb/protocol.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/hgweb/server.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/hgweb/wsgicgi.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/httprepo.py Tue Apr 05 16:11:40 2011 -0500
@@ -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/i18n.py Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/i18n.py Tue Apr 05 16:11:40 2011 -0500
@@ -51,7 +51,13 @@
# An unknown encoding results in a LookupError.
return message
-if 'HGPLAIN' in os.environ:
+def _plain():
+ if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
+ return False
+ exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
+ return 'i18n' not in exceptions
+
+if _plain():
_ = lambda message: message
else:
_ = gettext
--- a/mercurial/localrepo.py Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/localrepo.py Tue Apr 05 16:11:40 2011 -0500
@@ -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"))
@@ -361,7 +362,12 @@
tags = {}
for (name, (node, hist)) in alltags.iteritems():
if node != nullid:
- tags[encoding.tolocal(name)] = node
+ try:
+ # ignore tags to unknown nodes
+ self.changelog.lookup(node)
+ tags[encoding.tolocal(name)] = node
+ except error.LookupError:
+ pass
tags['tip'] = self.changelog.tip()
tagtypes = dict([(encoding.tolocal(name), value)
for (name, value) in tagtypes.iteritems()])
@@ -386,7 +392,7 @@
for t, n in self.tags().iteritems():
try:
r = self.changelog.rev(n)
- except:
+ except error.LookupError:
r = -2 # sort to the beginning of the list if unknown
l.append((r, t, n))
return [(t, n) for r, t, n in sorted(l)]
@@ -521,7 +527,7 @@
if isinstance(key, int):
return self.changelog.node(key)
elif key == '.':
- return self.dirstate.parents()[0]
+ return self.dirstate.p1()
elif key == 'null':
return nullid
elif key == 'tip':
@@ -558,6 +564,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 +1024,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 +1327,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 +1357,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 +1436,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 +1448,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 +1586,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 +1818,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 +1918,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 +1931,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/manifest.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/merge.py Tue Apr 05 16:11:40 2011 -0500
@@ -268,7 +268,7 @@
updated, merged, removed, unresolved = 0, 0, 0, 0
ms = mergestate(repo)
- ms.reset(wctx.parents()[0].node())
+ ms.reset(wctx.p1().node())
moves = []
action.sort(key=actionkey)
substate = wctx.substate # prime
@@ -286,7 +286,7 @@
fco = mctx[f2]
if mctx == actx: # backwards, use working dir parent as ancestor
if fcl.parents():
- fca = fcl.parents()[0]
+ fca = fcl.p1()
else:
fca = repo.filectx(f, fileid=nullrev)
else:
@@ -439,7 +439,7 @@
if f:
repo.dirstate.forget(f)
-def update(repo, node, branchmerge, force, partial):
+def update(repo, node, branchmerge, force, partial, ancestor=None):
"""
Perform a merge between the working directory and the given node
@@ -492,9 +492,12 @@
overwrite = force and not branchmerge
pl = wc.parents()
p1, p2 = pl[0], repo[node]
- pa = p1.ancestor(p2)
+ if ancestor:
+ pa = repo[ancestor]
+ else:
+ 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 +507,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 +552,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/osutil.c Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/parser.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/patch.py Tue Apr 05 16:11:40 2011 -0500
@@ -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))
@@ -1331,7 +1324,7 @@
opts = mdiff.defaultopts
if not node1 and not node2:
- node1 = repo.dirstate.parents()[0]
+ node1 = repo.dirstate.p1()
def lrugetfilectx():
cache = {}
--- a/mercurial/posix.py Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/posix.py Tue Apr 05 16:11:40 2011 -0500
@@ -7,7 +7,7 @@
from i18n import _
import osutil
-import os, sys, errno, stat, getpass, pwd, grp
+import os, sys, errno, stat, getpass, pwd, grp, tempfile
posixfile = open
nulldev = '/dev/null'
@@ -108,6 +108,45 @@
# Turn off all +x bits
os.chmod(f, s & 0666)
+def checkexec(path):
+ """
+ Check whether the given path is on a filesystem with UNIX-like exec flags
+
+ Requires a directory (like /foo/.hg)
+ """
+
+ # VFAT on some Linux versions can flip mode but it doesn't persist
+ # a FS remount. Frequently we can detect it if files are created
+ # with exec bit on.
+
+ try:
+ EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
+ fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
+ try:
+ os.close(fh)
+ m = os.stat(fn).st_mode & 0777
+ new_file_has_exec = m & EXECFLAGS
+ os.chmod(fn, m ^ EXECFLAGS)
+ exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
+ finally:
+ os.unlink(fn)
+ except (IOError, OSError):
+ # we don't care, the user probably won't be able to commit anyway
+ return False
+ return not (new_file_has_exec or exec_flags_cannot_flip)
+
+def checklink(path):
+ """check whether the given path is on a symlink-capable filesystem"""
+ # mktemp is not racy because symlink creation will fail if the
+ # file already exists
+ name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
+ try:
+ os.symlink(".", name)
+ os.unlink(name)
+ return True
+ except (OSError, AttributeError):
+ return False
+
def set_binary(fd):
pass
--- a/mercurial/repair.py Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/repair.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/revlog.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/revset.py Tue Apr 05 16:11:40 2011 -0500
@@ -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, ")")),
@@ -207,7 +207,7 @@
First parent of changesets in set, or the working directory.
"""
if x is None:
- p = repo[x].parents()[0].rev()
+ p = repo[x].p1().rev()
return [r for r in subset if r == p]
ps = set()
@@ -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,
@@ -786,7 +808,7 @@
elif op == 'func':
f = getstring(x[1], _("not a symbol"))
wa, ta = optimize(x[2], small)
- if f in "grep date user author keyword branch file outgoing":
+ if f in "grep date user author keyword branch file outgoing closed":
w = 10 # slow
elif f in "modifies adds removes":
w = 30 # slower
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/sshrepo.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/sshserver.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/statichttprepo.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/subrepo.py Tue Apr 05 16:11:40 2011 -0500
@@ -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')
@@ -144,7 +144,7 @@
debug(s, "prompt remove")
wctx.sub(s).remove()
- for s, r in s2.items():
+ for s, r in sorted(s2.items()):
if s in s1:
continue
elif s not in sa:
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/templatefilters.py Tue Apr 05 16:11:40 2011 -0500
@@ -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('&', '&')
- .replace('<', '<')
- .replace('>', '>')
- .replace('"', '"')
- .replace("'", ''')) # ' 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('&', '&')
+ .replace('<', '<')
+ .replace('>', '>')
+ .replace('"', '"')
+ .replace("'", ''')) # ' 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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/templatekw.py Tue Apr 05 16:11:40 2011 -0500
@@ -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.
@@ -73,8 +73,7 @@
def getfiles(repo, ctx, revcache):
if 'files' not in revcache:
- revcache['files'] = repo.status(ctx.parents()[0].node(),
- ctx.node())[:3]
+ revcache['files'] = repo.status(ctx.p1().node(), ctx.node())[:3]
return revcache['files']
def getlatesttags(repo, ctx, cache):
@@ -143,32 +142,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 +200,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 +227,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 +268,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 +313,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/templater.py Tue Apr 05 16:11:40 2011 -0500
@@ -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/templates/coal/map Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/templates/coal/map Tue Apr 05 16:11:40 2011 -0500
@@ -94,14 +94,12 @@
filerename = '{file|escape}@'
filelogrename = '
- <tr>
- <th>base:</th>
- <td>
- <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
- {file|escape}@{node|short}
- </a>
- </td>
- </tr>'
+ <span class="base">
+ base
+ <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
+ {file|escape}@{node|short}
+ </a>
+ </span>'
fileannotateparent = '
<tr>
<td class="metatag">parent:</td>
--- a/mercurial/templates/paper/filelogentry.tmpl Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/templates/paper/filelogentry.tmpl Tue Apr 05 16:11:40 2011 -0500
@@ -1,5 +1,5 @@
<tr class="parity{parity}">
<td class="age">{date|age}</td>
<td class="author">{author|person}</td>
- <td class="description"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}</td>
+ <td class="description"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{rename%filelogrename}</td>
</tr>
--- a/mercurial/templates/paper/map Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/templates/paper/map Tue Apr 05 16:11:40 2011 -0500
@@ -93,14 +93,12 @@
filerename = '{file|escape}@'
filelogrename = '
- <tr>
- <th>base:</th>
- <td>
- <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
- {file|escape}@{node|short}
- </a>
- </td>
- </tr>'
+ <span class="base">
+ base
+ <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
+ {file|escape}@{node|short}
+ </a>
+ </span>'
fileannotateparent = '
<tr>
<td class="metatag">parent:</td>
--- a/mercurial/templates/static/style-coal.css Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/templates/static/style-coal.css Tue Apr 05 16:11:40 2011 -0500
@@ -173,6 +173,7 @@
.bigtable .age { width: 6em; }
.bigtable .author { width: 12em; }
.bigtable .description { }
+.bigtable .description .base { font-size: 70%; float: right; line-height: 1.66; }
.bigtable .node { width: 5em; font-family: monospace;}
.bigtable .lineno { width: 2em; text-align: right;}
.bigtable .lineno a { color: #999; font-size: smaller; font-family: monospace;}
--- a/mercurial/templates/static/style-paper.css Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/templates/static/style-paper.css Tue Apr 05 16:11:40 2011 -0500
@@ -164,6 +164,7 @@
.bigtable .age { width: 7em; }
.bigtable .author { width: 12em; }
.bigtable .description { }
+.bigtable .description .base { font-size: 70%; float: right; line-height: 1.66; }
.bigtable .node { width: 5em; font-family: monospace;}
.bigtable .permissions { width: 8em; text-align: left;}
.bigtable .size { width: 5em; text-align: right; }
--- a/mercurial/ui.py Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/ui.py Tue Apr 05 16:11:40 2011 -0500
@@ -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,20 +273,27 @@
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?
- Plain mode means that all configuration variables which affect the
- behavior and output of Mercurial should be ignored. Additionally, the
- output should be stable, reproducible and suitable for use in scripts or
- applications.
+ Plain mode means that all configuration variables which affect
+ the behavior and output of Mercurial should be
+ ignored. Additionally, the output should be stable,
+ reproducible and suitable for use in scripts or applications.
+
+ The only way to trigger plain mode is by setting either the
+ `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
- The only way to trigger plain mode is by setting the `HGPLAIN'
- environment variable.
+ The return value can either be False, True, or a list of
+ features that plain mode should not apply to (e.g., i18n,
+ progress, etc).
'''
- return 'HGPLAIN' in os.environ
+ if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
+ return False
+ exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
+ return exceptions or True
def username(self):
"""Return default username to be used in commits.
@@ -325,7 +332,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 +490,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/url.py Tue Apr 05 16:11:40 2011 -0500
@@ -7,33 +7,272 @@
# 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, re
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 = "/!~*'()+"
+ _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
+
+ 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 self._matchscheme(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 +431,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 +449,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 +619,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 +919,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 +949,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/util.py Tue Apr 05 16:11:40 2011 -0500
@@ -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)
@@ -680,45 +683,6 @@
return ''.join(result)
-def checkexec(path):
- """
- Check whether the given path is on a filesystem with UNIX-like exec flags
-
- Requires a directory (like /foo/.hg)
- """
-
- # VFAT on some Linux versions can flip mode but it doesn't persist
- # a FS remount. Frequently we can detect it if files are created
- # with exec bit on.
-
- try:
- EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
- fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
- try:
- os.close(fh)
- m = os.stat(fn).st_mode & 0777
- new_file_has_exec = m & EXECFLAGS
- os.chmod(fn, m ^ EXECFLAGS)
- exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
- finally:
- os.unlink(fn)
- except (IOError, OSError):
- # we don't care, the user probably won't be able to commit anyway
- return False
- return not (new_file_has_exec or exec_flags_cannot_flip)
-
-def checklink(path):
- """check whether the given path is on a symlink-capable filesystem"""
- # mktemp is not racy because symlink creation will fail if the
- # file already exists
- name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
- try:
- os.symlink(".", name)
- os.unlink(name)
- return True
- except (OSError, AttributeError):
- return False
-
def checknlink(testfile):
'''check whether hardlink count reporting works properly'''
@@ -769,7 +733,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,10 +1179,17 @@
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] == "<":
+ if not date[1:]:
+ raise Abort(_("invalid day spec, use '<DATE'"))
when = upper(date[1:])
return lambda x: x <= when
elif date[0] == ">":
+ if not date[1:]:
+ raise Abort(_("invalid day spec, use '>DATE'"))
when = lower(date[1:])
return lambda x: x >= when
elif date[0] == "-":
@@ -1215,6 +1197,9 @@
days = int(date[1:])
except ValueError:
raise Abort(_("invalid day spec: %s") % date[1:])
+ if days < 0:
+ raise Abort(_("%s must be nonnegative (see 'hg help dates')")
+ % date[1:])
when = makedate()[0] - days * 3600 * 24
return lambda x: x >= when
elif " to " in date:
@@ -1367,26 +1352,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/win32.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/windows.py Tue Apr 05 16:11:40 2011 -0500
@@ -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
@@ -132,6 +132,12 @@
def set_flags(f, l, x):
pass
+def checkexec(path):
+ return False
+
+def checklink(path):
+ return False
+
def set_binary(fd):
# When run without console, pipes may expose invalid
# fileno(), usually set to -1.
@@ -286,53 +292,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/mercurial/wireproto.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/setup.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/run-tests.py Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-586.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-acl.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-annotate.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-basic.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-bisect.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-bisect2.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-bookmarks-pushpull.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-bookmarks.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-bundle.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-command-template.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-baz Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-darcs.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-git.t Tue Apr 05 16:11:40 2011 -0500
@@ -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-source.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-hg-source.t Tue Apr 05 16:11:40 2011 -0500
@@ -17,6 +17,7 @@
$ hg copy foo baz
$ hg ci -m 'make bar and baz copies of foo' -d '2 0'
created new head
+ $ hg bookmark premerge1
$ hg merge
merging baz and foo to baz
1 files updated, 1 files merged, 0 files removed, 0 files unresolved
@@ -24,6 +25,7 @@
$ hg ci -m 'merge local copy' -d '3 0'
$ hg up -C 1
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg bookmark premerge2
$ hg merge 2
merging foo and baz to baz
1 files updated, 1 files merged, 0 files removed, 0 files unresolved
@@ -50,12 +52,16 @@
2 mark baz executable
1 branch foo
0 close
+ updating bookmarks
$ cd new
$ hg out ../orig
comparing with ../orig
searching for changes
no changes found
[1]
+ $ hg bookmarks
+ premerge1 3:973ef48a98a4
+ premerge2 7:0d18d1112f1d
$ cd ..
check shamap LF and CRLF handling
@@ -82,6 +88,7 @@
converting...
1 change foo again again
0 change foo again
+ updating bookmarks
init broken repository
--- a/tests/test-convert-hg-startrev.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-hg-startrev.t Tue Apr 05 16:11:40 2011 -0500
@@ -1,5 +1,5 @@
- $ cat > $HGRCPATH <<EOF
+ $ cat >> $HGRCPATH <<EOF
> [extensions]
> graphlog =
> convert =
--- a/tests/test-convert-mtn.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-mtn.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-svn-branches.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-svn-encoding.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-svn-move.t Tue Apr 05 16:11:40 2011 -0500
@@ -5,7 +5,7 @@
> {
> tr '\\' /
> }
- $ cat > $HGRCPATH <<EOF
+ $ cat >> $HGRCPATH <<EOF
> [extensions]
> convert =
> graphlog =
--- a/tests/test-convert-svn-sink.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-svn-sink.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-svn-source.t Tue Apr 05 16:11:40 2011 -0500
@@ -5,7 +5,7 @@
> {
> tr '\\' /
> }
- $ cat > $HGRCPATH <<EOF
+ $ cat >> $HGRCPATH <<EOF
> [extensions]
> convert =
> graphlog =
--- a/tests/test-convert-svn-startrev.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-svn-startrev.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-svn-tags.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-tagsbranch-topology.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-convert-tla.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-debugcomplete.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-diff-color.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-eol-add.t Tue Apr 05 16:11:40 2011 -0500
@@ -1,6 +1,6 @@
Test adding .hgeol
- $ cat > $HGRCPATH <<EOF
+ $ cat >> $HGRCPATH <<EOF
> [diff]
> git = 1
> EOF
--- a/tests/test-eol-clone.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-eol-clone.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-eol-hook.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-eol-patch.t Tue Apr 05 16:11:40 2011 -0500
@@ -1,6 +1,6 @@
Test EOL patching
- $ cat > $HGRCPATH <<EOF
+ $ cat >> $HGRCPATH <<EOF
> [diff]
> git = 1
> EOF
--- a/tests/test-eol-tag.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-eol-tag.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-eol-update.t Tue Apr 05 16:11:40 2011 -0500
@@ -1,6 +1,6 @@
Test EOL update
- $ cat > $HGRCPATH <<EOF
+ $ cat >> $HGRCPATH <<EOF
> [diff]
> git = 1
> EOF
--- a/tests/test-eol.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-eol.t Tue Apr 05 16:11:40 2011 -0500
@@ -1,6 +1,6 @@
Test EOL extension
- $ cat > $HGRCPATH <<EOF
+ $ cat >> $HGRCPATH <<EOF
> [diff]
> git = True
> EOF
--- a/tests/test-extdiff.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-extdiff.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-globalopts.t Tue Apr 05 16:11:40 2011 -0500
@@ -28,6 +28,7 @@
pulling from ../b
searching for changes
warning: repository is unrelated
+ requesting all changes
adding changesets
adding manifests
adding file changes
@@ -334,22 +335,24 @@
config Configuration Files
dates Date Formats
- patterns File Name Patterns
+ diffs Diff Formats
environment Environment Variables
- revisions Specifying Single Revisions
+ extensions Using additional features
+ glossary Glossary
+ hgweb Configuring hgweb
+ merge-tools Merge Tools
multirevs Specifying Multiple Revisions
+ patterns File Name Patterns
+ revisions Specifying Single Revisions
revsets Specifying Revision Sets
- diffs Diff Formats
- merge-tools Merge Tools
+ subrepos Subrepositories
templating Template Usage
urls URL Paths
- extensions Using additional features
- subrepos Subrepositories
- hgweb Configuring hgweb
- glossary Glossary
use "hg -v help" to show builtin aliases and global options
+
+
$ hg --help
Mercurial Distributed SCM
@@ -411,19 +414,19 @@
config Configuration Files
dates Date Formats
- patterns File Name Patterns
+ diffs Diff Formats
environment Environment Variables
- revisions Specifying Single Revisions
+ extensions Using additional features
+ glossary Glossary
+ hgweb Configuring hgweb
+ merge-tools Merge Tools
multirevs Specifying Multiple Revisions
+ patterns File Name Patterns
+ revisions Specifying Single Revisions
revsets Specifying Revision Sets
- diffs Diff Formats
- merge-tools Merge Tools
+ subrepos Subrepositories
templating Template Usage
urls URL Paths
- extensions Using additional features
- subrepos Subrepositories
- hgweb Configuring hgweb
- glossary Glossary
use "hg -v help" to show builtin aliases and global options
--- a/tests/test-help.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-help.t Tue Apr 05 16:11:40 2011 -0500
@@ -105,19 +105,19 @@
config Configuration Files
dates Date Formats
- patterns File Name Patterns
+ diffs Diff Formats
environment Environment Variables
- revisions Specifying Single Revisions
+ extensions Using additional features
+ glossary Glossary
+ hgweb Configuring hgweb
+ merge-tools Merge Tools
multirevs Specifying Multiple Revisions
+ patterns File Name Patterns
+ revisions Specifying Single Revisions
revsets Specifying Revision Sets
- diffs Diff Formats
- merge-tools Merge Tools
+ subrepos Subrepositories
templating Template Usage
urls URL Paths
- extensions Using additional features
- subrepos Subrepositories
- hgweb Configuring hgweb
- glossary Glossary
use "hg -v help" to show builtin aliases and global options
@@ -178,19 +178,19 @@
config Configuration Files
dates Date Formats
- patterns File Name Patterns
+ diffs Diff Formats
environment Environment Variables
- revisions Specifying Single Revisions
+ extensions Using additional features
+ glossary Glossary
+ hgweb Configuring hgweb
+ merge-tools Merge Tools
multirevs Specifying Multiple Revisions
+ patterns File Name Patterns
+ revisions Specifying Single Revisions
revsets Specifying Revision Sets
- diffs Diff Formats
- merge-tools Merge Tools
+ subrepos Subrepositories
templating Template Usage
urls URL Paths
- extensions Using additional features
- subrepos Subrepositories
- hgweb Configuring hgweb
- glossary Glossary
Test short command list with verbose option
@@ -705,22 +705,24 @@
config Configuration Files
dates Date Formats
- patterns File Name Patterns
+ diffs Diff Formats
environment Environment Variables
- revisions Specifying Single Revisions
+ extensions Using additional features
+ glossary Glossary
+ hgweb Configuring hgweb
+ merge-tools Merge Tools
multirevs Specifying Multiple Revisions
+ patterns File Name Patterns
+ revisions Specifying Single Revisions
revsets Specifying Revision Sets
- diffs Diff Formats
- merge-tools Merge Tools
+ subrepos Subrepositories
templating Template Usage
urls URL Paths
- extensions Using additional features
- subrepos Subrepositories
- hgweb Configuring hgweb
- glossary Glossary
use "hg -v help" to show builtin aliases and global options
+
+
Test list of commands with command with no help text
$ hg help helpext
@@ -765,6 +767,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-hgrc.t Tue Apr 05 16:11:40 2011 -0500
@@ -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
@@ -133,3 +133,39 @@
none: ui.verbose=False
none: ui.debug=True
none: ui.quiet=False
+
+plain mode with exceptions
+
+ $ cat > plain.py <<EOF
+ > def uisetup(ui):
+ > ui.write('plain: %r\n' % ui.plain())
+ > EOF
+ $ echo "[extensions]" >> $HGRCPATH
+ $ echo "plain=./plain.py" >> $HGRCPATH
+ $ HGPLAINEXCEPT=; export HGPLAINEXCEPT
+ $ hg showconfig --config ui.traceback=True --debug
+ plain: ['']
+ read config from: $TESTTMP/hgrc
+ $TESTTMP/hgrc:15: extensions.plain=./plain.py
+ none: ui.traceback=True
+ none: ui.verbose=False
+ none: ui.debug=True
+ none: ui.quiet=False
+ $ unset HGPLAIN
+ $ hg showconfig --config ui.traceback=True --debug
+ plain: ['']
+ read config from: $TESTTMP/hgrc
+ $TESTTMP/hgrc:15: extensions.plain=./plain.py
+ none: ui.traceback=True
+ none: ui.verbose=False
+ none: ui.debug=True
+ none: ui.quiet=False
+ $ HGPLAINEXCEPT=i18n; export HGPLAINEXCEPT
+ $ hg showconfig --config ui.traceback=True --debug
+ plain: ['i18n']
+ read config from: $TESTTMP/hgrc
+ $TESTTMP/hgrc:15: extensions.plain=./plain.py
+ none: ui.traceback=True
+ none: ui.verbose=False
+ none: ui.debug=True
+ none: ui.quiet=False
--- a/tests/test-hgweb-commands.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-hgweb-commands.t Tue Apr 05 16:11:40 2011 -0500
@@ -913,7 +913,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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-http-branchmap.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-http-clone-r.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-http-proxy.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-https.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-identify.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-init.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-install.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-keyword.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-log.t Tue Apr 05 16:11:40 2011 -0500
@@ -512,12 +512,33 @@
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
$ hg log -d -1
+log -d ">"
+
+ $ hg log -d ">"
+ abort: invalid day spec, use '>DATE'
+ [255]
+
+log -d "<"
+
+ $ hg log -d "<"
+ abort: invalid day spec, use '<DATE'
+ [255]
+
+Negative ranges
+ $ hg log -d "--2"
+ abort: -2 must be nonnegative (see 'hg help dates')
+ [255]
+
log -p -l2 --color=always
--- a/tests/test-mq-strip.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-mq-strip.t Tue Apr 05 16:11:40 2011 -0500
@@ -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-parse-date.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-parse-date.t Tue Apr 05 16:11:40 2011 -0500
@@ -96,52 +96,32 @@
Test date formats with '>' or '<' accompanied by space characters
$ hg log -d '>' --template '{date|date}\n'
- Sun Jan 15 13:30:00 2006 +0500
- Sun Jan 15 13:30:00 2006 -0800
- Sat Jul 15 13:30:00 2006 +0500
- Sat Jul 15 13:30:00 2006 -0700
- Sun Jun 11 00:26:40 2006 -0400
- Sat Apr 15 13:30:00 2006 +0200
- Sat Apr 15 13:30:00 2006 +0000
- Wed Feb 01 13:00:30 2006 -0500
- Wed Feb 01 13:00:30 2006 +0000
+ abort: invalid day spec, use '>DATE'
+ [255]
$ hg log -d '<' hg log -d '>' --template '{date|date}\n'
+ abort: invalid day spec, use '>DATE'
+ [255]
$ hg log -d ' >' --template '{date|date}\n'
- Sun Jan 15 13:30:00 2006 +0500
- Sun Jan 15 13:30:00 2006 -0800
- Sat Jul 15 13:30:00 2006 +0500
- Sat Jul 15 13:30:00 2006 -0700
- Sun Jun 11 00:26:40 2006 -0400
- Sat Apr 15 13:30:00 2006 +0200
- Sat Apr 15 13:30:00 2006 +0000
- Wed Feb 01 13:00:30 2006 -0500
- Wed Feb 01 13:00:30 2006 +0000
+ abort: invalid day spec, use '>DATE'
+ [255]
$ hg log -d ' <' --template '{date|date}\n'
+ abort: invalid day spec, use '<DATE'
+ [255]
$ hg log -d '> ' --template '{date|date}\n'
- Sun Jan 15 13:30:00 2006 +0500
- Sun Jan 15 13:30:00 2006 -0800
- Sat Jul 15 13:30:00 2006 +0500
- Sat Jul 15 13:30:00 2006 -0700
- Sun Jun 11 00:26:40 2006 -0400
- Sat Apr 15 13:30:00 2006 +0200
- Sat Apr 15 13:30:00 2006 +0000
- Wed Feb 01 13:00:30 2006 -0500
- Wed Feb 01 13:00:30 2006 +0000
+ abort: invalid day spec, use '>DATE'
+ [255]
$ hg log -d '< ' --template '{date|date}\n'
+ abort: invalid day spec, use '<DATE'
+ [255]
$ hg log -d ' > ' --template '{date|date}\n'
- Sun Jan 15 13:30:00 2006 +0500
- Sun Jan 15 13:30:00 2006 -0800
- Sat Jul 15 13:30:00 2006 +0500
- Sat Jul 15 13:30:00 2006 -0700
- Sun Jun 11 00:26:40 2006 -0400
- Sat Apr 15 13:30:00 2006 +0200
- Sat Apr 15 13:30:00 2006 +0000
- Wed Feb 01 13:00:30 2006 -0500
- Wed Feb 01 13:00:30 2006 +0000
+ abort: invalid day spec, use '>DATE'
+ [255]
$ hg log -d ' < ' --template '{date|date}\n'
+ abort: invalid day spec, use '<DATE'
+ [255]
$ hg log -d '>02/01' --template '{date|date}\n'
$ hg log -d '<02/01' --template '{date|date}\n'
--- a/tests/test-paths.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-paths.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-pull-branch.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-pull.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-rebase-collapse.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-rebase-mq.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Tue Apr 05 16:11:40 2011 -0500
@@ -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-parameters.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-rebase-parameters.t Tue Apr 05 16:11:40 2011 -0500
@@ -330,3 +330,63 @@
$ cd ..
+Test --tool parameter:
+
+ $ hg init b
+ $ cd b
+
+ $ echo c1 > c1
+ $ hg ci -Am c1
+ adding c1
+
+ $ echo c2 > c2
+ $ hg ci -Am c2
+ adding c2
+
+ $ hg up -q 0
+ $ echo c2b > c2
+ $ hg ci -Am c2b
+ adding c2
+ created new head
+
+ $ cd ..
+
+ $ hg clone -q -u . b b1
+ $ cd b1
+
+ $ hg rebase -s 2 -d 1 --tool internal:local
+ saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob)
+
+ $ hg cat c2
+ c2
+
+ $ cd ..
+
+
+ $ hg clone -q -u . b b2
+ $ cd b2
+
+ $ hg rebase -s 2 -d 1 --tool internal:other
+ saved backup bundle to $TESTTMP/b2/.hg/strip-backup/*-backup.hg (glob)
+
+ $ hg cat c2
+ c2b
+
+ $ cd ..
+
+
+ $ hg clone -q -u . b b3
+ $ cd b3
+
+ $ hg rebase -s 2 -d 1 --tool internal:fail
+ abort: unresolved conflicts (see hg resolve, then hg rebase --continue)
+ [255]
+
+ $ hg resolve -l
+ U c2
+
+ $ hg resolve -m c2
+ $ hg rebase -c --tool internal:fail
+ tool option will be ignored
+ saved backup bundle to $TESTTMP/b3/.hg/strip-backup/*-backup.hg (glob)
+
--- a/tests/test-relink.t Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-relink.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-rename-merge1.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-revset-outgoing.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-revset.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-schemes.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 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 Sun Mar 27 13:34:20 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 Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-ssh-clone-r.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-ssh.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-subrepo-git.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-subrepo-paths.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-transplant.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-unrelated-pull.t Tue Apr 05 16:11:40 2011 -0500
@@ -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 Sun Mar 27 13:34:20 2011 +0200
+++ b/tests/test-url.py Tue Apr 05 16:11:40 2011 -0500
@@ -49,6 +49,157 @@
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/'>
+ >>> url('/foo:bar')
+ <url path: '/foo:bar'>
+ >>> url('\\\\foo:bar')
+ <url path: '\\\\foo:bar'>
+ >>> url('./foo:bar')
+ <url path: './foo:bar'>
+
+ 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 Tue Apr 05 16:11:40 2011 -0500
@@ -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
+