# HG changeset patch # User Matt Mackall # Date 1301682344 18000 # Node ID 3cbf5c6dcdb8bca829e51b1624dfbcb5823b8ac1 # Parent 1209e1d52b6866fa693642869cb9c1786b62aeb5# Parent f911b1de5fa0145b2413b39a96635b6167b7161c merge with stable diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 .hgignore --- a/.hgignore Fri Apr 01 13:25:26 2011 -0500 +++ b/.hgignore Fri Apr 01 13:25:44 2011 -0500 @@ -7,6 +7,7 @@ *.mergebackup *.o *.so +*.dll *.pyd *.pyc *.pyo diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 contrib/check-code.py --- a/contrib/check-code.py Fri Apr 01 13:25:26 2011 -0500 +++ b/contrib/check-code.py Fri Apr 01 13:25:44 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 = [ diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 contrib/shrink-revlog.py --- a/contrib/shrink-revlog.py Fri Apr 01 13:25:26 2011 -0500 +++ b/contrib/shrink-revlog.py Fri Apr 01 13:25:44 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: diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 contrib/zsh_completion --- a/contrib/zsh_completion Fri Apr 01 13:25:26 2011 -0500 +++ b/contrib/zsh_completion Fri Apr 01 13:25:44 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]' diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 doc/hgrc.5.txt --- a/doc/hgrc.5.txt Fri Apr 01 13:25:26 2011 -0500 +++ b/doc/hgrc.5.txt Fri Apr 01 13:25:44 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`` diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/bugzilla.py --- a/hgext/bugzilla.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/bugzilla.py Fri Apr 01 13:25:44 2011 -0500 @@ -1,6 +1,7 @@ # bugzilla.py - bugzilla integration for mercurial # # Copyright 2006 Vadim Gelfer +# Copyright 2011 Jim Hague # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -8,67 +9,66 @@ '''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 updates the Bugzilla database directly. Only Bugzilla -installations using MySQL are supported. +The hook does not change bug status. -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. +Three basic modes of access to Bugzilla are provided: + +1. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later. + +2. Check data via the Bugzilla XMLRPC interface and submit bug change + via email to Bugzilla email interface. Requires Bugzilla 3.4 or later. -The extension is configured through three different configuration -sections. These keys are recognized in the [bugzilla] section: - -host - Hostname of the MySQL server holding the Bugzilla database. +2. Writing directly to the Bugzilla database. Only Bugzilla installations + using MySQL are supported. Requires Python MySQLdb. -db - Name of the Bugzilla database in MySQL. Default 'bugs'. - -user - Username to use to access MySQL server. Default 'bugs'. - -password - Password to use to access MySQL server. +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. -timeout - Database connection timeout (seconds). Default 5. +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. -version - Bugzilla version. Specify '3.0' for Bugzilla versions 3.0 and later, - '2.18' for Bugzilla versions from 2.18 and '2.16' for versions prior - to 2.18. +Access via XMLRPC/email behaves uses XMLRPC to query Bugzilla, but sends +email to the Bugzilla email interface to submit comments to bugs. +The From: address in the email is set to the email address of the Mercurial +user, so the comment appears to come from the Mercurial user. In the event +that the Mercurial user email is not recognised by Bugzilla as a Bugzilla +user, the Bugzilla username and password used to log into Bugzilla are +used instead as the source of the comment. -bzuser - Fallback Bugzilla user name to record comments with, if changeset - committer cannot be found as a Bugzilla user. +Configuration items common to all access modes: -bzdir - Bugzilla install directory. Used by default notify. Default - '/var/www/html/bugzilla'. +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. -notify - The command to run to get Bugzilla to send bug change notification - emails. Substitutes from a map with 3 keys, 'bzdir', 'id' (bug id) - and 'user' (committer bugzilla email). Default depends on version; - from 2.18 it is "cd %(bzdir)s && perl -T contrib/sendbugmail.pl - %(id)s %(user)s". +bugzilla.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. -regexp - Regular expression to match bug IDs in changeset commit message. - Must contain one "()" group. The default expression matches 'Bug - 1234', 'Bug no. 1234', 'Bug number 1234', 'Bugs 1234,5678', 'Bug - 1234 and 5678' and variations thereof. Matching is case insensitive. - -style +bugzilla.style The style file to use when formatting comments. -template +bugzilla.template Template to use when formatting comments. Overrides style if specified. In addition to the usual Mercurial keywords, the extension specifies:: @@ -78,27 +78,89 @@ {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}' + Default ``changeset {node|short} in repo {root} refers to bug + {bug}.\\ndetails:\\n\\t{desc|tabindent}`` -strip - The number of slashes to strip from the front of {root} to produce - {webroot}. Default 0. +bugzilla.strip + The number of path separator characters to strip from the front of + the Mercurial repository path (``{root}`` in templates) to produce + ``{webroot}``. For example, a repository with ``{root}`` + ``/var/local/my-project`` with a strip of 2 gives a value for + ``{webroot}`` of ``my-project``. Default 0. -usermap - Path of file containing Mercurial committer ID to Bugzilla user ID +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. + 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``. + +XMLRPC access mode configuration: + +bugzilla.bzurl + The base URL for the Bugzilla installation. + Default ``http://localhost/bugzilla``. -The [usermap] section is used to specify mappings of Mercurial -committer ID to Bugzilla user ID. See also [bugzilla].usermap. -"committer"="Bugzilla user" +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: + +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]``. -Finally, the [web] section supports one entry: +MySQL access mode configuration: + +bugzilla.host + Hostname of the MySQL server holding the Bugzilla database. + Default ``localhost``. + +bugzilla.db + Name of the Bugzilla database in MySQL. Default ``bugs``. + +bugzilla.user + Username to use to access MySQL server. Default ``bugs``. + +bugzilla.password + Password to use to access MySQL server. -baseurl - Base URL for browsing Mercurial repositories. Reference from - templates as {hgweb}. +bugzilla.timeout + Database connection timeout (seconds). Default 5. + +bugzilla.bzuser + Fallback Bugzilla user name to record comments with, if changeset + committer cannot be found as a Bugzilla user. + +bugzilla.bzdir + Bugzilla install directory. Used by default notify. Default + ``/var/www/html/bugzilla``. + +bugzilla.notify + The command to run to get Bugzilla to send bug change notification + emails. Substitutes from a map with 3 keys, ``bzdir``, ``id`` (bug + id) and ``user`` (committer bugzilla email). Default depends on + version; from 2.18 it is "cd %(bzdir)s && perl -T + contrib/sendbugmail.pl %(id)s %(user)s". Activating the extension:: @@ -109,11 +171,44 @@ # 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/``. :: + + [bugzilla] + bzurl=http://my-project.org/bugzilla + user=bugmail@my-project.org + password=plugh + version=xmlrpc + + [web] + baseurl=http://my-project.org/hg -This example configuration is for a collection of Mercurial -repositories in /var/local/hg/repos/ used with a local Bugzilla 3.2 -installation in /opt/bugzilla-3.2. :: +XMLRPC+email example configuration. This uses the Bugzilla at +``http://my-project.org/bugzilla``, logging in as user +``bugmail@my-project.org`` wityh password ``plugh``. It is used with a +collection of Mercurial repositories in ``/var/local/hg/repos/``. Bug +comments are sent to the Bugzilla email address +``buzilla@my-project.org``. :: + + [bugzilla] + user=bugmail@my-project.org + password=plugh + version=xmlrpc + bzemail=bugzilla@my-project.org + + [web] + baseurl=https://dev.laicatc.com/hg + bugzillaurl=https://dev.laicatc.com/bugzilla + +MySQL example configuration. This is for a collection of Mercurial +repositories in ``/var/local/hg/repos/`` used with a local Bugzilla +3.2 installation in /opt/bugzilla-3.2. The MySQL database is on +``localhost``, the Bugzilla database name is ``bugs`` and MySQL is +accessed with MySQL username ``bugs`` password ``XYZZY``. :: [bugzilla] host=localhost @@ -132,7 +227,7 @@ [usermap] user@emaildomain.com=user.name@bugzilladomain.com -Commits add a comment to the Bugzilla bug record of the form:: +All the above add a comment to the Bugzilla bug record of the form:: Changeset 3b16791d6642 in repository-name. http://dev.domain.com/hg/repository-name/rev/3b16791d6642 @@ -142,31 +237,83 @@ from mercurial.i18n import _ from mercurial.node import short -from mercurial import cmdutil, templater, util -import re, time - -MySQLdb = None +from mercurial import cmdutil, mail, templater, util +import re, time, xmlrpclib -def buglist(ids): - return '(' + ','.join(map(str, ids)) + ')' - -class bugzilla_2_16(object): - '''support for bugzilla version 2.16.''' +class bzaccess(object): + '''Base class for access to Bugzilla.''' def __init__(self, ui): self.ui = ui + usermap = self.ui.config('bugzilla', 'usermap') + if usermap: + self.ui.readconfig(usermap, sections=['usermap']) + + def map_committer(self, user): + '''map name of committer to Bugzilla user name.''' + for committer, bzuser in self.ui.configitems('usermap'): + if committer.lower() == user.lower(): + return bzuser + return user + + # Methods to be implemented by access classes. + def filter_real_bug_ids(self, ids): + '''remove bug IDs that do not exist in Bugzilla from set.''' + pass + + def filter_cset_known_bug_ids(self, node, ids): + '''remove bug IDs where node occurs in comment text from set.''' + pass + + def add_comment(self, bugid, text, committer): + '''add comment to bug. + + If possible add the comment as being from the committer of + the changeset. Otherwise use the default Bugzilla user. + ''' + pass + + def notify(self, ids, committer): + '''Force sending of Bugzilla notification emails.''' + pass + +# Bugzilla via direct access to MySQL database. +class bzmysql(bzaccess): + '''Support for direct MySQL access to Bugzilla. + + The earliest Bugzilla version this is tested with is version 2.16. + + If your Bugzilla is version 3.2 or above, you are strongly + recommended to use the XMLRPC access method instead. + ''' + + @staticmethod + def sql_buglist(ids): + '''return SQL-friendly list of bug ids''' + return '(' + ','.join(map(str, ids)) + ')' + + _MySQLdb = None + + def __init__(self, ui): + try: + import MySQLdb as mysql + bzmysql._MySQLdb = mysql + except ImportError, err: + raise util.Abort(_('python mysql support not available: %s') % err) + + bzaccess.__init__(self, ui) + host = self.ui.config('bugzilla', 'host', 'localhost') user = self.ui.config('bugzilla', 'user', 'bugs') passwd = self.ui.config('bugzilla', 'password') db = self.ui.config('bugzilla', 'db', 'bugs') timeout = int(self.ui.config('bugzilla', 'timeout', 5)) - usermap = self.ui.config('bugzilla', 'usermap') - if usermap: - self.ui.readconfig(usermap, sections=['usermap']) self.ui.note(_('connecting to %s:%s as %s, password %s\n') % (host, db, user, '*' * len(passwd))) - self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd, - db=db, connect_timeout=timeout) + self.conn = bzmysql._MySQLdb.connect(host=host, + user=user, passwd=passwd, + db=db, + connect_timeout=timeout) self.cursor = self.conn.cursor() self.longdesc_id = self.get_longdesc_id() self.user_ids = {} @@ -177,7 +324,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 +337,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 +398,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 +432,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 +454,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 +656,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 +681,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 +723,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 +734,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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/color.py --- a/hgext/color.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/color.py Fri Apr 01 13:25:44 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', diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/convert/__init__.py --- a/hgext/convert/__init__.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/convert/__init__.py Fri Apr 01 13:25:44 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] diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/convert/common.py --- a/hgext/convert/common.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/convert/common.py Fri Apr 01 13:25:44 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): diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/convert/convcmd.py --- a/hgext/convert/convcmd.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/convert/convcmd.py Fri Apr 01 13:25:44 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() diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/convert/git.py --- a/hgext/convert/git.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/convert/git.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/convert/hg.py --- a/hgext/convert/hg.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/convert/hg.py Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/convert/monotone.py --- a/hgext/convert/monotone.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/convert/monotone.py Fri Apr 01 13:25:44 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 + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/convert/subversion.py --- a/hgext/convert/subversion.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/convert/subversion.py Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/eol.py --- a/hgext/eol.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/eol.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/extdiff.py --- a/hgext/extdiff.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/extdiff.py Fri Apr 01 13:25:44 2011 -0500 @@ -121,7 +121,7 @@ msg = _('cannot specify --rev and --change at the same time') raise util.Abort(msg) elif change: - node2 = repo.lookup(change) + node2 = cmdutil.revsingle(repo, change, None).node() node1a, node1b = repo.changelog.parents(node2) else: node1a, node2 = cmdutil.revpair(repo, revs) @@ -187,14 +187,14 @@ # Handle bogus modifies correctly by checking if the files exist if len(common) == 1: common_file = util.localpath(common.pop()) - dir1a = os.path.join(dir1a, common_file) + dir1a = os.path.join(tmproot, dir1a, common_file) label1a = common_file + rev1a - if not os.path.isfile(os.path.join(tmproot, dir1a)): + if not os.path.isfile(dir1a): dir1a = os.devnull if do3way: - dir1b = os.path.join(dir1b, common_file) + dir1b = os.path.join(tmproot, dir1b, common_file) label1b = common_file + rev1b - if not os.path.isfile(os.path.join(tmproot, dir1b)): + if not os.path.isfile(dir1b): dir1b = os.devnull dir2 = os.path.join(dir2root, dir2, common_file) label2 = common_file + rev2 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/graphlog.py --- a/hgext/graphlog.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/graphlog.py Fri Apr 01 13:25:44 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"))) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/keyword.py --- a/hgext/keyword.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/keyword.py Fri Apr 01 13:25:44 2011 -0500 @@ -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): diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/mq.py --- a/hgext/mq.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/mq.py Fri Apr 01 13:25:44 2011 -0500 @@ -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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/rebase.py --- a/hgext/rebase.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/rebase.py Fri Apr 01 13:25:44 2011 -0500 @@ -90,7 +90,8 @@ contf = opts.get('continue') abortf = opts.get('abort') collapsef = opts.get('collapse', False) - extrafn = opts.get('extrafn') + collapsemsg = cmdutil.logmessage(opts) + extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion keepf = opts.get('keep', False) keepbranchesf = opts.get('keepbranches', False) detachf = opts.get('detach', False) @@ -98,6 +99,10 @@ # other extensions keepopen = opts.get('keepopen', False) + if collapsemsg and not collapsef: + raise util.Abort( + _('message can only be specified with collapse')) + if contf or abortf: if contf and abortf: raise util.Abort(_('cannot use both abort and continue')) @@ -138,8 +143,7 @@ external = checkexternal(repo, state, targetancestors) if keepbranchesf: - if extrafn: - raise util.Abort(_('cannot use both keepbranches and extrafn')) + assert not extrafn, 'cannot use both keepbranches and extrafn' def extrafn(ctx, extra): extra['branch'] = ctx.branch() @@ -190,11 +194,14 @@ if collapsef and not keepopen: p1, p2 = defineparents(repo, min(state), target, state, targetancestors) - commitmsg = 'Collapsed revision' - for rebased in state: - if rebased not in skipped and state[rebased] != nullmerge: - commitmsg += '\n* %s' % repo[rebased].description() - commitmsg = ui.edit(commitmsg, repo.ui.username()) + if collapsemsg: + commitmsg = collapsemsg + else: + commitmsg = 'Collapsed revision' + for rebased in state: + if rebased not in skipped and state[rebased] != nullmerge: + commitmsg += '\n* %s' % repo[rebased].description() + commitmsg = ui.edit(commitmsg, repo.ui.username()) newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, extrafn=extrafn) @@ -270,7 +277,7 @@ if k in m1: if v in m1 or v in m2: repo.dirstate.copy(v, k) - if v in m2 and v not in m1: + if v in m2 and v not in m1 and k in m2: repo.dirstate.remove(v) def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None): @@ -354,6 +361,8 @@ 'Update rebased mq patches - finalize and then import them' mqrebase = {} mq = repo.mq + original_series = mq.full_series[:] + for p in mq.applied: rev = repo[p.node].rev() if rev in state: @@ -371,6 +380,15 @@ repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name)) mq.qimport(repo, (), patchname=name, git=isgit, rev=[str(state[rev])]) + + # Restore missing guards + for s in original_series: + pname = mq.guard_re.split(s, 1)[0] + if pname in mq.full_series: + repo.ui.debug('restoring guard for patch %s' % (pname)) + mq.full_series.remove(pname) + mq.full_series.append(s) + mq.series_dirty = True mq.save_dirty() def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches, @@ -475,9 +493,10 @@ if src: commonbase = repo[src].ancestor(repo[dest]) + samebranch = repo[src].branch() == repo[dest].branch() if commonbase == repo[src]: raise util.Abort(_('source is ancestor of destination')) - if commonbase == repo[dest]: + if samebranch and commonbase == repo[dest]: raise util.Abort(_('source is descendant of destination')) source = repo[src].rev() if detach: @@ -565,6 +584,10 @@ ('d', 'dest', '', _('rebase onto the specified changeset'), _('REV')), ('', 'collapse', False, _('collapse the rebased changesets')), + ('m', 'message', '', + _('use text as collapse commit message'), _('TEXT')), + ('l', 'logfile', '', + _('read collapse commit message from file'), _('FILE')), ('', 'keep', False, _('keep original changesets')), ('', 'keepbranches', False, _('keep original branch names')), ('', 'detach', False, _('force detaching of source from its original ' diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/record.py --- a/hgext/record.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/record.py Fri Apr 01 13:25:44 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: diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/relink.py --- a/hgext/relink.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/relink.py Fri Apr 01 13:25:44 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': ( diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/schemes.py --- a/hgext/schemes.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/schemes.py Fri Apr 01 13:25:44 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 '' % 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 hgext/transplant.py --- a/hgext/transplant.py Fri Apr 01 13:25:26 2011 -0500 +++ b/hgext/transplant.py Fri Apr 01 13:25:44 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 @@ -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] diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/ancestor.py --- a/mercurial/ancestor.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/ancestor.py Fri Apr 01 13:25:44 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() diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/archival.py --- a/mercurial/archival.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/archival.py Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/bdiff.c --- a/mercurial/bdiff.c Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/bdiff.c Fri Apr 01 13:25:44 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; diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/bookmarks.py --- a/mercurial/bookmarks.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/bookmarks.py Fri Apr 01 13:25:44 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")) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/bundlerepo.py Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/changegroup.py --- a/mercurial/changegroup.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/changegroup.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/cmdutil.py --- a/mercurial/cmdutil.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/cmdutil.py Fri Apr 01 13:25:44 2011 -0500 @@ -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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/commands.py --- a/mercurial/commands.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/commands.py Fri Apr 01 13:25:44 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: @@ -1175,6 +1201,7 @@ if len(items) > 1 or items and sections: raise util.Abort(_('only one config item permitted')) for section, name, value in ui.walkconfig(untrusted=untrusted): + value = str(value).replace('\n', '\\n') sectname = section + '.' + name if values: for v in values: @@ -1191,6 +1218,81 @@ ui.configsource(section, name, untrusted)) ui.write('%s=%s\n' % (sectname, value)) +def debugknown(ui, repopath, *ids, **opts): + """test whether node ids are known to a repo + + Every ID must be a full-length hex node id string. Returns a list of 0s and 1s + indicating unknown/known. + """ + repo = hg.repository(ui, repopath) + if not repo.capable('known'): + raise util.Abort("known() not supported by target repository") + flags = repo.known([bin(s) for s in ids]) + ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags]))) + +def debugbundle(ui, bundlepath, all=None, **opts): + """lists the contents of a bundle""" + f = url.open(ui, bundlepath) + try: + gen = changegroup.readbundle(f, bundlepath) + if all: + ui.write("format: id, p1, p2, cset, len(delta)\n") + + def showchunks(named): + ui.write("\n%s\n" % named) + while 1: + chunkdata = gen.parsechunk() + if not chunkdata: + break + node = chunkdata['node'] + p1 = chunkdata['p1'] + p2 = chunkdata['p2'] + cs = chunkdata['cs'] + delta = chunkdata['data'] + ui.write("%s %s %s %s %s\n" % + (hex(node), hex(p1), hex(p2), + hex(cs), len(delta))) + + showchunks("changelog") + showchunks("manifest") + while 1: + fname = gen.chunk() + if not fname: + break + showchunks(fname) + else: + while 1: + chunkdata = gen.parsechunk() + if not chunkdata: + break + node = chunkdata['node'] + ui.write("%s\n" % hex(node)) + finally: + f.close() + +def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts): + """retrieves a bundle from a repo + + Every ID must be a full-length hex node id string. Saves the bundle to the + given file. + """ + repo = hg.repository(ui, repopath) + if not repo.capable('getbundle'): + raise util.Abort("getbundle() not supported by target repository") + args = {} + if common: + args['common'] = [bin(s) for s in common] + if head: + args['heads'] = [bin(s) for s in head] + bundle = repo.getbundle('debug', **args) + + bundletype = opts.get('type', 'bzip2').lower() + btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'} + bundletype = btypes.get(bundletype) + if bundletype not in changegroup.bundletypes: + raise util.Abort(_('unknown bundle type specified with --type')) + changegroup.writebundle(bundle, bundlepath, bundletype) + def debugpushkey(ui, repopath, namespace, *keyinfo): '''access the pushkey key/value protocol @@ -1214,7 +1316,7 @@ def debugrevspec(ui, repo, expr): '''parse and apply a revision specification''' if ui.verbose: - tree = revset.parse(expr) + tree = revset.parse(expr)[0] ui.note(tree, "\n") func = revset.match(expr) for c in func(repo, range(len(repo))): @@ -1238,11 +1340,15 @@ finally: wlock.release() -def debugstate(ui, repo, nodates=None): +def debugstate(ui, repo, nodates=None, datesort=None): """show the contents of the current dirstate""" timestr = "" showdate = not nodates - for file_, ent in sorted(repo.dirstate._map.iteritems()): + if datesort: + keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename + else: + keyfunc = None # sort by filename + for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc): if showdate: if ent[3] == -1: # Pad or slice to locale representation @@ -1457,45 +1563,6 @@ ui.write(_(" (templates seem to have been installed incorrectly)\n")) problems += 1 - # patch - ui.status(_("Checking patch...\n")) - patchproblems = 0 - a = "1\n2\n3\n4\n" - b = "1\n2\n3\ninsert\n4\n" - fa = writetemp(a) - d = mdiff.unidiff(a, None, b, None, os.path.basename(fa), - os.path.basename(fa)) - fd = writetemp(d) - - files = {} - try: - patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files) - except util.Abort, e: - ui.write(_(" patch call failed:\n")) - ui.write(" " + str(e) + "\n") - patchproblems += 1 - else: - if list(files) != [os.path.basename(fa)]: - ui.write(_(" unexpected patch output!\n")) - patchproblems += 1 - a = open(fa).read() - if a != b: - ui.write(_(" patch test failed!\n")) - patchproblems += 1 - - if patchproblems: - if ui.config('ui', 'patch'): - ui.write(_(" (Current patch tool may be incompatible with patch," - " or misconfigured. Please check your configuration" - " file)\n")) - else: - ui.write(_(" Internal patcher failure, please report this error" - " to http://mercurial.selenic.com/wiki/BugTracker\n")) - problems += patchproblems - - os.unlink(fa) - os.unlink(fd) - # editor ui.status(_("Checking commit editor...\n")) editor = ui.geteditor() @@ -1555,6 +1622,21 @@ line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '') ui.write("%s\n" % line.rstrip()) +def debugwireargs(ui, repopath, *vals, **opts): + repo = hg.repository(hg.remoteui(ui, opts), repopath) + for opt in remoteopts: + del opts[opt[1]] + args = {} + for k, v in opts.iteritems(): + if v: + args[k] = v + # run twice to check that we don't mess up the stream for the next command + res1 = repo.debugwireargs(*vals, **args) + res2 = repo.debugwireargs(*vals, **args) + ui.write("%s\n" % res1) + if res1 != res2: + ui.warn("%s\n" % res2) + def diff(ui, repo, *pats, **opts): """diff repository (or selected files) @@ -1595,7 +1677,7 @@ msg = _('cannot specify --rev and --change at the same time') raise util.Abort(msg) elif change: - node2 = repo.lookup(change) + node2 = cmdutil.revsingle(repo, change, None).node() node1 = repo[node2].parents()[0].node() else: node1, node2 = cmdutil.revpair(repo, revs) @@ -1962,7 +2044,7 @@ Returns 0 if successful. """ option_lists = [] - textwidth = ui.termwidth() - 2 + textwidth = min(ui.termwidth(), 80) - 2 def addglobalopts(aliases): if ui.verbose: @@ -2141,6 +2223,8 @@ 'extensions\n')) help.addtopichook('revsets', revset.makedoc) + help.addtopichook('templates', templatekw.makedoc) + help.addtopichook('templates', templatefilters.makedoc) if name and name != 'shortlist': i = None @@ -2267,6 +2351,7 @@ output = [] revs = [] + bms = [] if source: source, branches = hg.parseurl(ui.expandpath(source)) repo = hg.repository(ui, source) @@ -2277,10 +2362,19 @@ rev = revs[0] if not rev: rev = "tip" - if num or branch or tags or bookmarks: - raise util.Abort(_("can't query remote revision number," - " branch, tags, or bookmarks")) - output = [hexfunc(repo.lookup(rev))] + if num or branch or tags: + raise util.Abort( + _("can't query remote revision number, branch, or tags")) + + remoterev = repo.lookup(rev) + if default or id: + output = [hexfunc(remoterev)] + + if 'bookmarks' in repo.listkeys('namespaces'): + hexremoterev = hex(remoterev) + bms = [bm for bm, bmrev in repo.listkeys('bookmarks').iteritems() + if bmrev == hexremoterev] + elif not rev: ctx = repo[None] parents = ctx.parents() @@ -2300,6 +2394,9 @@ if num: output.append(str(ctx.rev())) + if repo.local(): + bms = ctx.bookmarks() + if repo.local() and default and not ui.quiet: b = ctx.branch() if b != 'default': @@ -2310,8 +2407,9 @@ if t: output.append(t) + if default and not ui.quiet: # multiple bookmarks for a single parent separated by '/' - bm = '/'.join(ctx.bookmarks()) + bm = '/'.join(bms) if bm: output.append(bm) @@ -2322,7 +2420,7 @@ output.extend(ctx.tags()) if bookmarks: - output.extend(ctx.bookmarks()) + output.extend(bms) ui.write("%s\n" % ' '.join(output)) @@ -2891,7 +2989,13 @@ else: ui.status(_("not updating, since new heads added\n")) if modheads > 1: - ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n")) + currentbranchheads = len(repo.branchheads()) + if currentbranchheads == modheads: + ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n")) + elif currentbranchheads > 1: + ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n")) + else: + ui.status(_("(run 'hg heads' to see heads)\n")) else: ui.status(_("(run 'hg update' to get a working copy)\n")) @@ -2938,6 +3042,7 @@ raise util.Abort(err) modheads = repo.pull(other, heads=revs, force=opts.get('force')) + bookmarks.updatefromremote(ui, repo, other) if checkout: checkout = str(repo.changelog.rev(other.lookup(checkout))) repo._subtoppath = source @@ -3998,15 +4103,16 @@ fnames = (fname1,) + fnames lock = repo.lock() + wc = repo['.'] try: for fname in fnames: f = url.open(ui, fname) gen = changegroup.readbundle(f, fname) modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname, lock=lock) + bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch()) finally: lock.release() - return postincoming(ui, repo, modheads, opts.get('update'), None) def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False): @@ -4053,7 +4159,7 @@ if rev and node: raise util.Abort(_("please specify just one revision")) - if not rev: + if rev is None or rev == '': rev = node # if we defined a bookmark, we have to remember the original bookmark name @@ -4269,6 +4375,7 @@ ('g', 'good', False, _('mark changeset good')), ('b', 'bad', False, _('mark changeset bad')), ('s', 'skip', False, _('skip testing changeset')), + ('e', 'extend', False, _('extend the bisect range')), ('c', 'command', '', _('use command to check changeset state'), _('CMD')), ('U', 'noupdate', False, _('do not update to target'))], @@ -4359,6 +4466,11 @@ ('n', 'new-file', None, _('add new file at each rev')), ], _('[OPTION]... TEXT')), + "debugbundle": + (debugbundle, + [('a', 'all', None, _('show all details')), + ], + _('FILE')), "debugcheckstate": (debugcheckstate, [], ''), "debugcommands": (debugcommands, [], _('[COMMAND]')), "debugcomplete": @@ -4379,12 +4491,20 @@ _('[-e] DATE [RANGE]')), "debugdata": (debugdata, [], _('FILE REV')), "debugfsinfo": (debugfsinfo, [], _('[PATH]')), + "debuggetbundle": + (debuggetbundle, + [('H', 'head', [], _('id of head node'), _('ID')), + ('C', 'common', [], _('id of common node'), _('ID')), + ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')), + ], + _('REPO FILE [-H|-C ID]...')), "debugignore": (debugignore, [], ''), "debugindex": (debugindex, [('f', 'format', 0, _('revlog format'), _('FORMAT'))], _('FILE')), "debugindexdot": (debugindexdot, [], _('FILE')), "debuginstall": (debuginstall, [], ''), + "debugknown": (debugknown, [], _('REPO ID...')), "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')), "debugrebuildstate": (debugrebuildstate, @@ -4402,7 +4522,8 @@ (debugsetparents, [], _('REV1 [REV2]')), "debugstate": (debugstate, - [('', 'nodates', None, _('do not display the saved mtime'))], + [('', 'nodates', None, _('do not display the saved mtime')), + ('', 'datesort', None, _('sort by saved mtime'))], _('[OPTION]...')), "debugsub": (debugsub, @@ -4410,6 +4531,12 @@ _('revision to check'), _('REV'))], _('[-r REV] [REV]')), "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')), + "debugwireargs": + (debugwireargs, + [('', 'three', '', 'three'), + ('', 'four', '', 'four'), + ] + remoteopts, + _('REPO [OPTIONS]... [ONE [TWO]]')), "^diff": (diff, [('r', 'rev', [], @@ -4743,6 +4870,7 @@ } norepo = ("clone init version help debugcommands debugcomplete" - " debugdate debuginstall debugfsinfo debugpushkey") + " debugdate debuginstall debugfsinfo debugpushkey debugwireargs" + " debugknown debuggetbundle debugbundle") optionalrepo = ("identify paths serve showconfig debugancestor debugdag" " debugdata debugindex debugindexdot") diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/config.py --- a/mercurial/config.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/config.py Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/dirstate.py --- a/mercurial/dirstate.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/dirstate.py Fri Apr 01 13:25:44 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 @@ -236,6 +237,7 @@ "_ignore"): if a in self.__dict__: delattr(self, a) + self._lastnormaltime = None self._dirty = False def copy(self, source, dest): @@ -281,9 +283,15 @@ self._dirty = True self._addpath(f) s = os.lstat(self._join(f)) - self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime)) + mtime = int(s.st_mtime) + self._map[f] = ('n', s.st_mode, s.st_size, mtime) if f in self._copymap: del self._copymap[f] + if mtime > self._lastnormaltime: + # Remember the most recent modification timeslot for status(), + # to make sure we won't miss future size-preserving file content + # modifications that happen within the same timeslot. + self._lastnormaltime = mtime def normallookup(self, f): '''Mark a file normal, but possibly dirty.''' @@ -397,6 +405,7 @@ delattr(self, "_dirs") self._copymap = {} self._pl = [nullid, nullid] + self._lastnormaltime = None self._dirty = True def rebuild(self, parent, files): @@ -444,6 +453,7 @@ write(f) st.write(cs.getvalue()) st.rename() + self._lastnormaltime = None self._dirty = self._dirtypl = False def _dirignore(self, f): @@ -680,6 +690,7 @@ # lines are an expansion of "islink => checklink" # where islink means "is this a link?" and checklink # means "can we check links?". + mtime = int(st.st_mtime) if (size >= 0 and (size != st.st_size or ((mode ^ st.st_mode) & 0100 and self._checkexec)) @@ -687,9 +698,15 @@ or size == -2 # other parent or fn in self._copymap): madd(fn) - elif (time != int(st.st_mtime) + elif (mtime != time and (mode & lnkkind != lnkkind or self._checklink)): ladd(fn) + elif mtime == self._lastnormaltime: + # fn may have been changed in the same timeslot without + # changing its size. This can happen if we quickly do + # multiple commits in a single transaction. + # Force lookup, so we don't miss such a racy file change. + ladd(fn) elif listclean: cadd(fn) elif state == 'm': diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/discovery.py --- a/mercurial/discovery.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/discovery.py Fri Apr 01 13:25:44 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: diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/help.py --- a/mercurial/help.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/help.py Fri Apr 01 13:25:44 2011 -0500 @@ -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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/help/environment.txt --- a/mercurial/help/environment.txt Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/help/environment.txt Fri Apr 01 13:25:44 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: diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/help/templates.txt --- a/mercurial/help/templates.txt Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/help/templates.txt Fri Apr 01 13:25:44 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 "
" 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 - `` becomes ``example.com``. - -:email: Any text. Extracts the first string that looks like an email - address. Example: ``User `` 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/hg.py --- a/mercurial/hg.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/hg.py Fri Apr 01 13:25:44 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, diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/hgweb/common.py --- a/mercurial/hgweb/common.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/hgweb/common.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/hgweb/hgweb_mod.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/hgweb/hgwebdir_mod.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/hgweb/protocol.py --- a/mercurial/hgweb/protocol.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/hgweb/protocol.py Fri Apr 01 13:25:44 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: diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/hgweb/server.py --- a/mercurial/hgweb/server.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/hgweb/server.py Fri Apr 01 13:25:44 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, diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/hgweb/wsgicgi.py --- a/mercurial/hgweb/wsgicgi.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/hgweb/wsgicgi.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/httprepo.py --- a/mercurial/httprepo.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/httprepo.py Fri Apr 01 13:25:44 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') diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/i18n.py --- a/mercurial/i18n.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/i18n.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/localrepo.py --- a/mercurial/localrepo.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/localrepo.py Fri Apr 01 13:25:44 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")) @@ -558,6 +559,10 @@ repo = (remote and remote.local()) and remote or self return repo[key].branch() + def known(self, nodes): + nm = self.changelog.nodemap + return [(n in nm) for n in nodes] + def local(self): return True @@ -1014,10 +1019,7 @@ raise # update bookmarks, dirstate and mergestate - parents = (p1, p2) - if p2 == nullid: - parents = (p1,) - bookmarks.update(self, parents, ret) + bookmarks.update(self, p1, ret) for f in changes[0] + changes[1]: self.dirstate.normal(f) for f in changes[2]: @@ -1320,20 +1322,24 @@ def pull(self, remote, heads=None, force=False): lock = self.lock() try: + usecommon = remote.capable('getbundle') tmp = discovery.findcommonincoming(self, remote, heads=heads, - force=force) + force=force, commononly=usecommon) common, fetch, rheads = tmp if not fetch: self.ui.status(_("no changes found\n")) result = 0 else: - if heads is None and fetch == [nullid]: + if heads is None and list(common) == [nullid]: self.ui.status(_("requesting all changes\n")) elif heads is None and remote.capable('changegroupsubset'): # issue1320, avoid a race if remote changed after discovery heads = rheads - if heads is None: + if usecommon: + cg = remote.getbundle('pull', common=common, + heads=heads or rheads) + elif heads is None: cg = remote.changegroup(fetch, 'pull') elif not remote.capable('changegroupsubset'): raise util.Abort(_("partial pull cannot be done because " @@ -1346,27 +1352,6 @@ finally: lock.release() - self.ui.debug("checking for updated bookmarks\n") - rb = remote.listkeys('bookmarks') - changed = False - for k in rb.keys(): - if k in self._bookmarks: - nr, nl = rb[k], self._bookmarks[k] - if nr in self: - cr = self[nr] - cl = self[nl] - if cl.rev() >= cr.rev(): - continue - if cr in cl.descendants(): - self._bookmarks[k] = cr.node() - changed = True - self.ui.status(_("updating bookmark %s\n") % k) - else: - self.ui.warn(_("not updating divergent" - " bookmark %s\n") % k) - if changed: - bookmarks.write(self) - return result def checkpush(self, force, revs): @@ -1446,7 +1431,7 @@ for node in nodes: self.ui.debug("%s\n" % hex(node)) - def changegroupsubset(self, bases, heads, source, extranodes=None): + def changegroupsubset(self, bases, heads, source): """Compute a changegroup consisting of all the nodes that are descendents of any of the bases and ancestors of any of the heads. Return a chunkbuffer object whose read() method will return @@ -1458,214 +1443,127 @@ Another wrinkle is doing the reverse, figuring out which changeset in the changegroup a particular filenode or manifestnode belongs to. - - The caller can specify some nodes that must be included in the - changegroup using the extranodes argument. It should be a dict - where the keys are the filenames (or 1 for the manifest), and the - values are lists of (node, linknode) tuples, where node is a wanted - node and linknode is the changelog node that should be transmitted as - the linkrev. """ - - # Set up some initial variables - # Make it easy to refer to self.changelog cl = self.changelog - # Compute the list of changesets in this changegroup. - # Some bases may turn out to be superfluous, and some heads may be - # too. nodesbetween will return the minimal set of bases and heads - # necessary to re-create the changegroup. if not bases: bases = [nullid] - msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads) + csets, bases, heads = cl.nodesbetween(bases, heads) + # We assume that all ancestors of bases are known + common = set(cl.ancestors(*[cl.rev(n) for n in bases])) + return self._changegroupsubset(common, csets, heads, source) + + def getbundle(self, source, heads=None, common=None): + """Like changegroupsubset, but returns the set difference between the + ancestors of heads and the ancestors common. + + If heads is None, use the local heads. If common is None, use [nullid]. - if extranodes is None: - # can we go through the fast path ? - heads.sort() - allheads = self.heads() - allheads.sort() - if heads == allheads: - return self._changegroup(msng_cl_lst, source) + The nodes in common might not all be known locally due to the way the + current discovery protocol works. + """ + cl = self.changelog + if common: + nm = cl.nodemap + common = [n for n in common if n in nm] + else: + common = [nullid] + if not heads: + heads = cl.heads() + common, missing = cl.findcommonmissing(common, heads) + return self._changegroupsubset(common, missing, heads, source) + + def _changegroupsubset(self, commonrevs, csets, heads, source): + + cl = self.changelog + mf = self.manifest + mfs = {} # needed manifests + fnodes = {} # needed file nodes + changedfiles = set() + fstate = ['', {}] + count = [0] + + # can we go through the fast path ? + heads.sort() + if heads == sorted(self.heads()): + return self._changegroup(csets, source) # slow path self.hook('preoutgoing', throw=True, source=source) - - self.changegroupinfo(msng_cl_lst, source) - - # We assume that all ancestors of bases are known - commonrevs = set(cl.ancestors(*[cl.rev(n) for n in bases])) + self.changegroupinfo(csets, source) - # Make it easy to refer to self.manifest - mnfst = self.manifest - # We don't know which manifests are missing yet - msng_mnfst_set = {} - # Nor do we know which filenodes are missing. - msng_filenode_set = {} - - # A changeset always belongs to itself, so the changenode lookup - # function for a changenode is identity. - def identity(x): - return x + # filter any nodes that claim to be part of the known set + def prune(revlog, missing): + for n in missing: + if revlog.linkrev(revlog.rev(n)) not in commonrevs: + yield n - # A function generating function that sets up the initial environment - # the inner function. - def filenode_collector(changedfiles): - # This gathers information from each manifestnode included in the - # changegroup about which filenodes the manifest node references - # so we can include those in the changegroup too. - # - # It also remembers which changenode each filenode belongs to. It - # does this by assuming the a filenode belongs to the changenode - # the first manifest that references it belongs to. - def collect_msng_filenodes(mnfstnode): - r = mnfst.rev(mnfstnode) - if mnfst.deltaparent(r) in mnfst.parentrevs(r): - # If the previous rev is one of the parents, - # we only need to see a diff. - deltamf = mnfst.readdelta(mnfstnode) - # For each line in the delta - for f, fnode in deltamf.iteritems(): - # And if the file is in the list of files we care - # about. - if f in changedfiles: - # Get the changenode this manifest belongs to - clnode = msng_mnfst_set[mnfstnode] - # Create the set of filenodes for the file if - # there isn't one already. - ndset = msng_filenode_set.setdefault(f, {}) - # And set the filenode's changelog node to the - # manifest's if it hasn't been set already. - ndset.setdefault(fnode, clnode) - else: - # Otherwise we need a full manifest. - m = mnfst.read(mnfstnode) - # For every file in we care about. - for f in changedfiles: - fnode = m.get(f, None) - # If it's in the manifest - if fnode is not None: - # See comments above. - clnode = msng_mnfst_set[mnfstnode] - ndset = msng_filenode_set.setdefault(f, {}) - ndset.setdefault(fnode, clnode) - return collect_msng_filenodes + def lookup(revlog, x): + if revlog == cl: + c = cl.read(x) + changedfiles.update(c[3]) + mfs.setdefault(c[0], x) + count[0] += 1 + self.ui.progress(_('bundling'), count[0], unit=_('changesets')) + return x + elif revlog == mf: + clnode = mfs[x] + mdata = mf.readfast(x) + for f in changedfiles: + if f in mdata: + fnodes.setdefault(f, {}).setdefault(mdata[f], clnode) + count[0] += 1 + self.ui.progress(_('bundling'), count[0], + unit=_('manifests'), total=len(mfs)) + return mfs[x] + else: + self.ui.progress( + _('bundling'), count[0], item=fstate[0], + unit=_('files'), total=len(changedfiles)) + return fstate[1][x] - # If we determine that a particular file or manifest node must be a - # node that the recipient of the changegroup will already have, we can - # also assume the recipient will have all the parents. This function - # prunes them from the set of missing nodes. - def prune(revlog, missingnodes): - hasset = set() - # If a 'missing' filenode thinks it belongs to a changenode we - # assume the recipient must have, then the recipient must have - # that filenode. - for n in missingnodes: - clrev = revlog.linkrev(revlog.rev(n)) - if clrev in commonrevs: - hasset.add(n) - for n in hasset: - missingnodes.pop(n, None) - for r in revlog.ancestors(*[revlog.rev(n) for n in hasset]): - missingnodes.pop(revlog.node(r), None) + bundler = changegroup.bundle10(lookup) - # Add the nodes that were explicitly requested. - def add_extra_nodes(name, nodes): - if not extranodes or name not in extranodes: - return - - for node, linknode in extranodes[name]: - if node not in nodes: - nodes[node] = linknode - - # Now that we have all theses utility functions to help out and - # logically divide up the task, generate the group. def gengroup(): - # The set of changed files starts empty. - changedfiles = set() - collect = changegroup.collector(cl, msng_mnfst_set, changedfiles) - # Create a changenode group generator that will call our functions # back to lookup the owning changenode and collect information. - group = cl.group(msng_cl_lst, identity, collect) - for cnt, chnk in enumerate(group): - yield chnk - # revlog.group yields three entries per node, so - # dividing by 3 gives an approximation of how many - # nodes have been processed. - self.ui.progress(_('bundling'), cnt / 3, - unit=_('changesets')) - changecount = cnt / 3 + for chunk in cl.group(csets, bundler): + yield chunk self.ui.progress(_('bundling'), None) - prune(mnfst, msng_mnfst_set) - add_extra_nodes(1, msng_mnfst_set) - msng_mnfst_lst = msng_mnfst_set.keys() - # Sort the manifestnodes by revision number. - msng_mnfst_lst.sort(key=mnfst.rev) # Create a generator for the manifestnodes that calls our lookup # and data collection functions back. - group = mnfst.group(msng_mnfst_lst, - lambda mnode: msng_mnfst_set[mnode], - filenode_collector(changedfiles)) - efiles = {} - for cnt, chnk in enumerate(group): - if cnt % 3 == 1: - mnode = chnk[:20] - efiles.update(mnfst.readdelta(mnode)) - yield chnk - # see above comment for why we divide by 3 - self.ui.progress(_('bundling'), cnt / 3, - unit=_('manifests'), total=changecount) + count[0] = 0 + for chunk in mf.group(prune(mf, mfs), bundler): + yield chunk self.ui.progress(_('bundling'), None) - efiles = len(efiles) - # These are no longer needed, dereference and toss the memory for - # them. - msng_mnfst_lst = None - msng_mnfst_set.clear() + mfs.clear() - if extranodes: - for fname in extranodes: - if isinstance(fname, int): - continue - msng_filenode_set.setdefault(fname, {}) - changedfiles.add(fname) # Go through all our files in order sorted by name. - for idx, fname in enumerate(sorted(changedfiles)): + count[0] = 0 + for fname in sorted(changedfiles): filerevlog = self.file(fname) if not len(filerevlog): raise util.Abort(_("empty or missing revlog for %s") % fname) - # Toss out the filenodes that the recipient isn't really - # missing. - missingfnodes = msng_filenode_set.pop(fname, {}) - prune(filerevlog, missingfnodes) - add_extra_nodes(fname, missingfnodes) - # If any filenodes are left, generate the group for them, - # otherwise don't bother. - if missingfnodes: - yield changegroup.chunkheader(len(fname)) - yield fname - # Sort the filenodes by their revision # (topological order) - nodeiter = list(missingfnodes) - nodeiter.sort(key=filerevlog.rev) - # Create a group generator and only pass in a changenode - # lookup function as we need to collect no information - # from filenodes. - group = filerevlog.group(nodeiter, - lambda fnode: missingfnodes[fnode]) - for chnk in group: - # even though we print the same progress on - # most loop iterations, put the progress call - # here so that time estimates (if any) can be updated - self.ui.progress( - _('bundling'), idx, item=fname, - unit=_('files'), total=efiles) - yield chnk + fstate[0] = fname + fstate[1] = fnodes.pop(fname, {}) + first = True + + for chunk in filerevlog.group(prune(filerevlog, fstate[1]), + bundler): + if first: + if chunk == bundler.close(): + break + count[0] += 1 + yield bundler.fileheader(fname) + first = False + yield chunk # Signal that no more groups are left. - yield changegroup.closechunk() + yield bundler.close() self.ui.progress(_('bundling'), None) - if msng_cl_lst: - self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source) + if csets: + self.hook('outgoing', node=hex(csets[0]), source=source) return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN') @@ -1683,75 +1581,75 @@ nodes is the set of nodes to send""" - self.hook('preoutgoing', throw=True, source=source) + cl = self.changelog + mf = self.manifest + mfs = {} + changedfiles = set() + fstate = [''] + count = [0] - cl = self.changelog - revset = set([cl.rev(n) for n in nodes]) + self.hook('preoutgoing', throw=True, source=source) self.changegroupinfo(nodes, source) - def identity(x): - return x + revset = set([cl.rev(n) for n in nodes]) def gennodelst(log): for r in log: if log.linkrev(r) in revset: yield log.node(r) - def lookuplinkrev_func(revlog): - def lookuplinkrev(n): - return cl.node(revlog.linkrev(revlog.rev(n))) - return lookuplinkrev + def lookup(revlog, x): + if revlog == cl: + c = cl.read(x) + changedfiles.update(c[3]) + mfs.setdefault(c[0], x) + count[0] += 1 + self.ui.progress(_('bundling'), count[0], unit=_('changesets')) + return x + elif revlog == mf: + count[0] += 1 + self.ui.progress(_('bundling'), count[0], + unit=_('manifests'), total=len(mfs)) + return cl.node(revlog.linkrev(revlog.rev(x))) + else: + self.ui.progress( + _('bundling'), count[0], item=fstate[0], + total=len(changedfiles), unit=_('files')) + return cl.node(revlog.linkrev(revlog.rev(x))) + + bundler = changegroup.bundle10(lookup) def gengroup(): '''yield a sequence of changegroup chunks (strings)''' # construct a list of all changed files - changedfiles = set() - mmfs = {} - collect = changegroup.collector(cl, mmfs, changedfiles) - for cnt, chnk in enumerate(cl.group(nodes, identity, collect)): - # revlog.group yields three entries per node, so - # dividing by 3 gives an approximation of how many - # nodes have been processed. - self.ui.progress(_('bundling'), cnt / 3, unit=_('changesets')) - yield chnk - changecount = cnt / 3 + for chunk in cl.group(nodes, bundler): + yield chunk self.ui.progress(_('bundling'), None) - mnfst = self.manifest - nodeiter = gennodelst(mnfst) - efiles = {} - for cnt, chnk in enumerate(mnfst.group(nodeiter, - lookuplinkrev_func(mnfst))): - if cnt % 3 == 1: - mnode = chnk[:20] - efiles.update(mnfst.readdelta(mnode)) - # see above comment for why we divide by 3 - self.ui.progress(_('bundling'), cnt / 3, - unit=_('manifests'), total=changecount) - yield chnk - efiles = len(efiles) + count[0] = 0 + for chunk in mf.group(gennodelst(mf), bundler): + yield chunk self.ui.progress(_('bundling'), None) - for idx, fname in enumerate(sorted(changedfiles)): + count[0] = 0 + for fname in sorted(changedfiles): filerevlog = self.file(fname) if not len(filerevlog): raise util.Abort(_("empty or missing revlog for %s") % fname) - nodeiter = gennodelst(filerevlog) - nodeiter = list(nodeiter) - if nodeiter: - yield changegroup.chunkheader(len(fname)) - yield fname - lookup = lookuplinkrev_func(filerevlog) - for chnk in filerevlog.group(nodeiter, lookup): - self.ui.progress( - _('bundling'), idx, item=fname, - total=efiles, unit=_('files')) - yield chnk + fstate[0] = fname + first = True + for chunk in filerevlog.group(gennodelst(filerevlog), bundler): + if first: + if chunk == bundler.close(): + break + count[0] += 1 + yield bundler.fileheader(fname) + first = False + yield chunk + yield bundler.close() self.ui.progress(_('bundling'), None) - yield changegroup.closechunk() - if nodes: self.hook('outgoing', node=hex(nodes[0]), source=source) @@ -1915,10 +1813,6 @@ self.hook("incoming", node=hex(cl.node(i)), source=srctype, url=url) - # FIXME - why does this care about tip? - if newheads == oldheads: - bookmarks.update(self, self.dirstate.parents(), self['tip'].node()) - # never return 0 here: if newheads < oldheads: return newheads - oldheads - 1 @@ -2019,6 +1913,10 @@ def listkeys(self, namespace): return pushkey.list(self, namespace) + def debugwireargs(self, one, two, three=None, four=None): + '''used to test argument passing over the wire''' + return "%s %s %s %s" % (one, two, three, four) + # used to avoid circular references so destructors work def aftertrans(files): renamefiles = [tuple(t) for t in files] @@ -2028,7 +1926,7 @@ return a def instance(ui, path, create): - return localrepository(ui, util.drop_scheme('file', path), create) + return localrepository(ui, urlmod.localpath(path), create) def islocal(path): return True diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/manifest.py --- a/mercurial/manifest.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/manifest.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/merge.py --- a/mercurial/merge.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/merge.py Fri Apr 01 13:25:44 2011 -0500 @@ -494,7 +494,6 @@ p1, p2 = pl[0], repo[node] pa = p1.ancestor(p2) fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2) - fastforward = False ### check phase if not overwrite and len(pl) > 1: @@ -504,9 +503,7 @@ raise util.Abort(_("merging with a working directory ancestor" " has no effect")) elif pa == p1: - if p1.branch() != p2.branch(): - fastforward = True - else: + if p1.branch() == p2.branch(): raise util.Abort(_("nothing to merge (use 'hg update'" " or check 'hg heads')")) if not force and (wc.files() or wc.deleted()): @@ -551,7 +548,7 @@ if not partial: repo.dirstate.setparents(fp1, fp2) recordupdates(repo, action, branchmerge) - if not branchmerge and not fastforward: + if not branchmerge: repo.dirstate.setbranch(p2.branch()) finally: wlock.release() diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/osutil.c --- a/mercurial/osutil.c Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/osutil.c Fri Apr 01 13:25:44 2011 -0500 @@ -514,6 +514,22 @@ } #endif +#ifdef __APPLE__ +#include + +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} }; diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/parser.py --- a/mercurial/parser.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/parser.py Fri Apr 01 13:25:44 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): diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/patch.py --- a/mercurial/patch.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/patch.py Fri Apr 01 13:25:44 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 to apply 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)) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/repair.py --- a/mercurial/repair.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/repair.py Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/revlog.py --- a/mercurial/revlog.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/revlog.py Fri Apr 01 13:25:44 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): """ diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/revset.py --- a/mercurial/revset.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/revset.py Fri Apr 01 13:25:44 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, ")")), @@ -298,9 +298,18 @@ return [r for r in subset if r in cs] def branch(repo, subset, x): - """``branch(set)`` - All changesets belonging to the branches of changesets in set. + """``branch(string or set)`` + All changesets belonging to the given branch or the branches of the given + changesets. """ + try: + b = getstring(x, '') + if b in repo.branchmap(): + return [r for r in subset if repo[r].branch() == b] + except error.ParseError: + # not a string, but another revspec, e.g. tip() + pass + s = getset(repo, range(len(repo)), x) b = set() for r in s: @@ -394,7 +403,7 @@ for e in c.files() + [c.user(), c.description()]: if gr.search(e): l.append(r) - continue + break return l def author(repo, subset, x): @@ -423,7 +432,7 @@ for f in repo[r].files(): if m(f): s.append(r) - continue + break return s def contains(repo, subset, x): @@ -438,13 +447,12 @@ for r in subset: if pat in repo[r]: s.append(r) - continue else: for r in subset: for f in repo[r].manifest(): if m(f): s.append(r) - continue + break return s def checkstatus(repo, subset, pat, field): @@ -466,12 +474,11 @@ if fast: if pat in files: s.append(r) - continue else: for f in files: if m(f): s.append(r) - continue + break return s def modifies(repo, subset, x): @@ -683,12 +690,27 @@ for r in bookmarksmod.listbookmarks(repo).values()]) return [r for r in subset if r in bms] +def bisected(repo, subset, x): + """``bisected(string)`` + Changesets marked in the specified bisect state (good, bad, skip). + """ + state = getstring(x, _("bisect requires a string")).lower() + if state not in ('good', 'bad', 'skip', 'unknown'): + raise ParseError(_('invalid bisect state')) + marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state]) + l = [] + for r in subset: + if r in marked: + l.append(r) + return l + symbols = { "adds": adds, "all": getall, "ancestor": ancestor, "ancestors": ancestors, "author": author, + "bisected": bisected, "bookmark": bookmark, "branch": branch, "children": children, @@ -808,26 +830,16 @@ def match(spec): if not spec: raise error.ParseError(_("empty query")) - tree = parse(spec) + tree, pos = parse(spec) + if (pos != len(spec)): + raise error.ParseError("invalid token", pos) weight, tree = optimize(tree, True) def mfunc(repo, subset): return getset(repo, subset, tree) return mfunc def makedoc(topic, doc): - """Generate and include predicates help in revsets topic.""" - predicates = [] - for name in sorted(symbols): - text = symbols[name].__doc__ - if not text: - continue - text = gettext(text.rstrip()) - lines = text.splitlines() - lines[1:] = [(' ' + l.strip()) for l in lines[1:]] - predicates.append('\n'.join(lines)) - predicates = '\n\n'.join(predicates) - doc = doc.replace('.. predicatesmarker', predicates) - return doc + return help.makeitemsdoc(topic, doc, '.. predicatesmarker', symbols) # tell hggettext to extract docstrings from these functions: i18nfunctions = symbols.values() diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/sshrepo.py --- a/mercurial/sshrepo.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/sshrepo.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/sshserver.py --- a/mercurial/sshserver.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/sshserver.py Fri Apr 01 13:25:44 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] diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/statichttprepo.py --- a/mercurial/statichttprepo.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/statichttprepo.py Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/subrepo.py --- a/mercurial/subrepo.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/subrepo.py Fri Apr 01 13:25:44 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') @@ -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): diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/templatefilters.py --- a/mercurial/templatefilters.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/templatefilters.py Fri Apr 01 13:25:44 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 "
" tag before the end of + every line except the last. + """ + return text.replace('\n', '
\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 + `` 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 `` 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', '
\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() diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/templatekw.py --- a/mercurial/templatekw.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/templatekw.py Fri Apr 01 13:25:44 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. @@ -143,32 +143,49 @@ def showauthor(repo, ctx, templ, **args): + """:author: String. The unmodified author of the changeset.""" return ctx.user() def showbranch(**args): + """:branch: String. The name of the branch on which the changeset was + committed. + """ return args['ctx'].branch() def showbranches(**args): + """:branches: List of strings. The name of the branch on which the + changeset was committed. Will be empty if the branch name was + default. + """ branch = args['ctx'].branch() if branch != 'default': return showlist('branch', [branch], plural='branches', **args) def showbookmarks(**args): + """:bookmarks: List of strings. Any bookmarks associated with the + changeset. + """ bookmarks = args['ctx'].bookmarks() return showlist('bookmark', bookmarks, **args) def showchildren(**args): + """:children: List of strings. The children of the changeset.""" ctx = args['ctx'] childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()] return showlist('children', childrevs, **args) def showdate(repo, ctx, templ, **args): + """:date: Date information. The date when the changeset was committed.""" return ctx.date() def showdescription(repo, ctx, templ, **args): + """:desc: String. The text of the changeset description.""" return ctx.description().strip() def showdiffstat(repo, ctx, templ, **args): + """:diffstat: String. Statistics of changes with the following format: + "modified files: +added/-removed lines" + """ files, adds, removes = 0, 0, 0 for i in patch.diffstatdata(util.iterlines(ctx.diff())): files += 1 @@ -184,10 +201,14 @@ yield templ('extra', **args) def showfileadds(**args): + """:file_adds: List of strings. Files added by this changeset.""" repo, ctx, revcache = args['repo'], args['ctx'], args['revcache'] return showlist('file_add', getfiles(repo, ctx, revcache)[1], **args) def showfilecopies(**args): + """:file_copies: List of strings. Files copied in this changeset with + their sources. + """ cache, ctx = args['cache'], args['ctx'] copies = args['revcache'].get('copies') if copies is None: @@ -207,25 +228,37 @@ # provided before calling the templater, usually with a --copies # command line switch. def showfilecopiesswitch(**args): + """:file_copies_switch: List of strings. Like "file_copies" but displayed + only if the --copied switch is set. + """ copies = args['revcache'].get('copies') or [] c = [{'name': x[0], 'source': x[1]} for x in copies] return showlist('file_copy', c, plural='file_copies', **args) def showfiledels(**args): + """:file_dels: List of strings. Files removed by this changeset.""" repo, ctx, revcache = args['repo'], args['ctx'], args['revcache'] return showlist('file_del', getfiles(repo, ctx, revcache)[2], **args) def showfilemods(**args): + """:file_mods: List of strings. Files modified by this changeset.""" repo, ctx, revcache = args['repo'], args['ctx'], args['revcache'] return showlist('file_mod', getfiles(repo, ctx, revcache)[0], **args) def showfiles(**args): + """:files: List of strings. All files modified, added, or removed by this + changeset. + """ return showlist('file', args['ctx'].files(), **args) def showlatesttag(repo, ctx, templ, cache, **args): + """:latesttag: String. Most recent global tag in the ancestors of this + changeset. + """ return getlatesttags(repo, ctx, cache)[2] def showlatesttagdistance(repo, ctx, templ, cache, **args): + """:latesttagdistance: Integer. Longest path to the latest tag.""" return getlatesttags(repo, ctx, cache)[1] def showmanifest(**args): @@ -236,12 +269,17 @@ return templ('manifest', **args) def shownode(repo, ctx, templ, **args): + """:node: String. The changeset identification hash, as a 40 hexadecimal + digit string. + """ return ctx.hex() def showrev(repo, ctx, templ, **args): + """:rev: Integer. The repository-local changeset revision number.""" return ctx.rev() def showtags(**args): + """:tags: List of strings. Any tags associated with the changeset.""" return showlist('tag', args['ctx'].tags(), **args) # keywords are callables like: @@ -276,3 +314,8 @@ 'tags': showtags, } +def makedoc(topic, doc): + return help.makeitemsdoc(topic, doc, '.. keywordsmarker', keywords) + +# tell hggettext to extract docstrings from these functions: +i18nfunctions = keywords.values() diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/templater.py --- a/mercurial/templater.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/templater.py Fri Apr 01 13:25:44 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] diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/templates/coal/map --- a/mercurial/templates/coal/map Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/templates/coal/map Fri Apr 01 13:25:44 2011 -0500 @@ -94,14 +94,12 @@ filerename = '{file|escape}@' filelogrename = ' - - base: - - - {file|escape}@{node|short} - - - ' + + base + + {file|escape}@{node|short} + + ' fileannotateparent = ' parent: diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/templates/paper/filelogentry.tmpl --- a/mercurial/templates/paper/filelogentry.tmpl Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/templates/paper/filelogentry.tmpl Fri Apr 01 13:25:44 2011 -0500 @@ -1,5 +1,5 @@ {date|age} {author|person} - {desc|strip|firstline|escape|nonempty}{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag} + {desc|strip|firstline|escape|nonempty}{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{rename%filelogrename} diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/templates/paper/map --- a/mercurial/templates/paper/map Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/templates/paper/map Fri Apr 01 13:25:44 2011 -0500 @@ -93,14 +93,12 @@ filerename = '{file|escape}@' filelogrename = ' - - base: - - - {file|escape}@{node|short} - - - ' + + base + + {file|escape}@{node|short} + + ' fileannotateparent = ' parent: diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/templates/static/style-coal.css --- a/mercurial/templates/static/style-coal.css Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/templates/static/style-coal.css Fri Apr 01 13:25:44 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;} diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/templates/static/style-paper.css --- a/mercurial/templates/static/style-paper.css Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/templates/static/style-paper.css Fri Apr 01 13:25:44 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; } diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/ui.py --- a/mercurial/ui.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/ui.py Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/url.py --- a/mercurial/url.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/url.py Fri Apr 01 13:25:44 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: + + ://:@:/?# + + 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('ssh://[::1]:2200//home/joe/repo') + + >>> url('file:///home/joe/repo') + + >>> url('bundle:foo') + + >>> url('bundle://../foo') + + >>> url('c:\\\\foo\\\\bar') + + + Authentication credentials: + + >>> url('ssh://joe:xyz@x/repo') + + >>> url('ssh://joe@x/repo') + + + Query strings and fragments: + + >>> url('http://host/a?b#c') + + >>> url('http://host/a?b#c', parsequery=False, parsefragment=False) + + """ + + _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 '' % ', '.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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/util.py --- a/mercurial/util.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/util.py Fri Apr 01 13:25:44 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) @@ -769,7 +772,18 @@ def gui(): '''Are we running in a GUI?''' - return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY") + if sys.platform == 'darwin': + if 'SSH_CONNECTION' in os.environ: + # handle SSH access to a box where the user is logged in + return False + elif getattr(osutil, 'isgui', None): + # check if a CoreGraphics session is available + return osutil.isgui() + else: + # pure build; use a safe default + return True + else: + return os.name == "nt" or os.environ.get("DISPLAY") def mktempcopy(name, emptyok=False, createmode=None): """Create a temporary file with the same contents from name @@ -1204,7 +1218,10 @@ return parsedate(date, extendeddateformats, d)[0] date = date.strip() - if date[0] == "<": + + if not date: + raise Abort(_("dates cannot consist entirely of whitespace")) + elif date[0] == "<": when = upper(date[1:]) return lambda x: x <= when elif date[0] == ">": @@ -1367,26 +1384,6 @@ return format % (nbytes / float(divisor)) return units[-1][2] % nbytes -def drop_scheme(scheme, path): - sc = scheme + ':' - if path.startswith(sc): - path = path[len(sc):] - if path.startswith('//'): - if scheme == 'file': - i = path.find('/', 2) - if i == -1: - return '' - # On Windows, absolute paths are rooted at the current drive - # root. On POSIX they are rooted at the file system root. - if os.name == 'nt': - droot = os.path.splitdrive(os.getcwd())[0] + '/' - path = os.path.join(droot, path[i + 1:]) - else: - path = path[i:] - else: - path = path[2:] - return path - def uirepr(s): # Avoid double backslash in Windows path repr() return repr(s).replace('\\\\', '\\') diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/win32.py --- a/mercurial/win32.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/win32.py Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/windows.py --- a/mercurial/windows.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/windows.py Fri Apr 01 13:25:44 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 @@ -286,53 +286,13 @@ def unlinkpath(f): """unlink and remove the directory if it is empty""" - os.unlink(f) + unlink(f) # try removing directories that might now be empty try: _removedirs(os.path.dirname(f)) except OSError: pass -def unlink(f): - '''try to implement POSIX' unlink semantics on Windows''' - - # POSIX allows to unlink and rename open files. Windows has serious - # problems with doing that: - # - Calling os.unlink (or os.rename) on a file f fails if f or any - # hardlinked copy of f has been opened with Python's open(). There is no - # way such a file can be deleted or renamed on Windows (other than - # scheduling the delete or rename for the next reboot). - # - Calling os.unlink on a file that has been opened with Mercurial's - # posixfile (or comparable methods) will delay the actual deletion of - # the file for as long as the file is held open. The filename is blocked - # during that time and cannot be used for recreating a new file under - # that same name ("zombie file"). Directories containing such zombie files - # cannot be removed or moved. - # A file that has been opened with posixfile can be renamed, so we rename - # f to a random temporary name before calling os.unlink on it. This allows - # callers to recreate f immediately while having other readers do their - # implicit zombie filename blocking on a temporary name. - - for tries in xrange(10): - temp = '%s-%08x' % (f, random.randint(0, 0xffffffff)) - try: - os.rename(f, temp) # raises OSError EEXIST if temp exists - break - except OSError, e: - if e.errno != errno.EEXIST: - raise - else: - raise IOError, (errno.EEXIST, "No usable temporary filename found") - - try: - os.unlink(temp) - except: - # Some very rude AV-scanners on Windows may cause this unlink to fail. - # Not aborting here just leaks the temp file, whereas aborting at this - # point may leave serious inconsistencies. Ideally, we would notify - # the user in this case here. - pass - def rename(src, dst): '''atomically rename file src to dst, replacing dst if it exists''' try: diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 mercurial/wireproto.py --- a/mercurial/wireproto.py Fri Apr 01 13:25:26 2011 -0500 +++ b/mercurial/wireproto.py Fri Apr 01 13:25:44 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'), diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 setup.py --- a/setup.py Fri Apr 01 13:25:26 2011 -0500 +++ b/setup.py Fri Apr 01 13:25:44 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. diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/run-tests.py --- a/tests/run-tests.py Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/run-tests.py Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-586.t --- a/tests/test-586.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-586.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-acl.t --- a/tests/test-acl.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-acl.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-annotate.t --- a/tests/test-annotate.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-annotate.t Fri Apr 01 13:25:44 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] diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-basic.t --- a/tests/test-basic.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-basic.t Fri Apr 01 13:25:44 2011 -0500 @@ -20,6 +20,22 @@ summary: test +Verify that updating to revision 0 via commands.update() works properly + + $ cat < 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-bisect.t --- a/tests/test-bisect.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-bisect.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-bisect2.t --- a/tests/test-bisect2.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-bisect2.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-bookmarks-pushpull.t --- a/tests/test-bookmarks-pushpull.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-bookmarks-pushpull.t Fri Apr 01 13:25:44 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` diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-bookmarks.t --- a/tests/test-bookmarks.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-bookmarks.t Fri Apr 01 13:25:44 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 + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-branch-tag-confict.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-branch-tag-confict.t Fri Apr 01 13:25:44 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. + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-bundle.t --- a/tests/test-bundle.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-bundle.t Fri Apr 01 13:25:44 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%) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-command-template.t --- a/tests/test-command-template.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-command-template.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-commit-multiple.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-commit-multiple.t Fri Apr 01 13:25:44 2011 -0500 @@ -0,0 +1,129 @@ +# reproduce issue2264, issue2516 + +create test repo + $ cat <> $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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-baz --- a/tests/test-convert-baz Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-baz Fri Apr 01 13:25:44 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 " echo "[extensions]" >> $HGRCPATH diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-darcs.t --- a/tests/test-convert-darcs.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-darcs.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-git.t --- a/tests/test-convert-git.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-git.t Fri Apr 01 13:25:44 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 date: Mon Jan 01 00:00:21 2007 +0000 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-hg-startrev.t --- a/tests/test-convert-hg-startrev.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-hg-startrev.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,5 +1,5 @@ - $ cat > $HGRCPATH <> $HGRCPATH < [extensions] > graphlog = > convert = diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-mtn.t --- a/tests/test-convert-mtn.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-mtn.t Fri Apr 01 13:25:44 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) + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-svn-branches.t --- a/tests/test-convert-svn-branches.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-svn-branches.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,7 +1,7 @@ $ "$TESTDIR/hghave" svn svn-bindings || exit 80 - $ cat > $HGRCPATH <> $HGRCPATH < [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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-svn-encoding.t --- a/tests/test-convert-svn-encoding.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-svn-encoding.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,7 +1,7 @@ $ "$TESTDIR/hghave" svn svn-bindings || exit 80 - $ cat > $HGRCPATH <> $HGRCPATH < [extensions] > convert = > graphlog = diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-svn-move.t --- a/tests/test-convert-svn-move.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-svn-move.t Fri Apr 01 13:25:44 2011 -0500 @@ -5,7 +5,7 @@ > { > tr '\\' / > } - $ cat > $HGRCPATH <> $HGRCPATH < [extensions] > convert = > graphlog = diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-svn-sink.t --- a/tests/test-convert-svn-sink.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-svn-sink.t Fri Apr 01 13:25:44 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 <> $HGRCPATH < [extensions] > convert = > graphlog = diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-svn-source.t --- a/tests/test-convert-svn-source.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-svn-source.t Fri Apr 01 13:25:44 2011 -0500 @@ -5,7 +5,7 @@ > { > tr '\\' / > } - $ cat > $HGRCPATH <> $HGRCPATH < [extensions] > convert = > graphlog = diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-svn-startrev.t --- a/tests/test-convert-svn-startrev.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-svn-startrev.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,7 +1,7 @@ $ "$TESTDIR/hghave" svn svn-bindings || exit 80 - $ cat > $HGRCPATH <> $HGRCPATH < [extensions] > convert = > graphlog = diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-svn-tags.t --- a/tests/test-convert-svn-tags.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-svn-tags.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,7 +1,7 @@ $ "$TESTDIR/hghave" svn svn-bindings || exit 80 - $ cat > $HGRCPATH <> $HGRCPATH < [extensions] > convert = > graphlog = diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-tagsbranch-topology.t --- a/tests/test-convert-tagsbranch-topology.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-tagsbranch-topology.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-convert-tla.t --- a/tests/test-convert-tla.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-convert-tla.t Fri Apr 01 13:25:44 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 " $ echo "[extensions]" >> $HGRCPATH $ echo "convert=" >> $HGRCPATH diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-debugbundle.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-debugbundle.t Fri Apr 01 13:25:44 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 + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-debugcomplete.t --- a/tests/test-debugcomplete.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-debugcomplete.t Fri Apr 01 13:25:44 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: diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-diff-color.t --- a/tests/test-diff-color.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-diff-color.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-eol-add.t --- a/tests/test-eol-add.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-eol-add.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,6 +1,6 @@ Test adding .hgeol - $ cat > $HGRCPATH <> $HGRCPATH < [diff] > git = 1 > EOF diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-eol-clone.t --- a/tests/test-eol-clone.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-eol-clone.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,9 +1,6 @@ Testing cloning with the EOL extension - $ cat > $HGRCPATH < [diff] - > git = True - > + $ cat >> $HGRCPATH < [extensions] > eol = > diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-eol-hook.t --- a/tests/test-eol-hook.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-eol-hook.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,14 +1,7 @@ Test the EOL hook - $ cat > $HGRCPATH < [diff] - > git = True - > EOF $ hg init main $ cat > main/.hg/hgrc < [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 < [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 < [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] diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-eol-patch.t --- a/tests/test-eol-patch.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-eol-patch.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,6 +1,6 @@ Test EOL patching - $ cat > $HGRCPATH <> $HGRCPATH < [diff] > git = 1 > EOF diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-eol-tag.t --- a/tests/test-eol-tag.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-eol-tag.t Fri Apr 01 13:25:44 2011 -0500 @@ -2,10 +2,7 @@ Testing tagging with the EOL extension - $ cat > $HGRCPATH < [diff] - > git = True - > + $ cat >> $HGRCPATH < [extensions] > eol = > diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-eol-update.t --- a/tests/test-eol-update.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-eol-update.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,6 +1,6 @@ Test EOL update - $ cat > $HGRCPATH <> $HGRCPATH < [diff] > git = 1 > EOF diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-eol.t --- a/tests/test-eol.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-eol.t Fri Apr 01 13:25:44 2011 -0500 @@ -1,6 +1,6 @@ Test EOL extension - $ cat > $HGRCPATH <> $HGRCPATH < [diff] > git = True > EOF diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-extdiff.t --- a/tests/test-extdiff.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-extdiff.t Fri Apr 01 13:25:44 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] diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-getbundle.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-getbundle.t Fri Apr 01 13:25:44 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 + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-globalopts.t --- a/tests/test-globalopts.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-globalopts.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-help.t --- a/tests/test-help.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-help.t Fri Apr 01 13:25:44 2011 -0500 @@ -765,6 +765,14 @@ working directory is checked out, it is equivalent to null. If an uncommitted merge is in progress, "." is the revision of the first parent. +Test templating help + + $ hg help templating | egrep '(desc|diffstat|firstline|nonempty) ' + desc String. The text of the changeset description. + diffstat String. Statistics of changes with the following format: + firstline Any text. Returns the first line of text. + nonempty Any text. Returns '(none)' if the string is empty. + Test help hooks $ cat > helphook1.py < plain.py < 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-hgweb-commands.t --- a/tests/test-hgweb-commands.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-hgweb-commands.t Fri Apr 01 13:25:44 2011 -0500 @@ -905,7 +905,7 @@ $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'; echo 200 Script output follows - lookup changegroupsubset branchmap pushkey unbundle=HG10GZ,HG10BZ,HG10UN + lookup changegroupsubset branchmap pushkey known getbundle unbundle=HG10GZ,HG10BZ,HG10UN heads diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-http-branchmap.t --- a/tests/test-http-branchmap.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-http-branchmap.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-http-clone-r.t --- a/tests/test-http-clone-r.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-http-clone-r.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-http-proxy.t --- a/tests/test-http-proxy.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-http-proxy.t Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-https.t --- a/tests/test-https.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-https.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-identify.t --- a/tests/test-identify.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-identify.t Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-init.t --- a/tests/test-init.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-init.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-install.t --- a/tests/test-install.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-install.t Fri Apr 01 13:25:44 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") diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-issue1877.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-issue1877.t Fri Apr 01 13:25:44 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 + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-keyword.t --- a/tests/test-keyword.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-keyword.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-known.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-known.t Fri Apr 01 13:25:44 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 + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-log.t --- a/tests/test-log.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-log.t Fri Apr 01 13:25:44 2011 -0500 @@ -512,7 +512,11 @@ date: Thu Jan 01 00:00:01 1970 +0000 summary: r1 +log -d " " (whitespaces only) + $ hg log -d " " + abort: dates cannot consist entirely of whitespace + [255] log -d -1 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-mq-strip.t --- a/tests/test-mq-strip.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-mq-strip.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-paths.t --- a/tests/test-paths.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-paths.t Fri Apr 01 13:25:44 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 < [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 + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-pull-branch.t --- a/tests/test-pull-branch.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-pull-branch.t Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-pull.t --- a/tests/test-pull.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-pull.t Fri Apr 01 13:25:44 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" diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-rebase-collapse.t --- a/tests/test-rebase-collapse.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-rebase-collapse.t Fri Apr 01 13:25:44 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: diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-rebase-mq.t --- a/tests/test-rebase-mq.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-rebase-mq.t Fri Apr 01 13:25:44 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) + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-rebase-named-branches.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rebase-named-branches.t Fri Apr 01 13:25:44 2011 -0500 @@ -0,0 +1,171 @@ + $ cat >> $HGRCPATH < [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 .. + + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-rebase-rename.t --- a/tests/test-rebase-rename.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-rebase-rename.t Fri Apr 01 13:25:44 2011 -0500 @@ -119,3 +119,52 @@ copy from a copy to a-copied + $ cd .. + + +Test rebase across repeating renames: + + $ hg init repo + + $ cd repo + + $ echo testing > file1.txt + $ hg add file1.txt + $ hg ci -m "Adding file1" + + $ hg rename file1.txt file2.txt + $ hg ci -m "Rename file1 to file2" + + $ echo Unrelated change > unrelated.txt + $ hg add unrelated.txt + $ hg ci -m "Unrelated change" + + $ hg rename file2.txt file1.txt + $ hg ci -m "Rename file2 back to file1" + + $ hg update -r -2 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + + $ echo Another unrelated change >> unrelated.txt + $ hg ci -m "Another unrelated change" + created new head + + $ hg tglog + @ 4: 'Another unrelated change' + | + | o 3: 'Rename file2 back to file1' + |/ + o 2: 'Unrelated change' + | + o 1: 'Rename file1 to file2' + | + o 0: 'Adding file1' + + + $ hg rebase -s 4 -d 3 + saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-backup.hg (glob) + + $ hg diff --stat -c . + unrelated.txt | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-relink.t --- a/tests/test-relink.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-relink.t Fri Apr 01 13:25:44 2011 -0500 @@ -20,23 +20,29 @@ $ hg init repo $ cd repo - $ echo '[ui]' > .hg/hgrc - $ echo 'username= A. Foo ' >> .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 ' >> .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 .. diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-rename-merge1.t --- a/tests/test-rename-merge1.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-rename-merge1.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-revset-outgoing.t --- a/tests/test-revset-outgoing.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-revset-outgoing.t Fri Apr 01 13:25:44 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' diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-revset.t --- a/tests/test-revset.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-revset.t Fri Apr 01 13:25:44 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] + diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-schemes.t --- a/tests/test-schemes.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-schemes.t Fri Apr 01 13:25:44 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] diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-serve --- a/tests/test-serve Fri Apr 01 13:25:26 2011 -0500 +++ /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/ diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-serve.out --- a/tests/test-serve.out Fri Apr 01 13:25:26 2011 -0500 +++ /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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-serve.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-serve.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-ssh-clone-r.t --- a/tests/test-ssh-clone-r.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-ssh-clone-r.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-ssh.t --- a/tests/test-ssh.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-ssh.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-subrepo-git.t --- a/tests/test-subrepo-git.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-subrepo-git.t Fri Apr 01 13:25:44 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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-subrepo-paths.t --- a/tests/test-subrepo-paths.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-subrepo-paths.t Fri Apr 01 13:25:44 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 <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) diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-unrelated-pull.t --- a/tests/test-unrelated-pull.t Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-unrelated-pull.t Fri Apr 01 13:25:44 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 diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-url.py --- a/tests/test-url.py Fri Apr 01 13:25:26 2011 -0500 +++ b/tests/test-url.py Fri Apr 01 13:25:44 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('http://host/a?') + + >>> url('http://host/a#b#c') + + >>> url('http://host/a#b?c') + + >>> url('http://host/?a#b') + + >>> url('http://host/?a#b', parsequery=False) + + >>> url('http://host/?a#b', parsefragment=False) + + >>> url('http://host/?a#b', parsequery=False, parsefragment=False) + + + IPv6 addresses: + + >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one') + + >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one') + + + Missing scheme, host, etc.: + + >>> url('://192.0.2.16:80/') + + >>> url('http://mercurial.selenic.com') + + >>> url('/foo') + + >>> url('bundle:/foo') + + >>> url('a?b#c') + + >>> url('http://x.com?arg=/foo') + + >>> url('http://joe:xxx@/foo') + + + Just a scheme and a path: + + >>> url('mailto:John.Doe@example.com') + + >>> url('a:b:c:d') + + >>> url('aa:bb:cc:dd') + + + SSH examples: + + >>> url('ssh://joe@host//home/joe') + + >>> url('ssh://joe:xxx@host/src') + + >>> url('ssh://joe:xxx@host') + + >>> url('ssh://joe@host') + + >>> url('ssh://host') + + >>> url('ssh://') + + >>> url('ssh:') + + + Non-numeric port: + + >>> url('http://example.com:dd') + + >>> url('ssh://joe:xxx@host:ssh/foo') + + + Bad authentication credentials: + + >>> url('http://joe@joeville:123@4:@host/a?b#c') + + >>> url('http://!*#?/@!*#?/:@host/a?b#c') + + >>> url('http://!*#?@!*#?:@host/a?b#c') + + >>> url('http://!*@:!*@@host/a?b#c') + + + File paths: + + >>> url('a/b/c/d.g.f') + + >>> url('/x///z/y/') + + >>> url('/foo:bar') + + >>> url('\\\\foo:bar') + + >>> url('./foo:bar') + + + Non-localhost file URL: + + >>> u = url('file://mercurial.selenic.com/foo') + Traceback (most recent call last): + File "", line 1, in ? + Abort: file:// URLs can only refer to localhost + + Empty URL: + + >>> u = url('') + >>> u + + >>> 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 + + >>> 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') diff -r f911b1de5fa0 -r 3cbf5c6dcdb8 tests/test-wireproto.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-wireproto.t Fri Apr 01 13:25:44 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 < 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 +