changeset 2219:ec82cff7d2c4

merge with crew.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Mon, 08 May 2006 08:04:46 -0700
parents afe24f5b7a9e (current diff) f72c438ce08c (diff)
children 6d3cc2a982f3
files hgext/bugzilla.py tests/run-tests
diffstat 25 files changed, 540 insertions(+), 306 deletions(-) [+]
line wrap: on
line diff
--- a/MANIFEST.in	Wed May 03 14:56:07 2006 -0700
+++ b/MANIFEST.in	Mon May 08 08:04:46 2006 -0700
@@ -2,7 +2,7 @@
 recursive-include mercurial *.py
 include hgweb.cgi hgwebdir.cgi
 include hgeditor rewrite-log
-include tests/README tests/run-tests tests/md5sum.py tests/test-*[a-z0-9] tests/*.out
+include tests/README tests/coverage.py tests/run-tests.py tests/md5sum.py tests/test-*[a-z0-9] tests/*.out
 prune tests/*.err
 include *.txt
 include templates/map templates/map-*[a-z0-9]
--- a/Makefile	Wed May 03 14:56:07 2006 -0700
+++ b/Makefile	Mon May 08 08:04:46 2006 -0700
@@ -16,10 +16,10 @@
 	TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py sdist --force-manifest
 
 tests:
-	cd tests && ./run-tests
+	cd tests && $(PYTHON) run-tests.py
 
 test-%:
-	cd tests && ./run-tests $@
+	cd tests && $(PYTHON) run-tests.py $@
 
 doc:
 	$(MAKE) -C doc
--- a/README	Wed May 03 14:56:07 2006 -0700
+++ b/README	Mon May 08 08:04:46 2006 -0700
@@ -11,13 +11,16 @@
  $ tar xvzf mercurial-<ver>.tar.gz
  $ cd mercurial-<ver>
 
+ When installing, change python to python2.3 or python2.4 if 2.2 is the
+ default on your system.
+
  To install system-wide:
 
- $ python setup.py install   # change python to python2.3 if 2.2 is default
+ $ python setup.py install --force
 
  To install in your home directory (~/bin and ~/lib, actually), run:
 
- $ python2.3 setup.py install --home=~
+ $ python setup.py install --home=${HOME} --force
  $ export PYTHONPATH=${HOME}/lib/python  # (or lib64/ on some systems)
  $ export PATH=${HOME}/bin:$PATH         # add these to your .bashrc
 
@@ -30,10 +33,12 @@
 
 Setting up a Mercurial project:
 
- $ cd project/
- $ hg init         # creates .hg
- $ hg addremove    # add all unknown files and remove all missing files
- $ hg commit       # commit all changes, edit changelog entry
+ $ hg init project     # creates project directory
+ $ cd project
+                       # copy files in, edit them
+ $ hg add              # add all unknown files
+ $ hg remove --after   # remove deleted files
+ $ hg commit           # commit all changes, edit changelog entry
 
  Mercurial will look for a file named .hgignore in the root of your
  repository which contains a set of regular expressions to ignore in
@@ -47,7 +52,7 @@
  $ hg commit
  $ cd ../linux
  $ hg pull ../linux-work     # pull changesets from linux-work
- $ hg update -m              # merge the new tip from linux-work into
+ $ hg merge                  # merge the new tip from linux-work into
                              # our working directory
  $ hg commit                 # commit the result of the merge
 
@@ -55,8 +60,7 @@
 
  Fast:
  $ patch < ../p/foo.patch
- $ hg addremove
- $ hg commit
+ $ hg commit -A
 
  Faster:
  $ patch < ../p/foo.patch
@@ -87,7 +91,7 @@
 
  # merge changes from a remote machine
  bar$ hg pull http://foo/
- bar$ hg update -m        # merge changes into your working directory
+ bar$ hg merge   # merge changes into your working directory
 
  # Set up a CGI server on your webserver
  foo$ cp hgweb.cgi ~/public_html/hg/index.cgi
--- a/contrib/win32/ReadMe.html	Wed May 03 14:56:07 2006 -0700
+++ b/contrib/win32/ReadMe.html	Mon May 08 08:04:46 2006 -0700
@@ -14,7 +14,7 @@
   </head>
 
   <body>
-    <h1>Mercurial version 0.8 for Windows</h1>
+    <h1>Mercurial version 0.8.1 for Windows</h1>
 
     <p>Welcome to Mercurial for Windows!</p>
 
@@ -117,7 +117,7 @@
 	href="http://www.serpentine.com/blog">Bryan
 	O'Sullivan</a>.</p>
 
-    <p>Mercurial is Copyright 2005 Matt Mackall and others.  See the
+    <p>Mercurial is Copyright 2005, 2006 Matt Mackall and others.  See the
       <tt>Contributors.txt</tt> file for a list of contributors.</p>
 
     <p>Mercurial is free software; you can redistribute it and/or
--- a/contrib/win32/mercurial.iss	Wed May 03 14:56:07 2006 -0700
+++ b/contrib/win32/mercurial.iss	Mon May 08 08:04:46 2006 -0700
@@ -2,9 +2,9 @@
 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
 
 [Setup]
-AppCopyright=Copyright 2005 Matt Mackall and others
+AppCopyright=Copyright 2005, 2006 Matt Mackall and others
 AppName=Mercurial
-AppVerName=Mercurial version 0.8
+AppVerName=Mercurial version 0.8.1
 InfoAfterFile=contrib/win32/postinstall.txt
 LicenseFile=COPYING
 ShowLanguageDialog=yes
@@ -14,12 +14,12 @@
 AppUpdatesURL=http://www.selenic.com/mercurial
 AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}
 AppContact=mercurial@selenic.com
-OutputBaseFilename=Mercurial-0.8
+OutputBaseFilename=Mercurial-0.8.1
 DefaultDirName={sd}\Mercurial
 SourceDir=C:\hg\hg-release
-VersionInfoVersion=0.8
+VersionInfoVersion=0.8.1
 VersionInfoDescription=Mercurial distributed SCM
-VersionInfoCopyright=Copyright 2005 Matt Mackall and others
+VersionInfoCopyright=Copyright 2005, 2006 Matt Mackall and others
 VersionInfoCompany=Matt Mackall and others
 InternalCompressLevel=max
 SolidCompression=true
--- a/contrib/win32/postinstall.txt	Wed May 03 14:56:07 2006 -0700
+++ b/contrib/win32/postinstall.txt	Mon May 08 08:04:46 2006 -0700
@@ -8,6 +8,22 @@
 Release Notes
 -------------
 
+2006-04-07  v0.8.1
+
+* Major changes from 0.8 to 0.8.1:
+
+  - new extensions:
+    mq (manage a queue of patches, like quilt only better)
+    email (send changes as series of email patches)
+  - new command: merge (replaces "update -m")
+  - improved commands: log (--limit option added), pull/push ("-r" works
+    on specific revisions), revert (rewritten, much better)
+  - comprehensive hook support
+  - output templating added, supporting e.g. GNU changelog style
+  - Windows, Mac OS X: prebuilt binary packages, better support
+  - many reliability, performance, and memory usage improvements
+
+
 2006-01-29  v0.8 
 
 * Upgrade notes:
--- a/doc/hg.1.txt	Wed May 03 14:56:07 2006 -0700
+++ b/doc/hg.1.txt	Mon May 08 08:04:46 2006 -0700
@@ -180,7 +180,7 @@
 -----
  .hgignore::
     This file contains regular expressions (one per line) that describe file
-    names that should be ignored by hg.
+    names that should be ignored by hg. For details, see hgignore(5).
 
  .hgtags::
     This file contains changeset hash values and text tag names (one of each
@@ -200,7 +200,7 @@
 
 SEE ALSO
 --------
-hgrc(5)
+hgignore(5), hgrc(5)
 
 AUTHOR
 ------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/hgignore.5.txt	Mon May 08 08:04:46 2006 -0700
@@ -0,0 +1,92 @@
+HGIGNORE(5)
+===========
+Vadim Gelfer <vadim.gelfer@gmail.com>
+
+NAME
+----
+hgignore - syntax for Mercurial ignore files
+
+SYNOPSIS
+--------
+
+The Mercurial system uses a file called .hgignore in the root
+directory of a repository to control its behavior when it finds files
+that it is not currently managing.
+
+DESCRIPTION
+-----------
+
+Mercurial ignores every unmanaged file that matches any pattern in an
+ignore file.  The patterns in an ignore file do not apply to files
+managed by Mercurial.  To control Mercurial's handling of files that
+it manages, see the hg(1) man page.  Look for the "-I" and "-X"
+options.
+
+In addition, a Mercurial configuration file can point to a set of
+per-user or global ignore files.  See the hgrc(5) man page for details
+of how to configure these files.  Look for the "ignore" entry in the
+"ui" section.
+
+SYNTAX
+------
+
+An ignore file is a plain text file consisting of a list of patterns,
+with one pattern per line.  Empty lines are skipped.  The "#"
+character is treated as a comment character, and the "\" character is
+treated as an escape character.
+
+Mercurial supports several pattern syntaxes.  The default syntax used
+is Python/Perl-style regular expressions.
+
+To change the syntax used, use a line of the following form:
+
+syntax: NAME
+
+where NAME is one of the following:
+
+regexp::
+  Regular expression, Python/Perl syntax.
+glob::
+  Shell-style glob.
+
+The chosen syntax stays in effect when parsing all patterns that
+follow, until another syntax is selected.
+
+Neither glob nor regexp patterns are rooted.  A glob-syntax pattern of
+the form "*.c" will match a file ending in ".c" in any directory, and
+a regexp pattern of the form "\.c$" will do the same.  To root a
+regexp pattern, start it with "^".
+
+EXAMPLE
+-------
+
+Here is an example ignore file.
+
+  # use glob syntax.
+  syntax: glob
+
+  *.elc
+  *.pyc
+  *~
+  .*.swp
+
+  # switch to regexp syntax.
+  syntax: regexp
+  ^\.pc/
+
+AUTHOR
+------
+Vadim Gelfer <vadim.gelfer@gmail.com>
+
+Mercurial was written by Matt Mackall <mpm@selenic.com>.
+
+SEE ALSO
+--------
+hg(1), hgrc(5)
+
+COPYING
+-------
+This manual page is copyright 2006 Vadim Gelfer.
+Mercurial is copyright 2005, 2006 Matt Mackall.
+Free use of this software is granted under the terms of the GNU General
+Public License (GPL).
--- a/doc/hgrc.5.txt	Wed May 03 14:56:07 2006 -0700
+++ b/doc/hgrc.5.txt	Mon May 08 08:04:46 2006 -0700
@@ -130,6 +130,24 @@
     # them to the working dir
     **.txt = tempfile: unix2dos -n INFILE OUTFILE
 
+email::
+  Settings for extensions that send email messages.
+  from;;
+    Optional.  Email address to use in "From" header and SMTP envelope
+    of outgoing messages.
+
+extensions::
+  Mercurial has an extension mechanism for adding new features. To
+  enable an extension, create an entry for it in this section.
+
+  If you know that the extension is already in Python's search path,
+  you can give the name of the module, followed by "=", with nothing
+  after the "=".
+
+  Otherwise, give a name that you choose, followed by "=", followed by
+  the path to the ".py" file (including the file name extension) that
+  defines the extension.
+
 hooks::
   Commands or Python functions that get automatically executed by
   various actions such as starting or finishing a commit. Multiple
@@ -240,6 +258,24 @@
   user;;
     Optional.  User name to authenticate with at the proxy server.
 
+smtp::
+  Configuration for extensions that need to send email messages.
+  host;;
+    Optional.  Host name of mail server.  Default: "mail".
+  port;;
+    Optional.  Port to connect to on mail server.  Default: 25.
+  tls;;
+    Optional.  Whether to connect to mail server using TLS.  True or
+    False.  Default: False.
+  username;;
+    Optional.  User name to authenticate to SMTP server with.
+    If username is specified, password must also be specified.
+    Default: none.
+  password;;
+    Optional.  Password to authenticate to SMTP server with.
+    If username is specified, password must also be specified.
+    Default: none.
+
 paths::
   Assigns symbolic names to repositories.  The left side is the
   symbolic name, and the right gives the directory or URL that is the
@@ -256,7 +292,8 @@
     the same format as a repository-wide .hgignore file. This option
     supports hook syntax, so if you want to specify multiple ignore
     files, you can do so by setting something like
-    "ignore.other = ~/.hgignore2".
+    "ignore.other = ~/.hgignore2". For details of the ignore file
+    format, see the hgignore(5) man page.
   interactive;;
     Allow to prompt the user.  True or False.  Default is True.
   logtemplate;;
@@ -300,6 +337,10 @@
   allowzip;;
     Whether to allow .zip downloading of repo revisions. Default is false.
     This feature creates temporary files.
+  baseurl;;
+    Base URL to use when publishing URLs in other locations, so
+    third-party tools like email notification hooks can construct URLs.
+    Example: "http://hgserver/repos/"
   description;;
     Textual description of the repository's purpose or contents.
     Default is "unknown".
@@ -330,11 +371,11 @@
 
 SEE ALSO
 --------
-hg(1)
+hg(1), hgignore(5)
 
 COPYING
 -------
 This manual page is copyright 2005 Bryan O'Sullivan.
-Mercurial is copyright 2005 Matt Mackall.
+Mercurial is copyright 2005, 2006 Matt Mackall.
 Free use of this software is granted under the terms of the GNU General
 Public License (GPL).
--- a/hgext/bugzilla.py	Wed May 03 14:56:07 2006 -0700
+++ b/hgext/bugzilla.py	Mon May 08 08:04:46 2006 -0700
@@ -30,7 +30,6 @@
 # OPTIONAL:
 #   bzuser = ...    # bugzilla user id to record comments with
 #   db = bugs       # database to connect to
-#   hgweb = http:// # root of hg web site for browsing commits
 #   notify = ...    # command to run to get bugzilla to send mail
 #   regexp = ...    # regexp to match bug ids (must contain one "()" group)
 #   strip = 0       # number of slashes to strip for url paths
@@ -38,11 +37,13 @@
 #   template = ...  # template to use when formatting comments
 #   timeout = 5     # database connection timeout (seconds)
 #   user = bugs     # user to connect to database as
+#   [web]
+#   baseurl = http://hgserver/... # root of hg web site for browsing commits
 
 from mercurial.demandload import *
 from mercurial.i18n import gettext as _
 from mercurial.node import *
-demandload(globals(), 'cStringIO mercurial:templater,util os re time')
+demandload(globals(), 'mercurial:templater,util os re time')
 
 MySQLdb = None
 
@@ -237,23 +238,9 @@
                 count -= 1
             return root
 
-        class stringio(object):
-            '''wrap cStringIO.'''
-            def __init__(self):
-                self.fp = cStringIO.StringIO()
-
-            def write(self, *args):
-                for a in args:
-                    self.fp.write(a)
-
-            write_header = write
-
-            def getvalue(self):
-                return self.fp.getvalue()
-
         mapfile = self.ui.config('bugzilla', 'style')
         tmpl = self.ui.config('bugzilla', 'template')
-        sio = stringio()
+        sio = templater.stringio()
         t = templater.changeset_templater(self.ui, self.repo, mapfile, sio)
         if not mapfile and not tmpl:
             tmpl = _('changeset {node|short} in repo {root} refers '
@@ -263,7 +250,7 @@
             t.use_template(tmpl)
         t.show(changenode=node, changes=changes,
                bug=str(bugid),
-               hgweb=self.ui.config('bugzilla', 'hgweb'),
+               hgweb=self.ui.config('web', 'baseurl'),
                root=self.repo.root,
                webroot=webroot(self.repo.root))
         self.add_comment(bugid, sio.getvalue(), templater.email(changes[1]))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/notify.py	Mon May 08 08:04:46 2006 -0700
@@ -0,0 +1,258 @@
+# notify.py - email notifications for mercurial
+#
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+# hook extension to email notifications to people when changesets are
+# committed to a repo they subscribe to.
+#
+# default mode is to print messages to stdout, for testing and
+# configuring.
+#
+# to use, configure notify extension and enable in hgrc like this:
+#
+#   [extensions]
+#   hgext.notify =
+#
+#   [hooks]
+#   # one email for each incoming changeset
+#   incoming.notify = python:hgext.notify.hook
+#   # batch emails when many changesets incoming at one time
+#   changegroup.notify = python:hgext.notify.hook
+#
+#   [notify]
+#   # config items go in here
+#
+# config items:
+#
+# REQUIRED:
+#   config = /path/to/file # file containing subscriptions
+#
+# OPTIONAL:
+#   test = True            # print messages to stdout for testing
+#   strip = 3              # number of slashes to strip for url paths
+#   domain = example.com   # domain to use if committer missing domain
+#   style = ...            # style file to use when formatting email
+#   template = ...         # template to use when formatting email
+#   incoming = ...         # template to use when run as incoming hook
+#   changegroup = ...      # template when run as changegroup hook
+#   maxdiff = 300          # max lines of diffs to include (0=none, -1=all)
+#   maxsubject = 67        # truncate subject line longer than this
+#   [email]
+#   from = user@host.com   # email address to send as if none given
+#   [web]
+#   baseurl = http://hgserver/... # root of hg web site for browsing commits
+#
+# notify config file has same format as regular hgrc. it has two
+# sections so you can express subscriptions in whatever way is handier
+# for you.
+#
+#   [usersubs]
+#   # key is subscriber email, value is ","-separated list of glob patterns
+#   user@host = pattern
+#
+#   [reposubs]
+#   # key is glob pattern, value is ","-separated list of subscriber emails
+#   pattern = user@host
+#
+# glob patterns are matched against path to repo root.
+#
+# if you like, you can put notify config file in repo that users can
+# push changes to, they can manage their own subscriptions.
+
+from mercurial.demandload import *
+from mercurial.i18n import gettext as _
+from mercurial.node import *
+demandload(globals(), 'email.Parser mercurial:commands,templater,util')
+demandload(globals(), 'fnmatch socket time')
+
+# template for single changeset can include email headers.
+single_template = '''
+Subject: changeset in {webroot}: {desc|firstline|strip}
+From: {author}
+
+changeset {node|short} in {root}
+details: {baseurl}{webroot}?cmd=changeset;node={node|short}
+description:
+\t{desc|tabindent|strip}
+'''.lstrip()
+
+# template for multiple changesets should not contain email headers,
+# because only first set of headers will be used and result will look
+# strange.
+multiple_template = '''
+changeset {node|short} in {root}
+details: {baseurl}{webroot}?cmd=changeset;node={node|short}
+summary: {desc|firstline}
+'''
+
+deftemplates = {
+    'changegroup': multiple_template,
+    }
+
+class notifier(object):
+    '''email notification class.'''
+
+    def __init__(self, ui, repo, hooktype):
+        self.ui = ui
+        self.ui.readconfig(self.ui.config('notify', 'config'))
+        self.repo = repo
+        self.stripcount = int(self.ui.config('notify', 'strip', 0))
+        self.root = self.strip(self.repo.root)
+        self.domain = self.ui.config('notify', 'domain')
+        self.sio = templater.stringio()
+        self.subs = self.subscribers()
+
+        mapfile = self.ui.config('notify', 'style')
+        template = (self.ui.config('notify', hooktype) or
+                    self.ui.config('notify', 'template'))
+        self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
+                                               self.sio)
+        if not mapfile and not template:
+            template = deftemplates.get(hooktype) or single_template
+        if template:
+            template = templater.parsestring(template, quoted=False)
+            self.t.use_template(template)
+
+    def strip(self, path):
+        '''strip leading slashes from local path, turn into web-safe path.'''
+
+        path = util.pconvert(path)
+        count = self.stripcount
+        while path and count >= 0:
+            c = path.find('/')
+            if c == -1:
+                break
+            path = path[c+1:]
+            count -= 1
+        return path
+
+    def fixmail(self, addr):
+        '''try to clean up email addresses.'''
+
+        addr = templater.email(addr.strip())
+        a = addr.find('@localhost')
+        if a != -1:
+            addr = addr[:a]
+        if '@' not in addr:
+            return addr + '@' + self.domain
+        return addr
+
+    def subscribers(self):
+        '''return list of email addresses of subscribers to this repo.'''
+
+        subs = {}
+        for user, pats in self.ui.configitems('usersubs'):
+            for pat in pats.split(','):
+                if fnmatch.fnmatch(self.repo.root, pat.strip()):
+                    subs[self.fixmail(user)] = 1
+        for pat, users in self.ui.configitems('reposubs'):
+            if fnmatch.fnmatch(self.repo.root, pat):
+                for user in users.split(','):
+                    subs[self.fixmail(user)] = 1
+        subs = subs.keys()
+        subs.sort()
+        return subs
+
+    def url(self, path=None):
+        return self.ui.config('web', 'baseurl') + (path or self.root)
+
+    def node(self, node):
+        '''format one changeset.'''
+
+        self.t.show(changenode=node, changes=self.repo.changelog.read(node),
+                    baseurl=self.ui.config('web', 'baseurl'),
+                    root=self.repo.root,
+                    webroot=self.root)
+
+    def send(self, node, count):
+        '''send message.'''
+
+        p = email.Parser.Parser()
+        self.sio.seek(0)
+        msg = p.parse(self.sio)
+
+        def fix_subject():
+            '''try to make subject line exist and be useful.'''
+
+            subject = msg['Subject']
+            if not subject:
+                if count > 1:
+                    subject = _('%s: %d new changesets') % (self.root, count)
+                else:
+                    changes = self.repo.changelog.read(node)
+                    s = changes[4].lstrip().split('\n', 1)[0].rstrip()
+                    subject = '%s: %s' % (self.root, s)
+            maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
+            if maxsubject and len(subject) > maxsubject:
+                subject = subject[:maxsubject-3] + '...'
+            del msg['Subject']
+            msg['Subject'] = subject
+
+        def fix_sender():
+            '''try to make message have proper sender.'''
+
+            sender = msg['From']
+            if not sender:
+                sender = self.ui.config('email', 'from') or self.ui.username()
+            if '@' not in sender or '@localhost' in sender:
+                sender = self.fixmail(sender)
+            del msg['From']
+            msg['From'] = sender
+
+        fix_subject()
+        fix_sender()
+
+        msg['X-Hg-Notification'] = 'changeset ' + short(node)
+        if not msg['Message-Id']:
+            msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
+                                 (short(node), int(time.time()),
+                                  hash(self.repo.root), socket.getfqdn()))
+
+        msgtext = msg.as_string(0)
+        if self.ui.configbool('notify', 'test', True):
+            self.ui.write(msgtext)
+            if not msgtext.endswith('\n'):
+                self.ui.write('\n')
+        else:
+            mail = self.ui.sendmail()
+            mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
+
+    def diff(self, node):
+        maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
+        if maxdiff == 0:
+            return
+        fp = templater.stringio()
+        commands.dodiff(fp, self.ui, self.repo, node,
+                        self.repo.changelog.tip())
+        difflines = fp.getvalue().splitlines(1)
+        if maxdiff > 0 and len(difflines) > maxdiff:
+            self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
+                           (len(difflines), maxdiff))
+            difflines = difflines[:maxdiff]
+        elif difflines:
+            self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
+        self.sio.write(*difflines)
+
+def hook(ui, repo, hooktype, node=None, **kwargs):
+    '''send email notifications to interested subscribers.
+
+    if used as changegroup hook, send one email for all changesets in
+    changegroup. else send one email per changeset.'''
+    n = notifier(ui, repo, hooktype)
+    if not n.subs: return True
+    node = bin(node)
+    if hooktype == 'changegroup':
+        start = repo.changelog.rev(node)
+        end = repo.changelog.count()
+        count = end - start
+        for rev in xrange(start, end):
+            n.node(repo.changelog.node(rev))
+    else:
+        count = 1
+        n.node(node)
+    n.diff(node)
+    n.send(node, count)
+    return True
--- a/hgext/patchbomb.py	Wed May 03 14:56:07 2006 -0700
+++ b/hgext/patchbomb.py	Mon May 08 08:04:46 2006 -0700
@@ -31,20 +31,10 @@
 # the messages directly. This can be reviewed e.g. with "mutt -R -f mbox",
 # and finally sent with "formail -s sendmail -bm -t < mbox".
 #
-# To configure a default mail host, add a section like this to your
-# hgrc file:
-#
-# [smtp]
-# host = my_mail_host
-# port = 1025
-# tls = yes # or omit if not needed
-# username = user     # if SMTP authentication required
-# password = password # if SMTP authentication required - PLAINTEXT
-#
 # To configure other defaults, add a section like this to your hgrc
 # file:
 #
-# [patchbomb]
+# [email]
 # from = My Name <my@email>
 # to = recipient1, recipient2, ...
 # cc = cc1, cc2, ...
@@ -52,7 +42,7 @@
 from mercurial.demandload import *
 demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
                          mercurial:commands,hg,ui
-                         os errno popen2 smtplib socket sys tempfile time''')
+                         os errno popen2 socket sys tempfile time''')
 from mercurial.i18n import gettext as _
 
 try:
@@ -183,11 +173,13 @@
         jumbo.extend(p)
         msgs.append(makepatch(p, i + 1, len(patches)))
 
-    sender = (opts['from'] or ui.config('patchbomb', 'from') or
+    sender = (opts['from'] or ui.config('email', 'from') or
+              ui.config('patchbomb', 'from') or
               prompt('From', ui.username()))
 
     def getaddrs(opt, prpt, default = None):
-        addrs = opts[opt] or (ui.config('patchbomb', opt) or
+        addrs = opts[opt] or (ui.config('email', opt) or
+                              ui.config('patchbomb', opt) or
                               prompt(prpt, default = default)).split(',')
         return [a.strip() for a in addrs if a.strip()]
     to = getaddrs('to', 'To')
@@ -223,17 +215,7 @@
     ui.write('\n')
 
     if not opts['test'] and not opts['mbox']:
-        s = smtplib.SMTP()
-        s.connect(host = ui.config('smtp', 'host', 'mail'),
-                  port = int(ui.config('smtp', 'port', 25)))
-        if ui.configbool('smtp', 'tls'):
-            s.ehlo()
-            s.starttls()
-            s.ehlo()
-        username = ui.config('smtp', 'username')
-        password = ui.config('smtp', 'password')
-        if username and password:
-            s.login(username, password)
+        mail = ui.sendmail()
     parent = None
     tz = time.strftime('%z')
     sender_addr = email.Utils.parseaddr(sender)[1]
@@ -271,9 +253,9 @@
             fp.close()
         else:
             ui.status('Sending ', m['Subject'], ' ...\n')
-            s.sendmail(sender, to + cc, m.as_string(0))
+            mail.sendmail(sender, to + cc, m.as_string(0))
     if not opts['test'] and not opts['mbox']:
-        s.close()
+        mail.close()
 
 cmdtable = {
     'email':
--- a/mercurial/commands.py	Wed May 03 14:56:07 2006 -0700
+++ b/mercurial/commands.py	Mon May 08 08:04:46 2006 -0700
@@ -2232,21 +2232,26 @@
     return errs
 
 def revert(ui, repo, *pats, **opts):
-    """revert modified files or dirs back to their unmodified states
-
-    In its default mode, it reverts any uncommitted modifications made
-    to the named files or directories.  This restores the contents of
-    the affected files to an unmodified state.
+    """revert modified files or dirs to their states as of some revision
+
+    With no revision specified, revert the named files or directories
+    to the contents they had in the parent of the working directory.
+    This restores the contents of the affected files to an unmodified
+    state.  If the working directory has two parents, you must
+    explicitly specify the revision to revert to.
 
     Modified files are saved with a .orig suffix before reverting.
     To disable these backups, use --no-backup.
 
-    Using the -r option, it reverts the given files or directories to
-    their state as of an earlier revision.  This can be helpful to "roll
+    Using the -r option, revert the given files or directories to
+    their contents as of a specific revision.  This can be helpful to"roll
     back" some or all of a change that should not have been committed.
 
     Revert modifies the working directory.  It does not commit any
-    changes, or change the parent of the current working directory.
+    changes, or change the parent of the working directory.  If you
+    revert to a revision other than the parent of the working
+    directory, the reverted files will thus appear modified
+    afterwards.
 
     If a file has been deleted, it is recreated.  If the executable
     mode of a file was changed, it is reset.
@@ -2255,8 +2260,14 @@
 
     If no arguments are given, all files in the repository are reverted.
     """
-    parent = repo.dirstate.parents()[0]
-    node = opts['rev'] and repo.lookup(opts['rev']) or parent
+    parent, p2 = repo.dirstate.parents()
+    if opts['rev']:
+        node = repo.lookup(opts['rev'])
+    elif p2 != nullid:
+        raise util.Abort(_('working dir has two parents; '
+                           'you must specify the revision to revert to'))
+    else:
+        node = parent
     mf = repo.manifest.read(repo.changelog.read(node)[0])
 
     wlock = repo.wlock()
--- a/mercurial/templater.py	Wed May 03 14:56:07 2006 -0700
+++ b/mercurial/templater.py	Mon May 08 08:04:46 2006 -0700
@@ -499,3 +499,17 @@
                                                            inst.args[0]))
         except SyntaxError, inst:
             raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
+
+class stringio(object):
+    '''wrap cStringIO for use by changeset_templater.'''
+    def __init__(self):
+        self.fp = cStringIO.StringIO()
+
+    def write(self, *args):
+        for a in args:
+            self.fp.write(a)
+
+    write_header = write
+
+    def __getattr__(self, key):
+        return getattr(self.fp, key)
--- a/mercurial/ui.py	Wed May 03 14:56:07 2006 -0700
+++ b/mercurial/ui.py	Mon May 08 08:04:46 2006 -0700
@@ -8,7 +8,7 @@
 import ConfigParser
 from i18n import gettext as _
 from demandload import *
-demandload(globals(), "errno os re socket sys tempfile util")
+demandload(globals(), "errno os re smtplib socket sys tempfile util")
 
 class ui(object):
     def __init__(self, verbose=False, debug=False, quiet=False,
@@ -242,7 +242,8 @@
     def debug(self, *msg):
         if self.debugflag: self.write(*msg)
     def edit(self, text, user):
-        (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt")
+        (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
+                                      text=True)
         try:
             f = os.fdopen(fd, "w")
             f.write(text)
@@ -264,3 +265,17 @@
             os.unlink(name)
 
         return t
+
+    def sendmail(self):
+        s = smtplib.SMTP()
+        s.connect(host = self.config('smtp', 'host', 'mail'),
+                  port = int(self.config('smtp', 'port', 25)))
+        if self.configbool('smtp', 'tls'):
+            s.ehlo()
+            s.starttls()
+            s.ehlo()
+        username = self.config('smtp', 'username')
+        password = self.config('smtp', 'password')
+        if username and password:
+            s.login(username, password)
+        return s
--- a/mercurial/util.py	Wed May 03 14:56:07 2006 -0700
+++ b/mercurial/util.py	Mon May 08 08:04:46 2006 -0700
@@ -231,7 +231,7 @@
                 name_st = os.stat(name)
             except OSError:
                 break
-            if os.path.samestat(name_st, root_st):
+            if samestat(name_st, root_st):
                 rel.reverse()
                 name = os.path.join(*rel)
                 audit_path(name)
@@ -561,6 +561,9 @@
     makelock = _makelock_file
     readlock = _readlock_file
 
+    def samestat(s1, s2):
+        return False
+
     def explain_exit(code):
         return _("exited with status %d") % code, code
 
@@ -627,6 +630,7 @@
         return path
 
     normpath = os.path.normpath
+    samestat = os.path.samestat
 
     def makelock(info, pathname):
         try:
--- a/templates/changelogentry-gitweb.tmpl	Wed May 03 14:56:07 2006 -0700
+++ b/templates/changelogentry-gitweb.tmpl	Mon May 08 08:04:46 2006 -0700
@@ -1,5 +1,5 @@
 <div>
-<a class="title" href="?cmd=changeset;node=#node#;style=gitweb"><span class="age">#date|age# ago</span>#desc|firstline|escape#</a>
+<a class="title" href="?cmd=changeset;node=#node#;style=gitweb"><span class="age">#date|age# ago</span>#desc|strip|firstline|escape#</a>
 </div>
 <div class="title_text">
 <div class="log_link">
@@ -8,7 +8,7 @@
 <i>#author|obfuscate# [#date|rfc822date#] rev #rev#</i><br/>
 </div>
 <div class="log_body">
-#desc|escape|addbreaks#
+#desc|strip|escape|addbreaks#
 <br/>
 <br/>
 </div>
--- a/templates/changeset-gitweb.tmpl	Wed May 03 14:56:07 2006 -0700
+++ b/templates/changeset-gitweb.tmpl	Mon May 08 08:04:46 2006 -0700
@@ -14,7 +14,7 @@
 </div>
 
 <div>
-<a class="title" href="?cmd=changeset;node=#node#;style=raw">#desc|escape|firstline#</a>
+<a class="title" href="?cmd=changeset;node=#node#;style=raw">#desc|strip|escape|firstline#</a>
 </div>
 <div class="title_text">
 <table cellspacing="0">
@@ -28,7 +28,7 @@
 </table></div>
 
 <div class="title_text">
-#desc|escape|addbreaks#
+#desc|strip|escape|addbreaks#
 </div>
 
 <div class="title_text">
--- a/templates/map-gitweb	Wed May 03 14:56:07 2006 -0700
+++ b/templates/map-gitweb	Mon May 08 08:04:46 2006 -0700
@@ -45,6 +45,6 @@
 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>'
 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
 shortlog = shortlog-gitweb.tmpl
-shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> |  <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
-filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
+shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> |  <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
+filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
 archiveentry = ' | <a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
--- a/tests/README	Wed May 03 14:56:07 2006 -0700
+++ b/tests/README	Mon May 08 08:04:46 2006 -0700
@@ -3,7 +3,7 @@
 To run the tests, do:
 
 cd tests/
-./run-tests
+python run-tests.py
 
 This finds all scripts in the test directory named test-* and executes
 them. The scripts can be either shell scripts or Python. Each test is
--- a/tests/run-tests	Wed May 03 14:56:07 2006 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,197 +0,0 @@
-#!/bin/sh -e
-#
-# environment variables:
-#
-# TEST_COVERAGE - set non-empty if you want to print test coverage report
-# COVERAGE_STDLIB - set non-empty to report coverage of standard library
-
-LANG="C"; export LANG
-LC_CTYPE="C"; export LC_CTYPE
-LC_NUMERIC="C"; export LC_NUMERIC
-LC_TIME="C"; export LC_TIME
-LC_COLLATE="C"; export LC_COLLATE
-LC_MONETARY="C"; export LC_MONETARY
-LC_MESSAGES="C"; export LC_MESSAGES
-LC_PAPER="C"; export LC_PAPER
-LC_NAME="C"; export LC_NAME
-LC_ADDRESS="C"; export LC_ADDRESS
-LC_TELEPHONE="C"; export LC_TELEPHONE
-LC_MEASUREMENT="C"; export LC_MEASUREMENT
-LC_IDENTIFICATION="C"; export LC_IDENTIFICATION
-LC_ALL=""; export LC_ALL
-TZ=GMT; export TZ
-HGEDITOR=true; export HGEDITOR
-HGMERGE=true; export HGMERGE
-HGUSER="test"; export HGUSER
-HGRCPATH=""; export HGRCPATH
-OS=`uname`
-
-case "$OS" in
-  HP-UX|SunOS)
-    DIFFOPTS=
-    ;;
-  *)
-    DIFFOPTS=-u
-    ;;
-esac
-
-if [ `echo -n HG` = "-n HG" ]
-then
-    ECHO_N=echo
-    NNL="\c"
-else
-    ECHO_N="echo -n"
-    NNL=
-fi
-
-umask 022
-
-tests=0
-failed=0
-
-HGTMP=""
-cleanup_exit() {
-    rm -rf "$HGTMP"
-}
-
-# Remove temporary files even if we get interrupted
-trap "cleanup_exit" 0 # normal exit
-trap "exit 255" 1 2 3 6 15 # HUP INT QUIT ABRT TERM
-
-HGTMP="${TMPDIR-/tmp}/hgtests.$RANDOM.$RANDOM.$RANDOM.$$"
-(umask 077 && mkdir "$HGTMP") || {
-    echo "Could not create temporary directory! Exiting." 1>&2
-    exit 1
-}
-
-TESTDIR="`pwd`"
-export TESTDIR
-INST="$HGTMP/install"
-PYTHONDIR="$INST/lib/python"
-cd ..
-if ${PYTHON-python} setup.py clean --all install --force --home="$INST" \
-  --install-lib="$PYTHONDIR" > tests/install.err 2>&1
-then
-    rm tests/install.err
-else
-    cat tests/install.err
-    exit 1
-fi
-cd "$TESTDIR"
-
-BINDIR="$INST/bin"; export BINDIR
-if [ -n "$TEST_COVERAGE" ]; then
-    COVERAGE_FILE="$TESTDIR/.coverage"; export COVERAGE_FILE
-    rm -f "$COVERAGE_FILE"
-    mv "$BINDIR/hg" "$BINDIR/hg.py"
-    {
-        echo '#!/bin/sh'
-        echo "exec \"${PYTHON-python}\" \"$TESTDIR/coverage.py\"" \
-            "-x \"$BINDIR/hg.py\" \"\$@\""
-    } > "$BINDIR/hg"
-    chmod 700 "$BINDIR/hg"
-fi
-    
-PATH="$BINDIR:$PATH"; export PATH
-if [ -n "$PYTHON" ]; then
-    {
-        echo "#!/bin/sh"
-        echo "exec \"$PYTHON"'" "$@"'
-    } > "$BINDIR/python"
-    chmod 755 "$BINDIR/python"
-fi
-
-PYTHONPATH="$PYTHONDIR"; export PYTHONPATH
-
-run_one() {
-    rm -f "$1.err"
-
-    mkdir "$HGTMP/$1"
-    cd "$HGTMP/$1"
-    fail=0
-    HOME="$HGTMP/$1"; export HOME
-    OUT="$HGTMP/$1.out"
-    OUTOK="$TESTDIR/$1.out"
-    ERR="$TESTDIR/$1.err"
-
-    if "$TESTDIR/$1" > "$OUT" 2>&1; then
-	: no error
-    else
-	echo "$1 failed with error code $?"
-	fail=1
-    fi
-
-    if [ -s "$OUT" -a ! -s "$OUTOK" ] ; then
-	cp "$OUT" "$ERR"
-	echo
-	echo "$1 generated unexpected output:"
-	cat "$ERR"
-	fail=1
-    elif [ -r "$OUTOK" ]; then
-	if diff $DIFFOPTS "$OUTOK" "$OUT" > /dev/null; then
-	    : no differences
-	else
-	    cp "$OUT" "$ERR"
-	    echo
-	    echo "$1 output changed:"
-	    diff $DIFFOPTS "$OUTOK" "$ERR" || true
-	    fail=1
-	fi
-    fi
-
-    cd "$TESTDIR"
-    rm -f "$HGTMP/$1.out"
-    rm -rf "$HGTMP/$1"
-    return $fail
-}
-
-# list of prerequisite programs
-# stuff from coreutils (cat, rm, etc) are not tested
-prereqs="python merge diff grep unzip gunzip bunzip2 sed"
-missing=''
-for pre in $prereqs ; do
-    if type $pre > /dev/null 2>&1 ; then
-        : prereq exists
-    else
-        missing="$pre $missing"
-    fi
-done
-
-if [ "$missing" != ''  ] ; then
-    echo "ERROR: the test suite needs some programs to execute correctly."
-    echo "The following programs are missing: "
-    for pre in $missing; do
-        echo "   $pre"
-    done
-    exit 1
-fi
-
-TESTS="$*"
-if [ -z "$TESTS" ] ; then
-    TESTS=`ls test-* | grep -v "[.~]"`
-fi
-
-for f in $TESTS ; do
-    $ECHO_N ".${NNL}"
-    run_one $f || failed=`expr $failed + 1`
-    tests=`expr $tests + 1`
-done
-
-echo
-echo "Ran $tests tests, $failed failed."
-
-if [ -n "$TEST_COVERAGE" ]; then
-    unset PYTHONPATH
-    $ECHO_N "$BINDIR,$TESTDIR,$HGTMP/test-," > "$HGTMP/omit"
-    if [ -z "$COVERAGE_STDLIB" ]; then
-        "${PYTHON-python}" -c 'import sys; print ",".join(sys.path)' \
-            >> "$HGTMP/omit"
-    fi
-    cd "$PYTHONDIR"
-    "${PYTHON-python}" "$TESTDIR/coverage.py" -r --omit="`cat \"$HGTMP/omit\"`"
-fi
-
-if [ $failed -gt 0 ] ; then
-    exit 1
-fi
-exit 0
--- a/tests/run-tests.py	Wed May 03 14:56:07 2006 -0700
+++ b/tests/run-tests.py	Mon May 08 08:04:46 2006 -0700
@@ -176,18 +176,20 @@
     ret, out = run(cmd)
     vlog("# Ret was:", ret)
 
-    if ret == 0:
-        # If reference output file exists, check test output against it
-        if os.path.exists(ref):
-            f = open(ref, "r")
-            ref_out = f.read().splitlines()
-            f.close()
-            if out != ref_out:
-                ret = 1
-                print "\nERROR: %s output changed" % (test)
-                show_diff(ref_out, out)
-    else:
+    diffret = 0
+    # If reference output file exists, check test output against it
+    if os.path.exists(ref):
+        f = open(ref, "r")
+        ref_out = f.read().splitlines()
+        f.close()
+        if out != ref_out:
+            diffret = 1
+            print "\nERROR: %s output changed" % (test)
+            show_diff(ref_out, out)
+    if ret:
         print "\nERROR: %s failed with error code %d" % (test, ret)
+    elif diffret:
+        ret = diffret
 
     if ret != 0: # Save errors to a file for diagnosis
         f = open(err, "w")
--- a/tests/test-confused-revert	Wed May 03 14:56:07 2006 -0700
+++ b/tests/test-confused-revert	Mon May 08 08:04:46 2006 -0700
@@ -41,9 +41,12 @@
 echo "%%% should show a removed and b added"
 hg status
 
-echo "reverting..."
+echo "%%% revert should fail"
 hg revert
 
+echo "%%% revert should be ok now"
+hg revert -r2
+
 echo "%%% should show b unknown and a marked modified (merged)"
 hg status
 
--- a/tests/test-confused-revert.out	Wed May 03 14:56:07 2006 -0700
+++ b/tests/test-confused-revert.out	Mon May 08 08:04:46 2006 -0700
@@ -16,7 +16,9 @@
 %%% should show a removed and b added
 A b
 R a
-reverting...
+%%% revert should fail
+abort: working dir has two parents; you must specify the revision to revert to
+%%% revert should be ok now
 undeleting a
 forgetting b
 %%% should show b unknown and a marked modified (merged)
--- a/tests/test-help.out	Wed May 03 14:56:07 2006 -0700
+++ b/tests/test-help.out	Mon May 08 08:04:46 2006 -0700
@@ -14,7 +14,7 @@
  pull       pull changes from the specified source
  push       push changes to the specified destination
  remove     remove the specified files on the next commit
- revert     revert modified files or dirs back to their unmodified states
+ revert     revert modified files or dirs to their states as of some revision
  serve      export the repository via HTTP
  status     show changed files in the working directory
  update     update or merge working directory
@@ -30,7 +30,7 @@
  pull       pull changes from the specified source
  push       push changes to the specified destination
  remove     remove the specified files on the next commit
- revert     revert modified files or dirs back to their unmodified states
+ revert     revert modified files or dirs to their states as of some revision
  serve      export the repository via HTTP
  status     show changed files in the working directory
  update     update or merge working directory
@@ -68,7 +68,7 @@
  recover    roll back an interrupted transaction
  remove     remove the specified files on the next commit
  rename     rename files; equivalent of copy + remove
- revert     revert modified files or dirs back to their unmodified states
+ revert     revert modified files or dirs to their states as of some revision
  root       print the root (top) of the current working dir
  serve      export the repository via HTTP
  status     show changed files in the working directory
@@ -110,7 +110,7 @@
  recover    roll back an interrupted transaction
  remove     remove the specified files on the next commit
  rename     rename files; equivalent of copy + remove
- revert     revert modified files or dirs back to their unmodified states
+ revert     revert modified files or dirs to their states as of some revision
  root       print the root (top) of the current working dir
  serve      export the repository via HTTP
  status     show changed files in the working directory
@@ -226,7 +226,7 @@
  pull       pull changes from the specified source
  push       push changes to the specified destination
  remove     remove the specified files on the next commit
- revert     revert modified files or dirs back to their unmodified states
+ revert     revert modified files or dirs to their states as of some revision
  serve      export the repository via HTTP
  status     show changed files in the working directory
  update     update or merge working directory
@@ -247,7 +247,7 @@
  pull       pull changes from the specified source
  push       push changes to the specified destination
  remove     remove the specified files on the next commit
- revert     revert modified files or dirs back to their unmodified states
+ revert     revert modified files or dirs to their states as of some revision
  serve      export the repository via HTTP
  status     show changed files in the working directory
  update     update or merge working directory