changeset 9535:75d290db2df6

merge with mpm
author Benoit Boissinot <benoit.boissinot@ens-lyon.org>
date Sat, 03 Oct 2009 23:38:10 +0200
parents 8e202431d620 (current diff) d932dc655881 (diff)
children f04d17912441
files
diffstat 28 files changed, 731 insertions(+), 341 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/bash_completion	Sat Oct 03 23:36:08 2009 +0200
+++ b/contrib/bash_completion	Sat Oct 03 23:38:10 2009 +0200
@@ -530,3 +530,20 @@
     return
 }
 
+# shelve
+_hg_shelves()
+{
+    local shelves="$("$hg" unshelve -l . 2>/dev/null)"
+    local IFS=$'\n'
+    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$shelves' -- "$cur"))
+}
+
+_hg_cmd_shelve()
+{
+    _hg_status "mard"
+}
+
+_hg_cmd_unshelve()
+{
+    _hg_shelves
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/shrink-revlog.py	Sat Oct 03 23:38:10 2009 +0200
@@ -0,0 +1,218 @@
+#!/usr/bin/env python
+
+"""\
+Reorder a revlog (by default the the manifest file in the current
+repository) to save space.  Specifically, this topologically sorts the
+revisions in the revlog so that revisions on the same branch are adjacent
+as much as possible.  This is a workaround for the fact that Mercurial
+computes deltas relative to the previous revision rather than relative to a
+parent revision.  This is *not* safe to run on a changelog.
+"""
+
+# Originally written by Benoit Boissinot <benoit.boissinot at ens-lyon.org>
+# as a patch to rewrite-log.  Cleaned up, refactored, documented, and
+# renamed by Greg Ward <greg at gerg.ca>.
+
+# XXX would be nice to have a way to verify the repository after shrinking,
+# e.g. by comparing "before" and "after" states of random changesets
+# (maybe: export before, shrink, export after, diff).
+
+import sys, os, tempfile
+import optparse
+from mercurial import ui as ui_, hg, revlog, transaction, node, util
+
+def toposort(rl):
+    write = sys.stdout.write
+
+    children = {}
+    root = []
+    # build children and roots
+    write('reading %d revs ' % len(rl))
+    try:
+        for i in rl:
+            children[i] = []
+            parents = [p for p in rl.parentrevs(i) if p != node.nullrev]
+            # in case of duplicate parents
+            if len(parents) == 2 and parents[0] == parents[1]:
+                del parents[1]
+            for p in parents:
+                assert p in children
+                children[p].append(i)
+
+            if len(parents) == 0:
+                root.append(i)
+
+            if i % 1000 == 0:
+                write('.')
+    finally:
+        write('\n')
+
+    # XXX this is a reimplementation of the 'branchsort' topo sort
+    # algorithm in hgext.convert.convcmd... would be nice not to duplicate
+    # the algorithm
+    write('sorting ...')
+    visit = root
+    ret = []
+    while visit:
+        i = visit.pop(0)
+        ret.append(i)
+        if i not in children:
+            # This only happens if some node's p1 == p2, which can
+            # happen in the manifest in certain circumstances.
+            continue
+        next = []
+        for c in children.pop(i):
+            parents_unseen = [p for p in rl.parentrevs(c)
+                              if p != node.nullrev and p in children]
+            if len(parents_unseen) == 0:
+                next.append(c)
+        visit = next + visit
+    write('\n')
+    return ret
+
+def writerevs(r1, r2, order, tr):
+    write = sys.stdout.write
+    write('writing %d revs ' % len(order))
+    try:
+        count = 0
+        for rev in order:
+            n = r1.node(rev)
+            p1, p2 = r1.parents(n)
+            l = r1.linkrev(rev)
+            t = r1.revision(n)
+            n2 = r2.addrevision(t, tr, l, p1, p2)
+
+            if count % 1000 == 0:
+                write('.')
+            count += 1
+    finally:
+        write('\n')
+    
+def report(olddatafn, newdatafn):
+    oldsize = float(os.stat(olddatafn).st_size)
+    newsize = float(os.stat(newdatafn).st_size)
+
+    # argh: have to pass an int to %d, because a float >= 2^32 
+    # blows up under Python 2.5 or earlier
+    sys.stdout.write('old file size: %12d bytes (%6.1f MiB)\n'
+                     % (int(oldsize), oldsize/1024/1024))
+    sys.stdout.write('new file size: %12d bytes (%6.1f MiB)\n'
+                     % (int(newsize), newsize/1024/1024))
+
+    shrink_percent = (oldsize - newsize) / oldsize * 100
+    shrink_factor = oldsize / newsize
+    sys.stdout.write('shrinkage: %.1f%% (%.1fx)\n'
+                     % (shrink_percent, shrink_factor))
+
+def main():
+
+    # Unbuffer stdout for nice progress output.
+    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
+    write = sys.stdout.write
+
+    parser = optparse.OptionParser(description=__doc__)
+    parser.add_option('-R', '--repository',
+                      default=os.path.curdir,
+                      metavar='REPO',
+                      help='repository root directory [default: current dir]')
+    parser.add_option('--revlog',
+                      metavar='FILE',
+                      help='shrink FILE [default: REPO/hg/store/00manifest.i]')
+    (options, args) = parser.parse_args()
+    if args:
+        parser.error('too many arguments')
+
+    # Open the specified repository.
+    ui = ui_.ui()
+    repo = hg.repository(ui, options.repository)
+    if not repo.local():
+        parser.error('not a local repository: %s' % options.repository)
+
+    if options.revlog is None:
+        indexfn = repo.sjoin('00manifest.i')
+    else:
+        if not options.revlog.endswith('.i'):
+            parser.error('--revlog option must specify the revlog index file '
+                         '(*.i), not %s' % options.revlog)
+
+        indexfn = os.path.realpath(options.revlog)
+        store = repo.sjoin('')
+        if not indexfn.startswith(store):
+            parser.error('--revlog option must specify a revlog in %s, not %s'
+                         % (store, indexfn))
+
+    datafn = indexfn[:-2] + '.d'
+    if not os.path.exists(indexfn):
+        parser.error('no such file: %s' % indexfn)
+    if '00changelog' in indexfn:
+        parser.error('shrinking the changelog will corrupt your repository')
+    if not os.path.exists(datafn):
+        # This is just a lazy shortcut because I can't be bothered to
+        # handle all the special cases that entail from no .d file.
+        parser.error('%s does not exist: revlog not big enough '
+                     'to be worth shrinking' % datafn)
+
+    oldindexfn = indexfn + '.old'
+    olddatafn = datafn + '.old'
+    if os.path.exists(oldindexfn) or os.path.exists(olddatafn):
+        parser.error('one or both of\n'
+                     '  %s\n'
+                     '  %s\n'
+                     'exists from a previous run; please clean up before '
+                     'running again'
+                     % (oldindexfn, olddatafn))
+
+    write('shrinking %s\n' % indexfn)
+    prefix = os.path.basename(indexfn)[:-1]
+    (tmpfd, tmpindexfn) = tempfile.mkstemp(dir=os.path.dirname(indexfn),
+                                           prefix=prefix,
+                                           suffix='.i')
+    tmpdatafn = tmpindexfn[:-2] + '.d'
+    os.close(tmpfd)
+
+    r1 = revlog.revlog(util.opener(os.getcwd(), audit=False), indexfn)
+    r2 = revlog.revlog(util.opener(os.getcwd(), audit=False), tmpindexfn)
+
+    # Don't use repo.transaction(), because then things get hairy with
+    # paths: some need to be relative to .hg, and some need to be
+    # absolute.  Doing it this way keeps things simple: everything is an
+    # absolute path.
+    lock = repo.lock(wait=False)
+    tr = transaction.transaction(sys.stderr.write,
+                                 open,
+                                 repo.sjoin('journal'))
+
+    try:
+        try:
+            order = toposort(r1)
+            writerevs(r1, r2, order, tr)
+            report(datafn, tmpdatafn)
+            tr.close()
+        except:
+            # Abort transaction first, so we truncate the files before
+            # deleting them.
+            tr.abort()
+            if os.path.exists(tmpindexfn):
+                os.unlink(tmpindexfn)
+            if os.path.exists(tmpdatafn):
+                os.unlink(tmpdatafn)
+            raise
+    finally:
+        lock.release()
+
+    os.link(indexfn, oldindexfn)
+    os.link(datafn, olddatafn)
+    os.rename(tmpindexfn, indexfn)
+    os.rename(tmpdatafn, datafn)
+    write('note: old revlog saved in:\n'
+          '  %s\n'
+          '  %s\n'
+          '(You can delete those files when you are satisfied that your\n'
+          'repository is still sane.  '
+          'Running \'hg verify\' is strongly recommended.)\n'
+          % (oldindexfn, olddatafn))
+
+try:
+    main()
+except KeyboardInterrupt:
+    sys.exit("interrupted")
--- a/doc/hg.1.txt	Sat Oct 03 23:36:08 2009 +0200
+++ b/doc/hg.1.txt	Sat Oct 03 23:38:10 2009 +0200
@@ -95,6 +95,6 @@
 -------
 Copyright \(C) 2005-2009 Matt Mackall.
 Free use of this software is granted under the terms of the GNU General
-Public License (GPL).
+Public License version 2.
 
 .. include:: common.txt
--- a/doc/hgignore.5.txt	Sat Oct 03 23:36:08 2009 +0200
+++ b/doc/hgignore.5.txt	Sat Oct 03 23:38:10 2009 +0200
@@ -106,6 +106,6 @@
 This manual page is copyright 2006 Vadim Gelfer.
 Mercurial is copyright 2005-2009 Matt Mackall.
 Free use of this software is granted under the terms of the GNU General
-Public License (GPL).
+Public License version 2.
 
 .. include:: common.txt
--- a/doc/hgrc.5.txt	Sat Oct 03 23:36:08 2009 +0200
+++ b/doc/hgrc.5.txt	Sat Oct 03 23:38:10 2009 +0200
@@ -946,6 +946,6 @@
 This manual page is copyright 2005 Bryan O'Sullivan.
 Mercurial is copyright 2005-2009 Matt Mackall.
 Free use of this software is granted under the terms of the GNU General
-Public License (GPL).
+Public License version 2.
 
 .. include:: common.txt
--- a/hgext/color.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/hgext/color.py	Sat Oct 03 23:38:10 2009 +0200
@@ -160,9 +160,8 @@
     return retval
 
 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
-                   'missing': ['red', 'bold'],
-                   'unapplied': ['black', 'bold'], }
-
+                    'missing': ['red', 'bold'],
+                    'unapplied': ['black', 'bold'], }
 def colorwrap(orig, s):
     '''wrap ui.write for colored diff output'''
     lines = s.split('\n')
--- a/hgext/convert/subversion.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/hgext/convert/subversion.py	Sat Oct 03 23:38:10 2009 +0200
@@ -153,11 +153,13 @@
 def issvnurl(url):
     try:
         proto, path = url.split('://', 1)
-        path = urllib.url2pathname(path)
+        if proto == 'file':
+            path = urllib.url2pathname(path)
     except ValueError:
         proto = 'file'
         path = os.path.abspath(url)
-    path = path.replace(os.sep, '/')
+    if proto == 'file':
+        path = path.replace(os.sep, '/')
     check = protomap.get(proto, lambda p, p2: False)
     while '/' in path:
         if check(path, proto):
--- a/hgext/extdiff.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/hgext/extdiff.py	Sat Oct 03 23:38:10 2009 +0200
@@ -42,9 +42,9 @@
 '''
 
 from mercurial.i18n import _
-from mercurial.node import short
+from mercurial.node import short, nullid
 from mercurial import cmdutil, util, commands
-import os, shlex, shutil, tempfile
+import os, shlex, shutil, tempfile, re
 
 def snapshot(ui, repo, files, node, tmproot):
     '''snapshot files as of some revision
@@ -69,7 +69,7 @@
     for fn in files:
         wfn = util.pconvert(fn)
         if not wfn in ctx:
-            # skipping new file after a merge ?
+            # File doesn't exist; could be a bogus modify
             continue
         ui.note('  %s\n' % wfn)
         dest = os.path.join(base, wfn)
@@ -96,52 +96,95 @@
 
     revs = opts.get('rev')
     change = opts.get('change')
+    args = ' '.join(diffopts)
+    do3way = '$parent2' in args
 
     if revs and change:
         msg = _('cannot specify --rev and --change at the same time')
         raise util.Abort(msg)
     elif change:
         node2 = repo.lookup(change)
-        node1 = repo[node2].parents()[0].node()
+        node1a, node1b = repo.changelog.parents(node2)
     else:
-        node1, node2 = cmdutil.revpair(repo, revs)
+        node1a, node2 = cmdutil.revpair(repo, revs)
+        if not revs:
+            node1b = repo.dirstate.parents()[1]
+        else:
+            node1b = nullid
+
+    # Disable 3-way merge if there is only one parent
+    if do3way:
+        if node1b == nullid:
+            do3way = False
 
     matcher = cmdutil.match(repo, pats, opts)
-    modified, added, removed = repo.status(node1, node2, matcher)[:3]
-    if not (modified or added or removed):
-        return 0
+    mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
+    if do3way:
+        mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
+    else:
+        mod_b, add_b, rem_b = set(), set(), set()
+    modadd = mod_a | add_a | mod_b | add_b
+    common = modadd | rem_a | rem_b
+    if not common:
+       return 0
 
     tmproot = tempfile.mkdtemp(prefix='extdiff.')
-    dir2root = ''
     try:
-        # Always make a copy of node1
-        dir1 = snapshot(ui, repo, modified + removed, node1, tmproot)[0]
-        changes = len(modified) + len(removed) + len(added)
+        # Always make a copy of node1a (and node1b, if applicable)
+        dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
+        dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
+        if do3way:
+            dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
+            dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
+        else:
+            dir1b = None
+
+        fns_and_mtime = []
 
         # If node2 in not the wc or there is >1 change, copy it
-        if node2 or changes > 1:
-            dir2, fns_and_mtime = snapshot(ui, repo, modified + added, node2, tmproot)
+        dir2root = ''
+        if node2:
+            dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
+        elif len(common) > 1:
+            #we only actually need to get the files to copy back to the working
+            #dir in this case (because the other cases are: diffing 2 revisions
+            #or single file -- in which case the file is already directly passed
+            #to the diff tool).
+            dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
         else:
             # This lets the diff tool open the changed file directly
             dir2 = ''
             dir2root = repo.root
-            fns_and_mtime = []
 
         # If only one change, diff the files instead of the directories
-        if changes == 1 :
-            if len(modified):
-                dir1 = os.path.join(dir1, util.localpath(modified[0]))
-                dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
-            elif len(removed) :
-                dir1 = os.path.join(dir1, util.localpath(removed[0]))
-                dir2 = os.devnull
-            else:
-                dir1 = os.devnull
-                dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
+        # 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)
+            if not os.path.isfile(os.path.join(tmproot, dir1a)):
+                dir1a = os.devnull
+            if do3way:
+                dir1b = os.path.join(dir1b, common_file)
+                if not os.path.isfile(os.path.join(tmproot, dir1b)):
+                    dir1b = os.devnull
+            dir2 = os.path.join(dir2root, dir2, common_file)
 
-        cmdline = ('%s %s %s %s' %
-                   (util.shellquote(diffcmd), ' '.join(diffopts),
-                    util.shellquote(dir1), util.shellquote(dir2)))
+        # Function to quote file/dir names in the argument string
+        # When not operating in 3-way mode, an empty string is returned for parent2
+        replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b, child=dir2)
+        def quote(match):
+            key = match.group()[1:]
+            if not do3way and key == 'parent2':
+                return ''
+            return util.shellquote(replace[key])
+
+        # Match parent2 first, so 'parent1?' will match both parent1 and parent
+        regex = '\$(parent2|parent1?|child)'
+        if not do3way and not re.search(regex, args):
+            args += ' $parent1 $child'
+        args = re.sub(regex, quote, args)
+        cmdline = util.shellquote(diffcmd) + ' ' + args
+
         ui.debug('running %r in %s\n' % (cmdline, tmproot))
         util.system(cmdline, cwd=tmproot)
 
@@ -173,11 +216,11 @@
     that revision is compared to the working directory, and, when no
     revisions are specified, the working directory files are compared
     to its parent.'''
-    program = opts['program'] or 'diff'
-    if opts['program']:
-        option = opts['option']
-    else:
-        option = opts['option'] or ['-Npru']
+    program = opts.get('program')
+    option = opts.get('option')
+    if not program:
+        program = 'diff'
+        option = option or ['-Npru']
     return dodiff(ui, repo, program, option, pats, opts)
 
 cmdtable = {
--- a/hgext/inotify/__init__.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/hgext/inotify/__init__.py	Sat Oct 03 23:38:10 2009 +0200
@@ -17,28 +17,7 @@
 
 def serve(ui, repo, **opts):
     '''start an inotify server for this repository'''
-    timeout = opts.get('timeout')
-    if timeout:
-        timeout = float(timeout) * 1e3
-
-    class service(object):
-        def init(self):
-            try:
-                self.master = server.master(ui, repo.dirstate,
-                                            repo.root, timeout)
-            except server.AlreadyStartedException, inst:
-                raise util.Abort(str(inst))
-
-        def run(self):
-            try:
-                self.master.run()
-            finally:
-                self.master.shutdown()
-
-    service = service()
-    logfile = ui.config('inotify', 'log')
-    cmdutil.service(opts, initfn=service.init, runfn=service.run,
-                    logfile=logfile)
+    server.start(ui, repo.dirstate, repo.root, opts)
 
 def debuginotify(ui, repo, **opts):
     '''debugging information for inotify extension
--- a/hgext/inotify/client.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/hgext/inotify/client.py	Sat Oct 03 23:38:10 2009 +0200
@@ -34,7 +34,8 @@
                 self.ui.debug('(starting inotify server)\n')
                 try:
                     try:
-                        server.start(self.ui, self.dirstate, self.root)
+                        server.start(self.ui, self.dirstate, self.root,
+                                     dict(daemon=True, daemon_pipefds=''))
                     except server.AlreadyStartedException, inst:
                         # another process may have started its own
                         # inotify server while this one was starting.
--- a/hgext/inotify/server.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/hgext/inotify/server.py	Sat Oct 03 23:38:10 2009 +0200
@@ -7,7 +7,7 @@
 # GNU General Public License version 2, incorporated herein by reference.
 
 from mercurial.i18n import _
-from mercurial import osutil, util
+from mercurial import cmdutil, osutil, util
 import common
 import errno, os, select, socket, stat, struct, sys, tempfile, time
 
@@ -823,52 +823,29 @@
             sys.exit(0)
         pollable.run()
 
-def start(ui, dirstate, root):
-    def closefds(ignore):
-        # (from python bug #1177468)
-        # close all inherited file descriptors
-        # Python 2.4.1 and later use /dev/urandom to seed the random module's RNG
-        # a file descriptor is kept internally as os._urandomfd (created on demand
-        # the first time os.urandom() is called), and should not be closed
-        try:
-            os.urandom(4)
-            urandom_fd = getattr(os, '_urandomfd', None)
-        except AttributeError:
-            urandom_fd = None
-        ignore.append(urandom_fd)
-        for fd in range(3, 256):
-            if fd in ignore:
-                continue
+def start(ui, dirstate, root, opts):
+    timeout = opts.get('timeout')
+    if timeout:
+        timeout = float(timeout) * 1e3
+
+    class service(object):
+        def init(self):
             try:
-                os.close(fd)
-            except OSError:
-                pass
-
-    m = master(ui, dirstate, root)
-    sys.stdout.flush()
-    sys.stderr.flush()
+                self.master = master(ui, dirstate, root, timeout)
+            except AlreadyStartedException, inst:
+                raise util.Abort(str(inst))
 
-    pid = os.fork()
-    if pid:
-        return pid
-
-    closefds(pollable.instances.keys())
-    os.setsid()
-
-    fd = os.open('/dev/null', os.O_RDONLY)
-    os.dup2(fd, 0)
-    if fd > 0:
-        os.close(fd)
+        def run(self):
+            try:
+                self.master.run()
+            finally:
+                self.master.shutdown()
 
-    fd = os.open(ui.config('inotify', 'log', '/dev/null'),
-                 os.O_RDWR | os.O_CREAT | os.O_TRUNC)
-    os.dup2(fd, 1)
-    os.dup2(fd, 2)
-    if fd > 2:
-        os.close(fd)
+    runargs = None
+    if 'inserve' not in sys.argv:
+        runargs = [sys.argv[0], 'inserve', '-R', root]
 
-    try:
-        m.run()
-    finally:
-        m.shutdown()
-        os._exit(0)
+    service = service()
+    logfile = ui.config('inotify', 'log')
+    cmdutil.service(opts, initfn=service.init, runfn=service.run,
+                    logfile=logfile, runargs=runargs)
--- a/hgext/keyword.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/hgext/keyword.py	Sat Oct 03 23:38:10 2009 +0200
@@ -244,12 +244,14 @@
             return t2 != text
         return revlog.revlog.cmp(self, node, text)
 
-def _status(ui, repo, kwt, unknown, *pats, **opts):
+def _status(ui, repo, kwt, *pats, **opts):
     '''Bails out if [keyword] configuration is not active.
     Returns status of working directory.'''
     if kwt:
-        match = cmdutil.match(repo, pats, opts)
-        return repo.status(match=match, unknown=unknown, clean=True)
+        unknown = (opts.get('unknown') or opts.get('all')
+                   or opts.get('untracked'))
+        return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
+                           unknown=unknown)
     if ui.configitems('keyword'):
         raise util.Abort(_('[keyword] patterns cannot match'))
     raise util.Abort(_('no [keyword] patterns configured'))
@@ -259,7 +261,7 @@
     if repo.dirstate.parents()[1] != nullid:
         raise util.Abort(_('outstanding uncommitted merge'))
     kwt = kwtools['templater']
-    status = _status(ui, repo, kwt, False, *pats, **opts)
+    status = _status(ui, repo, kwt, *pats, **opts)
     modified, added, removed, deleted = status[:4]
     if modified or added or removed or deleted:
         raise util.Abort(_('outstanding uncommitted changes'))
@@ -380,30 +382,32 @@
     See "hg help keyword" on how to construct patterns both for
     inclusion and exclusion of files.
 
-    Use -u/--untracked to list untracked files as well.
-
-    With -a/--all and -v/--verbose the codes used to show the status
+    With -A/--all and -v/--verbose the codes used to show the status
     of files are::
 
       K = keyword expansion candidate
-      k = keyword expansion candidate (untracked)
+      k = keyword expansion candidate (not tracked)
       I = ignored
-      i = ignored (untracked)
+      i = ignored (not tracked)
     '''
     kwt = kwtools['templater']
-    status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
+    status = _status(ui, repo, kwt, *pats, **opts)
+    cwd = pats and repo.getcwd() or ''
     modified, added, removed, deleted, unknown, ignored, clean = status
-    files = sorted(modified + added + clean)
+    files = []
+    if not (opts.get('unknown') or opts.get('untracked')) or opts.get('all'):
+        files = sorted(modified + added + clean)
     wctx = repo[None]
     kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
-    kwuntracked = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
-    cwd = pats and repo.getcwd() or ''
-    kwfstats = (not opts.get('ignore') and
-                (('K', kwfiles), ('k', kwuntracked),) or ())
+    kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
+    if not opts.get('ignore') or opts.get('all'):
+        showfiles = kwfiles, kwunknown
+    else:
+        showfiles = [], []
     if opts.get('all') or opts.get('ignore'):
-        kwfstats += (('I', [f for f in files if f not in kwfiles]),
-                     ('i', [f for f in unknown if f not in kwuntracked]),)
-    for char, filenames in kwfstats:
+        showfiles += ([f for f in files if f not in kwfiles],
+                      [f for f in unknown if f not in kwunknown])
+    for char, filenames in zip('KkIi', showfiles):
         fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
         for f in filenames:
             ui.write(fmt % repo.pathto(f, cwd))
@@ -545,9 +549,12 @@
                  _('hg kwexpand [OPTION]... [FILE]...')),
     'kwfiles':
         (files,
-         [('a', 'all', None, _('show keyword status flags of all files')),
+         [('A', 'all', None, _('show keyword status flags of all files')),
           ('i', 'ignore', None, _('show files excluded from expansion')),
-          ('u', 'untracked', None, _('additionally show untracked files')),
+          ('u', 'unknown', None, _('only show unknown (not tracked) files')),
+          ('a', 'all', None,
+           _('show keyword status flags of all files (DEPRECATED)')),
+          ('u', 'untracked', None, _('only show untracked files (DEPRECATED)')),
          ] + commands.walkopts,
          _('hg kwfiles [OPTION]... [FILE]...')),
     'kwshrink': (shrink, commands.walkopts,
--- a/hgext/notify.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/hgext/notify.py	Sat Oct 03 23:38:10 2009 +0200
@@ -43,6 +43,7 @@
   diffstat = True        # add a diffstat before the diff content
   sources = serve        # notify if source of incoming changes in this list
                          # (serve == ssh or http, push, pull, bundle)
+  merge = False          # send notification for merges (default True)
   [email]
   from = user@host.com   # email address to send as if none given
   [web]
@@ -111,6 +112,7 @@
         self.test = self.ui.configbool('notify', 'test', True)
         self.charsets = mail._charsets(self.ui)
         self.subs = self.subscribers()
+        self.merge = self.ui.configbool('notify', 'merge', True)
 
         mapfile = self.ui.config('notify', 'style')
         template = (self.ui.config('notify', hooktype) or
@@ -166,10 +168,13 @@
         return self.ui.config('web', 'baseurl') + (path or self.root)
 
     def node(self, ctx, **props):
-        '''format one changeset.'''
+        '''format one changeset, unless it is a suppressed merge.'''
+        if not self.merge and len(ctx.parents()) > 1:
+            return False
         self.t.show(ctx, changes=ctx.changeset(),
                     baseurl=self.ui.config('web', 'baseurl'),
                     root=self.repo.root, webroot=self.root, **props)
+        return True
 
     def skipsource(self, source):
         '''true if incoming changes from this source should be skipped.'''
@@ -283,16 +288,29 @@
         return
 
     ui.pushbuffer()
+    data = ''
+    count = 0
     if hooktype == 'changegroup':
         start, end = ctx.rev(), len(repo)
-        count = end - start
         for rev in xrange(start, end):
-            n.node(repo[rev])
-        n.diff(ctx, repo['tip'])
+            if n.node(repo[rev]):
+                count += 1
+            else:
+                data += ui.popbuffer()
+                ui.note(_('notify: suppressing notification for merge %d:%s\n') %
+                        (rev, repo[rev].hex()[:12]))
+                ui.pushbuffer()
+        if count:
+            n.diff(ctx, repo['tip'])
     else:
-        count = 1
-        n.node(ctx)
+        if not n.node(ctx):
+            ui.popbuffer()
+            ui.note(_('notify: suppressing notification for merge %d:%s\n') %
+                    (ctx.rev(), ctx.hex()[:12]))
+            return
+        count += 1
         n.diff(ctx)
 
-    data = ui.popbuffer()
-    n.send(ctx, count, data)
+    data += ui.popbuffer()
+    if count:
+        n.send(ctx, count, data)
--- a/i18n/da.po	Sat Oct 03 23:36:08 2009 +0200
+++ b/i18n/da.po	Sat Oct 03 23:38:10 2009 +0200
@@ -17,8 +17,8 @@
 msgstr ""
 "Project-Id-Version: Mercurial\n"
 "Report-Msgid-Bugs-To: <mercurial-devel@selenic.com>\n"
-"POT-Creation-Date: 2009-07-20 23:05+0200\n"
-"PO-Revision-Date: 2009-07-21 00:18+0200\n"
+"POT-Creation-Date: 2009-09-29 00:26+0200\n"
+"PO-Revision-Date: 2009-09-29 00:41+0200\n"
 "Last-Translator:  <mg@lazybytes.net>\n"
 "Language-Team: Danish\n"
 "MIME-Version: 1.0\n"
@@ -1007,7 +1007,8 @@
 #, python-format
 msgid ""
 "unexpected response from CVS server (expected \"Valid-requests\", but got %r)"
-msgstr "uventet svar fra CVS serveren (forventede \"Valid-requests\", men fik %r)"
+msgstr ""
+"uventet svar fra CVS serveren (forventede \"Valid-requests\", men fik %r)"
 
 #, python-format
 msgid "%d bytes missing from remote file"
@@ -1086,6 +1087,10 @@
 msgid "%d changeset entries\n"
 msgstr "%d ændringer\n"
 
+#, python-format
+msgid "darcs version 2.1 or newer needed (found %r)"
+msgstr ""
+
 msgid "Python ElementTree module is not available"
 msgstr "Python ElementTree modulet er ikke tilstede"
 
@@ -1492,10 +1497,6 @@
 msgstr "sammenføjer med %d:%s\n"
 
 #, python-format
-msgid "Automated merge with %s"
-msgstr "Automatisk sammenføjning med %s"
-
-#, python-format
 msgid "new changeset %d:%s merges remote changes with local\n"
 msgstr "ny ændring %d:%s fletter fjernændringer sammen med lokale\n"
 
@@ -1576,10 +1577,6 @@
 "arbejdskopien af .hgsigs er ændret (deponer venligst .hgsigs manuelt eller "
 "brug --force)"
 
-#, python-format
-msgid "Added signature for changeset %s"
-msgstr "Tilføjede underskrift af ændring %s"
-
 msgid "unknown signature version"
 msgstr "ukendt underskrift-version"
 
@@ -2133,7 +2130,9 @@
 msgid ""
 "\n"
 "%s keywords written to %s:\n"
-msgstr "\n%s nøgleord skrevet til %s:\n"
+msgstr ""
+"\n"
+"%s nøgleord skrevet til %s:\n"
 
 msgid "unhooked all commit hooks\n"
 msgstr ""
@@ -3789,22 +3788,22 @@
 msgstr ""
 
 msgid "cannot use both abort and continue"
-msgstr ""
+msgstr "abort og continue kan ikke angives samtidig"
 
 msgid "cannot use collapse with continue or abort"
-msgstr ""
+msgstr "continue eller abort kan ikke angives samtidig med collapse"
 
 msgid "abort and continue do not allow specifying revisions"
-msgstr ""
+msgstr "abort og continue tillader ikke at der angives revisioner"
 
 msgid "cannot specify both a revision and a base"
-msgstr ""
+msgstr "man kan ikke angive både en revision og en basis"
 
 msgid "nothing to rebase\n"
 msgstr ""
 
 msgid "cannot use both keepbranches and extrafn"
-msgstr ""
+msgstr "man kan ikke bruge både keepbranches og extrafn"
 
 msgid "rebase merging completed\n"
 msgstr ""
@@ -3817,7 +3816,7 @@
 
 #, python-format
 msgid "%d revisions have been skipped\n"
-msgstr ""
+msgstr "sprang %d revisioner over\n"
 
 msgid " set parents\n"
 msgstr ""
@@ -4275,7 +4274,7 @@
 msgstr ""
 
 #, python-format
-msgid "[win32mbcs] filename conversion fail with %s encoding\n"
+msgid "[win32mbcs] filename conversion failed with %s encoding\n"
 msgstr ""
 
 msgid "[win32mbcs] cannot activate on this platform.\n"
@@ -4668,12 +4667,27 @@
 "    be expensive.\n"
 "    "
 msgstr ""
+"tilføj alle nye filer, fjern alle manglende filer\n"
+"\n"
+"    Tilføj alle nye filer og fjern alle manglende filer fra depotet.\n"
+"\n"
+"    Nye filer bliver ignoreret hvis de matcher et af mønstrene i\n"
+"    .hgignore. Som ved add, så træder disse ændringer først i kræft\n"
+"    ved næste commit.\n"
+"\n"
+"    Brug -s/--similarity tilvalget for at opdage omdøbte filer. Med en\n"
+"    parameter større end 0 bliver hver fjernet fil sammenlignet med\n"
+"    enhver tilføjet fil og filer der er tilstrækkelig ens bliver\n"
+"    opført som omdøbte. Dette tilvalg tager et procenttal mellem 0\n"
+"    (slået fra) og 100 (filer skal være identiske) som parameter. At\n"
+"    opdage omdøbninger på denne måde kan være dyrt.\n"
+"    "
 
 msgid "similarity must be a number"
-msgstr ""
+msgstr "lighedsgrad skal være et tal"
 
 msgid "similarity must be between 0 and 100"
-msgstr ""
+msgstr "lighedsgrad skal være mellem 0 og 100"
 
 msgid ""
 "show changeset information by line for each file\n"
@@ -4705,10 +4719,10 @@
 "    "
 
 msgid "at least one filename or pattern is required"
-msgstr ""
+msgstr "kræver mindst et filnavn eller mønster"
 
 msgid "at least one of -n/-c is required for -l"
-msgstr ""
+msgstr "brug af -l kræver mindst en af -n/-c"
 
 #, python-format
 msgid "%s: binary file\n"
@@ -4744,10 +4758,10 @@
 msgstr "intet arbejdskatalog: angive venligst en revision"
 
 msgid "repository root cannot be destination"
-msgstr ""
+msgstr "depotets rod kan ikke bruges som destination"
 
 msgid "cannot archive plain files to stdout"
-msgstr ""
+msgstr "flade filer kan ikke arkiveres til standarduddata"
 
 msgid ""
 "reverse effect of earlier changeset\n"
@@ -4806,11 +4820,7 @@
 msgstr "%s er ikke forælder til %s"
 
 msgid "cannot use --parent on non-merge changeset"
-msgstr ""
-
-#, python-format
-msgid "Backed out changeset %s"
-msgstr ""
+msgstr "kan ikke bruge --parent på en ændringer som ikke er en sammenføjning"
 
 #, python-format
 msgid "changeset %s backs out changeset %s\n"
@@ -4824,7 +4834,7 @@
 msgstr ""
 
 msgid "(use \"backout --merge\" if you want to auto-merge)\n"
-msgstr ""
+msgstr "(brug \"backout --merge\" hvis du vil sammenføje automatisk)\n"
 
 msgid ""
 "subdivision search of changesets\n"
@@ -4872,7 +4882,7 @@
 msgstr ""
 
 msgid "(use of 'hg bisect <cmd>' is deprecated)\n"
-msgstr ""
+msgstr "(formen 'hg bisect <kommando>' er forældet)\n"
 
 msgid "incompatible arguments"
 msgstr "inkompatible argumenter"
@@ -4883,7 +4893,7 @@
 
 #, python-format
 msgid "failed to execute %s"
-msgstr ""
+msgstr "kunne ikke køre %s"
 
 #, python-format
 msgid "%s killed"
@@ -4917,10 +4927,27 @@
 "    'hg commit --close-branch' to mark this branch as closed.\n"
 "    "
 msgstr ""
+"angiv eller vis navnet på den aktuelle gren\n"
+"\n"
+"    Uden noget argument vises navnet på den nuværende gren. Med et\n"
+"    argument angives arbejdskatalogets grennavn (grenen eksisterer\n"
+"    ikke i depotet før næste deponering). Det anbefales at den primære\n"
+"    udvikling foretages på 'default' grenen.\n"
+"\n"
+"    Med mindre -f/--force bruges, så vil branch ikke lade dig bruge et\n"
+"    grennavn som allerede eksisterer, selv hvis det er inaktivt.\n"
+"\n"
+"    Brug -C/--clean for at nulstille arbejdskatalogs gren til samme\n"
+"    gren dets forældre-ændring og derved negere end tidligere ændring.\n"
+"\n"
+"    Brug kommandoen 'hg update' for at skifte til en eksisterende\n"
+"    gren. Brug 'hg commit --close-branch' for at markere denne gren\n"
+"    som lukket.\n"
+"    "
 
 #, python-format
 msgid "reset working directory to branch %s\n"
-msgstr ""
+msgstr "nulstil arbejdskataloget til gren %s\n"
 
 msgid "a branch of the same name already exists (use --force to override)"
 msgstr ""
@@ -5003,6 +5030,22 @@
 "    %p   root-relative path name of file being printed\n"
 "    "
 msgstr ""
+"udskriv den aktuelle eller en given revision af filer\n"
+"\n"
+"    Udskriver de angivne filer som de så ud ved den givne revision.\n"
+"    Hvis der ikke angves en revision, så bruges forældre-revisionen\n"
+"    til arbejdskataloget, eller spidsen hvis der ikke er hentet noget\n"
+"    arbejdskatalog.\n"
+"\n"
+"    Output kan gemmes i en fil hvis navn angives med et formatstreng.\n"
+"    Reglerne for formatteringen er de samme som for export-kommandoen\n"
+"    med følgende tilføjelser:\n"
+"\n"
+"    %s   grundnavn for filen som udskrives\n"
+"    %d   katalognavn for filen som blvier udskrevet\n"
+"         eller '.' hvis filen er i katalogets rod\n"
+"    %p   rod-relativ sti for filen som bliver udkrevet\n"
+"    "
 
 msgid ""
 "make a copy of an existing repository\n"
@@ -5525,24 +5568,29 @@
 "\n"
 "    With no arguments, show all repository head changesets.\n"
 "\n"
-"    Repository \"heads\" are changesets that don't have child\n"
-"    changesets. They are where development generally takes place and\n"
-"    are the usual targets for update and merge operations.\n"
+"    Repository \"heads\" are changesets with no child changesets. They are\n"
+"    where development generally takes place and are the usual targets\n"
+"    for update and merge operations.\n"
 "\n"
 "    If one or more REV is given, the \"branch heads\" will be shown for\n"
-"    the named branch associated with that revision. The name of the\n"
-"    branch is called the revision's branch tag.\n"
-"\n"
-"    Branch heads are revisions on a given named branch that do not have\n"
-"    any descendants on the same branch. A branch head could be a true head\n"
-"    or it could be the last changeset on a branch before a new branch\n"
-"    was created. If none of the branch heads are true heads, the branch\n"
-"    is considered inactive. If -c/--closed is specified, also show branch\n"
-"    heads marked closed (see hg commit --close-branch).\n"
-"\n"
-"    If STARTREV is specified only those heads (or branch heads) that\n"
-"    are descendants of STARTREV will be displayed.\n"
-"    "
+"    the named branch associated with the specified changeset(s).\n"
+"\n"
+"    Branch heads are changesets on a named branch with no descendants on\n"
+"    the same branch. A branch head could be a \"true\" (repository) head,\n"
+"    or it could be the last changeset on that branch before it was\n"
+"    merged into another branch, or it could be the last changeset on the\n"
+"    branch before a new branch was created. If none of the branch heads\n"
+"    are true heads, the branch is considered inactive.\n"
+"\n"
+"    If -c/--closed is specified, also show branch heads marked closed\n"
+"    (see hg commit --close-branch).\n"
+"\n"
+"    If STARTREV is specified, only those heads that are descendants of\n"
+"    STARTREV will be displayed.\n"
+"    "
+msgstr ""
+
+msgid "you must specify a branch to use --closed"
 msgstr ""
 
 #, python-format
@@ -6403,17 +6451,9 @@
 msgstr "mærkaten '%s' er ikke en lokal mærkat"
 
 #, python-format
-msgid "Removed tag %s"
-msgstr "Mærke %s er fjernet"
-
-#, python-format
 msgid "tag '%s' already exists (use -f to force)"
 msgstr "mærkaten '%s' eksisterer allerede (brug -f for at gennemtvinge)"
 
-#, python-format
-msgid "Added tag %s for changeset %s"
-msgstr "Tilføjede mærkat %s til ændring %s"
-
 msgid ""
 "list repository tags\n"
 "\n"
@@ -6511,6 +6551,9 @@
 "    Se 'hg help dates' for en liste af gyldige formater til -d/--date.\n"
 "    "
 
+msgid "cannot specify both -c/--check and -C/--clean"
+msgstr "man kan ikke angive både -c/--check og -C/--clean"
+
 msgid "uncommitted local changes"
 msgstr "udeponerede lokale ændringer"
 
@@ -6525,6 +6568,14 @@
 "    integrity of their crosslinks and indices.\n"
 "    "
 msgstr ""
+"verificer depotets integritet\n"
+"\n"
+"    Verificer integreteten af det aktuelle depot.\n"
+"\n"
+"    Dette vil lave en udførlig kontrol af depotets integritet.\n"
+"    Hashværdier og tjeksummer valideres for hver indgang i\n"
+"    historikfilen, manifæstet og fulgte filer. Desuden valideres\n"
+"    integriteten af deres krydslinks og indekser."
 
 msgid "output version and copyright information"
 msgstr "udskriv version- og copyrightinformation"
@@ -6539,6 +6590,11 @@
 "This is free software; see the source for copying conditions. There is NO\n"
 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
 msgstr ""
+"\n"
+"Copyright (C) 2005-2009 Matt Mackall <mpm@selenic.com> og andre\n"
+"Dette er frit programmel; se kildekoden for kopieringsbetingelser. Der\n"
+"gives INGEN GARANTI; ikke engang for SALGBARHED eller EGNETHED FOR\n"
+"NOGET BESTEMT FORMÅL.\n"
 
 msgid "repository root directory or symbolic path name"
 msgstr "depotrodfolder eller symbolsk stinavn"
@@ -6721,10 +6777,10 @@
 msgstr "[-gbsr] [-c KOMMANDO] [REV]"
 
 msgid "set branch name even if it shadows an existing branch"
-msgstr ""
+msgstr "sæt grennavnet selv hvis det overskygger en eksisterende gren"
 
 msgid "reset branch name to parent branch name"
-msgstr ""
+msgstr "nulstil grennavnet til forældre-grennavnet"
 
 msgid "[-fC] [NAME]"
 msgstr "[-fC] [NAVN]"
@@ -6732,8 +6788,8 @@
 msgid "show only branches that have unmerged heads"
 msgstr "vil kun grene som har usammenføjne hoveder"
 
-msgid "show normal and closed heads"
-msgstr "vis normale og lukkede hoveder"
+msgid "show normal and closed branches"
+msgstr "vis normale og lukkede grene"
 
 msgid "[-a]"
 msgstr "[-a]"
@@ -6847,7 +6903,7 @@
 msgstr "[TILVALG]... [-r REV1 [-r REV2]] [FIL]..."
 
 msgid "diff against the second parent"
-msgstr ""
+msgstr "find forskelle i forhold til den anden forældre"
 
 msgid "[OPTION]... [-o OUTFILESPEC] REV..."
 msgstr "[TILVALG]... [-o UDFILSPECIFIKATION] REV..."
@@ -6879,8 +6935,11 @@
 msgid "show only heads which are descendants of REV"
 msgstr "vis kun hoveder som er efterkommere af REV"
 
-msgid "show only the active heads from open branches"
-msgstr "vis kun aktive hoveder fra åbne grene"
+msgid "show only the active branch heads from open branches"
+msgstr "vis kun de aktive grenhoveder fra åbne grene"
+
+msgid "show normal and closed branch heads"
+msgstr "vis normale og lukkede grenhoveder"
 
 msgid "[-r STARTREV] [REV]..."
 msgstr "[-r STARTREV] [REV]..."
@@ -7766,22 +7825,21 @@
 "    Mercurial supports several ways to specify individual revisions.\n"
 "\n"
 "    A plain integer is treated as a revision number. Negative integers\n"
-"    are treated as topological offsets from the tip, with -1 denoting\n"
-"    the tip. As such, negative numbers are only useful if you've\n"
-"    memorized your local tree numbers and want to save typing a single\n"
-"    digit. This editor suggests copy and paste.\n"
+"    are treated as sequential offsets from the tip, with -1 denoting\n"
+"    the tip, -2 denoting the revision prior to the tip, and so forth.\n"
 "\n"
 "    A 40-digit hexadecimal string is treated as a unique revision\n"
 "    identifier.\n"
 "\n"
 "    A hexadecimal string less than 40 characters long is treated as a\n"
-"    unique revision identifier, and referred to as a short-form\n"
+"    unique revision identifier and is referred to as a short-form\n"
 "    identifier. A short-form identifier is only valid if it is the\n"
 "    prefix of exactly one full-length identifier.\n"
 "\n"
-"    Any other string is treated as a tag name, which is a symbolic\n"
-"    name associated with a revision identifier. Tag names may not\n"
-"    contain the \":\" character.\n"
+"    Any other string is treated as a tag or branch name. A tag name is\n"
+"    a symbolic name associated with a revision identifier. A branch\n"
+"    name denotes the tipmost revision of that branch. Tag and branch\n"
+"    names must not contain the \":\" character.\n"
 "\n"
 "    The reserved name \"tip\" is a special tag that always identifies\n"
 "    the most recent revision.\n"
@@ -7943,13 +8001,19 @@
 "    - nonempty: Any text. Returns '(none)' if the string is empty.\n"
 "    - hgdate: Date. Returns the date as a pair of numbers:\n"
 "          \"1157407993 25200\" (Unix timestamp, timezone offset).\n"
-"    - isodate: Date. Returns the date in ISO 8601 format.\n"
+"    - isodate: Date. Returns the date in ISO 8601 format: \"2009-08-18\n"
+"          13:00 +0200\".\n"
+"    - isodatesec: Date. Returns the date in ISO 8601 format, including\n"
+"          seconds: \"2009-08-18 13:00:13 +0200\". See also the\n"
+"          rfc3339date filter.\n"
 "    - localdate: Date. Converts a date to local date.\n"
 "    - obfuscate: Any text. Returns the input text rendered as a\n"
 "          sequence of XML entities.\n"
 "    - person: Any text. Returns the text before an email address.\n"
 "    - rfc822date: Date. Returns a date using the same format used\n"
-"          in email headers.\n"
+"          in email headers: \"Tue, 18 Aug 2009 13:00:13 +0200\".\n"
+"    - rfc3339date: Date. Returns a date using the Internet date format\n"
+"          specified in RFC 3339: \"2009-08-18T13:00:13+02:00\".\n"
 "    - short: Changeset hash. Returns the short form of a changeset\n"
 "          hash, i.e. a 12-byte hexadecimal string.\n"
 "    - shortdate: Date. Returns a date like \"2006-09-18\".\n"
--- a/i18n/pt_BR.po	Sat Oct 03 23:36:08 2009 +0200
+++ b/i18n/pt_BR.po	Sat Oct 03 23:38:10 2009 +0200
@@ -9812,22 +9812,21 @@
 "    Mercurial supports several ways to specify individual revisions.\n"
 "\n"
 "    A plain integer is treated as a revision number. Negative integers\n"
-"    are treated as topological offsets from the tip, with -1 denoting\n"
-"    the tip. As such, negative numbers are only useful if you've\n"
-"    memorized your local tree numbers and want to save typing a single\n"
-"    digit. This editor suggests copy and paste.\n"
+"    are treated as sequential offsets from the tip, with -1 denoting\n"
+"    the tip, -2 denoting the revision prior to the tip, and so forth.\n"
 "\n"
 "    A 40-digit hexadecimal string is treated as a unique revision\n"
 "    identifier.\n"
 "\n"
 "    A hexadecimal string less than 40 characters long is treated as a\n"
-"    unique revision identifier, and referred to as a short-form\n"
+"    unique revision identifier and is referred to as a short-form\n"
 "    identifier. A short-form identifier is only valid if it is the\n"
 "    prefix of exactly one full-length identifier.\n"
 "\n"
-"    Any other string is treated as a tag name, which is a symbolic\n"
-"    name associated with a revision identifier. Tag names may not\n"
-"    contain the \":\" character.\n"
+"    Any other string is treated as a tag or branch name. A tag name is\n"
+"    a symbolic name associated with a revision identifier. A branch\n"
+"    name denotes the tipmost revision of that branch. Tag and branch\n"
+"    names must not contain the \":\" character.\n"
 "\n"
 "    The reserved name \"tip\" is a special tag that always identifies\n"
 "    the most recent revision.\n"
@@ -9846,23 +9845,22 @@
 "    individuais.\n"
 "\n"
 "    Um simples inteiro é tratado como um número de revisão. Inteiros\n"
-"    negativos são tratados como contados topologicamente a partir da\n"
-"    tip, com -1 denotando a tip. Assim, números negativos são úteis\n"
-"    apenas se você memorizou os números de sua árvore local e quiser\n"
-"    evitar digitar um único caractere. Este editor sugere copiar e\n"
-"    colar.\n"
+"    negativos são contados a partir da tip, com -1 denotando a tip,\n"
+"    -2 denotando a revisão anterior à tip, e assim por diante.\n"
 "\n"
 "    Uma string hexadecimal de 40 dígitos é tratada como um\n"
 "    identificador único de revisão.\n"
 "\n"
 "    Uma string hexadecimal de menos de 40 caracteres é tratada como\n"
-"    um identificador único de revisão, e referida como um\n"
-"    identificador curto. Um identificador curto é válido apenas se\n"
-"    for o prefixo de um identificador completo.\n"
-"\n"
-"    Qualquer outra string é tratada como um nome de etiqueta, que é\n"
-"    um nome simbólico associado a um identificador de revisão. Nomes\n"
-"    de etiqueta não podem conter o caractere \":\".\n"
+"    um identificador único de revisão, chamado de identificador\n"
+"    curto. Um identificador curto é válido apenas se for o prefixo\n"
+"    de um identificador completo.\n"
+"\n"
+"    Qualquer outra string é tratada como um nome de etiqueta ou\n"
+"    ramo. Um nome de etiqueta é um nome simbólico associado a um\n"
+"    identificador de revisão. Um nome de ramo denota a revisão mais\n"
+"    recente de tal ramo. Nomes de etiqueta ou de ramo não podem\n"
+"    conter o caractere \":\".\n"
 "\n"
 "    O nome reservado \"tip\" é uma etiqueta especial que sempre\n"
 "    identifica a revisão mais recente.\n"
@@ -10076,13 +10074,19 @@
 "    - nonempty: Any text. Returns '(none)' if the string is empty.\n"
 "    - hgdate: Date. Returns the date as a pair of numbers:\n"
 "          \"1157407993 25200\" (Unix timestamp, timezone offset).\n"
-"    - isodate: Date. Returns the date in ISO 8601 format.\n"
+"    - isodate: Date. Returns the date in ISO 8601 format: \"2009-08-18\n"
+"          13:00 +0200\".\n"
+"    - isodatesec: Date. Returns the date in ISO 8601 format, including\n"
+"          seconds: \"2009-08-18 13:00:13 +0200\". See also the\n"
+"          rfc3339date filter.\n"
 "    - localdate: Date. Converts a date to local date.\n"
 "    - obfuscate: Any text. Returns the input text rendered as a\n"
 "          sequence of XML entities.\n"
 "    - person: Any text. Returns the text before an email address.\n"
 "    - rfc822date: Date. Returns a date using the same format used\n"
-"          in email headers.\n"
+"          in email headers: \"Tue, 18 Aug 2009 13:00:13 +0200\".\n"
+"    - rfc3339date: Date. Returns a date using the Internet date format\n"
+"          specified in RFC 3339: \"2009-08-18T13:00:13+02:00\".\n"
 "    - short: Changeset hash. Returns the short form of a changeset\n"
 "          hash, i.e. a 12-byte hexadecimal string.\n"
 "    - shortdate: Date. Returns a date like \"2006-09-18\".\n"
@@ -10186,14 +10190,17 @@
 "    - nonempty: Qualquer texto. Devolve (none) se o texto for vazio.\n"
 "    - hgdate: Data. Devolve a data como um par de números:\n"
 "          \"1157407993 25200\" (timestamp Unix, defasagem de fuso)\n"
-"    - isodate: Data. Devolve a data em formato ISO 8601.\n"
+"    - isodate: Data. Devolve a data em formato ISO 8601: \"2009-08-18\n"
+"          13:00 +0200\".\n"
 "    - localdate: Data. Converte para data local.\n"
 "    - obfuscate: Qualquer texto. Devolve o texto de entrada\n"
 "          renderizado como uma seqüência de entidades XML.\n"
 "    - person: Qualquer texto. Devolve o texto antes de um endereço\n"
 "          de e-mail.\n"
-"    - rfc822date: Data. Devolve uma data usando o mesmo formato\n"
-"          utilizado em cabeçalhos de e-mail.\n"
+"    - rfc822date: Data. Devolve uma data usando o mesmo formato utilizado\n"
+"          em cabeçalhos de e-mail: \"Tue, 18 Aug 2009 13:00:13 +0200\".\n"
+"    - rfc3339date: Data. Devolve uma data usando o formato de data da\n"
+"          Internet especificado na RFC 3339: \"2009-08-18T13:00:13+02:00\".\n"
 "    - short: Hash do changeset. Devolve a forma curta do hash de\n"
 "          um changeset, ou seja, uma string hexadecimal de 12 bytes.\n"
 "    - shortdate: Data. Devolve uma data como \"2006-09-18\".\n"
--- a/mercurial/cmdutil.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/mercurial/cmdutil.py	Sat Oct 03 23:38:10 2009 +0200
@@ -546,24 +546,26 @@
 
     return errors
 
-def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None):
+def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
+    runargs=None):
     '''Run a command as a service.'''
 
     if opts['daemon'] and not opts['daemon_pipefds']:
         rfd, wfd = os.pipe()
-        args = sys.argv[:]
-        args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
+        if not runargs:
+            runargs = sys.argv[:]
+        runargs.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
         # Don't pass --cwd to the child process, because we've already
         # changed directory.
-        for i in xrange(1,len(args)):
-            if args[i].startswith('--cwd='):
-                del args[i]
+        for i in xrange(1,len(runargs)):
+            if runargs[i].startswith('--cwd='):
+                del runargs[i]
                 break
-            elif args[i].startswith('--cwd'):
-                del args[i:i+2]
+            elif runargs[i].startswith('--cwd'):
+                del runargs[i:i+2]
                 break
         pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
-                         args[0], args)
+                         runargs[0], runargs)
         os.close(wfd)
         os.read(rfd, 1)
         if parentfn:
--- a/mercurial/commands.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/mercurial/commands.py	Sat Oct 03 23:38:10 2009 +0200
@@ -1356,23 +1356,25 @@
 
     With no arguments, show all repository head changesets.
 
-    Repository "heads" are changesets that don't have child
-    changesets. They are where development generally takes place and
-    are the usual targets for update and merge operations.
+    Repository "heads" are changesets with no child changesets. They are
+    where development generally takes place and are the usual targets
+    for update and merge operations.
 
     If one or more REV is given, the "branch heads" will be shown for
-    the named branch associated with that revision. The name of the
-    branch is called the revision's branch tag.
-
-    Branch heads are revisions on a given named branch that do not have
-    any descendants on the same branch. A branch head could be a true head
-    or it could be the last changeset on a branch before a new branch
-    was created. If none of the branch heads are true heads, the branch
-    is considered inactive. If -c/--closed is specified, also show branch
-    heads marked closed (see hg commit --close-branch).
-
-    If STARTREV is specified only those heads (or branch heads) that
-    are descendants of STARTREV will be displayed.
+    the named branch associated with the specified changeset(s).
+
+    Branch heads are changesets on a named branch with no descendants on
+    the same branch. A branch head could be a "true" (repository) head,
+    or it could be the last changeset on that branch before it was
+    merged into another branch, or it could be the last changeset on the
+    branch before a new branch was created. If none of the branch heads
+    are true heads, the branch is considered inactive.
+
+    If -c/--closed is specified, also show branch heads marked closed
+    (see hg commit --close-branch).
+
+    If STARTREV is specified, only those heads that are descendants of
+    STARTREV will be displayed.
     """
     if opts.get('rev'):
         start = repo.lookup(opts['rev'])
--- a/mercurial/dirstate.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/mercurial/dirstate.py	Sat Oct 03 23:38:10 2009 +0200
@@ -38,6 +38,9 @@
 class dirstate(object):
 
     def __init__(self, opener, ui, root):
+        '''Create a new dirstate object.  opener is an open()-like callable
+        that can be used to open the dirstate file; root is the root of the
+        directory tracked by the dirstate.'''
         self._opener = opener
         self._root = root
         self._rootdir = os.path.join(root, '')
@@ -47,6 +50,8 @@
 
     @propertycache
     def _map(self):
+        '''Return the dirstate contents as a map from filename to
+        (state, mode, size, time).'''
         self._read()
         return self._map
 
@@ -169,12 +174,14 @@
         return path
 
     def __getitem__(self, key):
-        ''' current states:
-        n  normal
-        m  needs merging
-        r  marked for removal
-        a  marked for addition
-        ?  not tracked'''
+        '''Return the current state of key (a filename) in the dirstate.
+        States are:
+          n  normal
+          m  needs merging
+          r  marked for removal
+          a  marked for addition
+          ?  not tracked
+        '''
         return self._map.get(key, ("?",))[0]
 
     def __contains__(self, key):
@@ -373,13 +380,9 @@
             return
         st = self._opener("dirstate", "w", atomictemp=True)
 
-        try:
-            gran = int(self._ui.config('dirstate', 'granularity', 1))
-        except ValueError:
-            gran = 1
-        if gran > 0:
-            hlimit = util.fstat(st).st_mtime
-            llimit = hlimit - gran
+        # use the modification time of the newly created temporary file as the
+        # filesystem's notion of 'now'
+        now = int(util.fstat(st).st_mtime)
 
         cs = cStringIO.StringIO()
         copymap = self._copymap
@@ -389,9 +392,19 @@
         for f, e in self._map.iteritems():
             if f in copymap:
                 f = "%s\0%s" % (f, copymap[f])
-            if gran > 0 and e[0] == 'n' and llimit < e[3] <= hlimit:
-                # file was updated too recently, ignore stat data
-                e = (e[0], 0, -1, -1)
+
+            if e[0] == 'n' and e[3] == now:
+                # The file was last modified "simultaneously" with the current
+                # write to dirstate (i.e. within the same second for file-
+                # systems with a granularity of 1 sec). This commonly happens
+                # for at least a couple of files on 'update'.
+                # The user could change the file without changing its size
+                # within the same second. Invalidate the file's stat data in
+                # dirstate, forcing future 'status' calls to compare the
+                # contents of the file. This prevents mistakenly treating such
+                # files as clean.
+                e = (e[0], 0, -1, -1)   # mark entry as 'unset'
+
             e = pack(_format, e[0], e[1], e[2], e[3], len(f))
             write(e)
             write(f)
@@ -411,11 +424,11 @@
 
     def walk(self, match, unknown, ignored):
         '''
-        walk recursively through the directory tree, finding all files
-        matched by the match function
+        Walk recursively through the directory tree, finding all files
+        matched by match.
 
-        results are yielded in a tuple (filename, stat), where stat
-        and st is the stat result if the file was found in the directory.
+        Return a dict mapping filename to stat-like object (either
+        mercurial.osutil.stat instance or return value of os.stat()).
         '''
 
         def fwarn(f, msg):
@@ -553,12 +566,38 @@
         return results
 
     def status(self, match, ignored, clean, unknown):
+        '''Determine the status of the working copy relative to the
+        dirstate and return a tuple of lists (unsure, modified, added,
+        removed, deleted, unknown, ignored, clean), where:
+
+          unsure:
+            files that might have been modified since the dirstate was
+            written, but need to be read to be sure (size is the same
+            but mtime differs)
+          modified:
+            files that have definitely been modified since the dirstate
+            was written (different size or mode)
+          added:
+            files that have been explicitly added with hg add
+          removed:
+            files that have been explicitly removed with hg remove
+          deleted:
+            files that have been deleted through other means ("missing")
+          unknown:
+            files not in the dirstate that are not ignored
+          ignored:
+            files not in the dirstate that are ignored
+            (by _dirignore())
+          clean:
+            files that have definitely not been modified since the
+            dirstate was written
+        '''
         listignored, listclean, listunknown = ignored, clean, unknown
         lookup, modified, added, unknown, ignored = [], [], [], [], []
         removed, deleted, clean = [], [], []
 
         dmap = self._map
-        ladd = lookup.append
+        ladd = lookup.append            # aka "unsure"
         madd = modified.append
         aadd = added.append
         uadd = unknown.append
--- a/mercurial/posix.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/mercurial/posix.py	Sat Oct 03 23:38:10 2009 +0200
@@ -165,17 +165,11 @@
         return inst.errno != errno.ESRCH
 
 def explain_exit(code):
-    """return a 2-tuple (desc, code) describing a process's status"""
-    if os.WIFEXITED(code):
-        val = os.WEXITSTATUS(code)
-        return _("exited with status %d") % val, val
-    elif os.WIFSIGNALED(code):
-        val = os.WTERMSIG(code)
-        return _("killed by signal %d") % val, val
-    elif os.WIFSTOPPED(code):
-        val = os.WSTOPSIG(code)
-        return _("stopped by signal %d") % val, val
-    raise ValueError(_("invalid exit code"))
+    """return a 2-tuple (desc, code) describing a subprocess status
+    (codes from kill are negative - not os.system/wait encoding)"""
+    if code >= 0:
+        return _("exited with status %d") % code, code
+    return _("killed by signal %d") % -code, -code
 
 def isowner(st):
     """Return True if the stat object st is from the current user."""
--- a/mercurial/streamclone.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/mercurial/streamclone.py	Sat Oct 03 23:38:10 2009 +0200
@@ -48,8 +48,7 @@
         try:
             repo.ui.debug('scanning\n')
             for name, ename, size in repo.store.walk():
-                # for backwards compat, name was partially encoded
-                entries.append((store.encodedir(name), size))
+                entries.append((name, size))
                 total_bytes += size
         finally:
             lock.release()
@@ -62,6 +61,7 @@
     yield '%d %d\n' % (len(entries), total_bytes)
     for name, size in entries:
         repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
-        yield '%s\0%d\n' % (name, size)
+        # partially encode name over the wire for backwards compat
+        yield '%s\0%d\n' % (store.encodedir(name), size)
         for chunk in util.filechunkiter(repo.sopener(name), limit=size):
             yield chunk
--- a/mercurial/subrepo.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/mercurial/subrepo.py	Sat Oct 03 23:38:10 2009 +0200
@@ -167,7 +167,7 @@
         self._repo.ui.note(_('removing subrepo %s\n') % self._path)
         hg.clean(self._repo, node.nullid, False)
 
-    def get(self, state):
+    def _get(self, state):
         source, revision = state
         try:
             self._repo.lookup(revision)
@@ -178,9 +178,13 @@
             other = hg.repository(self._repo.ui, srcurl)
             self._repo.pull(other)
 
+    def get(self, state):
+        self._get(state)
+        source, revision = state
         hg.clean(self._repo, revision, False)
 
     def merge(self, state):
+        self._get(state)
         hg.merge(self._repo, state[1], remind=False)
 
     def push(self, force):
--- a/mercurial/util.py	Sat Oct 03 23:36:08 2009 +0200
+++ b/mercurial/util.py	Sat Oct 03 23:38:10 2009 +0200
@@ -357,41 +357,26 @@
         if val is True:
             return '1'
         return str(val)
-    oldenv = {}
-    for k in environ:
-        oldenv[k] = os.environ.get(k)
-    if cwd is not None:
-        oldcwd = os.getcwd()
     origcmd = cmd
     if os.name == 'nt':
         cmd = '"%s"' % cmd
-    try:
-        for k, v in environ.iteritems():
-            os.environ[k] = py2shell(v)
-        os.environ['HG'] = hgexecutable()
-        if cwd is not None and oldcwd != cwd:
-            os.chdir(cwd)
-        rc = os.system(cmd)
-        if sys.platform == 'OpenVMS' and rc & 1:
-            rc = 0
-        if rc and onerr:
-            errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
-                                explain_exit(rc)[0])
-            if errprefix:
-                errmsg = '%s: %s' % (errprefix, errmsg)
-            try:
-                onerr.warn(errmsg + '\n')
-            except AttributeError:
-                raise onerr(errmsg)
-        return rc
-    finally:
-        for k, v in oldenv.iteritems():
-            if v is None:
-                del os.environ[k]
-            else:
-                os.environ[k] = v
-        if cwd is not None and oldcwd != cwd:
-            os.chdir(oldcwd)
+    env = dict(os.environ)
+    env.update((k, py2shell(v)) for k, v in environ.iteritems())
+    env['HG'] = hgexecutable()
+    rc = subprocess.call(cmd, shell=True, close_fds=closefds,
+                         env=env, cwd=cwd)
+    if sys.platform == 'OpenVMS' and rc & 1:
+        rc = 0
+    if rc and onerr:
+        errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
+                            explain_exit(rc)[0])
+        if errprefix:
+            errmsg = '%s: %s' % (errprefix, errmsg)
+        try:
+            onerr.warn(errmsg + '\n')
+        except AttributeError:
+            raise onerr(errmsg)
+    return rc
 
 def checksignature(func):
     '''wrap a function with code to check for calling errors'''
@@ -1280,9 +1265,12 @@
     padding = '\n' + ' ' * hangindent
     # To avoid corrupting multi-byte characters in line, we must wrap
     # a Unicode string instead of a bytestring.
-    u = line.decode(encoding.encoding)
-    w = padding.join(textwrap.wrap(u, width=width - hangindent))
-    return w.encode(encoding.encoding)
+    try:
+        u = line.decode(encoding.encoding)
+        w = padding.join(textwrap.wrap(u, width=width - hangindent))
+        return w.encode(encoding.encoding)
+    except UnicodeDecodeError:
+        return padding.join(textwrap.wrap(line, width=width - hangindent))
 
 def iterlines(iterator):
     for chunk in iterator:
--- a/tests/test-http	Sat Oct 03 23:36:08 2009 +0200
+++ b/tests/test-http	Sat Oct 03 23:38:10 2009 +0200
@@ -5,6 +5,11 @@
 hg init test
 cd test
 echo foo>foo
+mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
+echo foo>foo.d/foo
+echo bar>foo.d/bAr.hg.d/BaR
+echo bar>foo.d/baR.d.hg/bAR
+
 hg commit -A -m 1
 hg --config server.uncompressed=True serve -p $HGPORT -d --pid-file=../hg1.pid
 hg serve -p $HGPORT1 -d --pid-file=../hg2.pid
--- a/tests/test-http.out	Sat Oct 03 23:36:08 2009 +0200
+++ b/tests/test-http.out	Sat Oct 03 23:38:10 2009 +0200
@@ -1,4 +1,7 @@
 adding foo
+adding foo.d/bAr.hg.d/BaR
+adding foo.d/baR.d.hg/bAR
+adding foo.d/foo
 abort: cannot start server at ':20060':
 % clone via stream
 streaming all changes
@@ -10,31 +13,31 @@
 checking manifests
 crosschecking files in changesets and manifests
 checking files
-1 files, 1 changesets, 1 total revisions
+4 files, 1 changesets, 4 total revisions
 % try to clone via stream, should use pull instead
 requesting all changes
 adding changesets
 adding manifests
 adding file changes
-added 1 changesets with 1 changes to 1 files
+added 1 changesets with 4 changes to 4 files
 updating working directory
-1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 % clone via pull
 requesting all changes
 adding changesets
 adding manifests
 adding file changes
-added 1 changesets with 1 changes to 1 files
+added 1 changesets with 4 changes to 4 files
 updating working directory
-1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 checking changesets
 checking manifests
 crosschecking files in changesets and manifests
 checking files
-1 files, 1 changesets, 1 total revisions
+4 files, 1 changesets, 4 total revisions
 adding bar
 % pull
-changegroup hook: HG_NODE=cfbd11a1fa315300a080c3de8fe36b0fc5820acf HG_SOURCE=pull HG_URL=http://localhost/ 
+changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=http://localhost/ 
 pulling from http://localhost/
 searching for changes
 adding changesets
--- a/tests/test-inotify-issue1208.out	Sat Oct 03 23:36:08 2009 +0200
+++ b/tests/test-inotify-issue1208.out	Sat Oct 03 23:38:10 2009 +0200
@@ -1,5 +1,5 @@
 % fail
-could not talk to new inotify server: No such file or directory
+abort: could not start server: File exists
 abort: could not start server: File exists
 % inserve
 % status
--- a/tests/test-keyword	Sat Oct 03 23:36:08 2009 +0200
+++ b/tests/test-keyword	Sat Oct 03 23:38:10 2009 +0200
@@ -48,6 +48,11 @@
 echo % cat
 cat a b
 
+echo % no kwfiles
+hg kwfiles
+echo % untracked candidates
+hg -v kwfiles --unknown
+
 echo % addremove
 hg addremove
 echo % status
@@ -162,6 +167,10 @@
 
 echo % kwfiles
 hg kwfiles
+echo % ignored files
+hg -v kwfiles --ignore
+echo % all files
+hg kwfiles --all
 
 echo % diff --rev
 hg diff --rev 1 | grep -v 'b/c'
--- a/tests/test-keyword.out	Sat Oct 03 23:36:08 2009 +0200
+++ b/tests/test-keyword.out	Sat Oct 03 23:38:10 2009 +0200
@@ -42,6 +42,9 @@
 do not process $Id:
 xxx $
 ignore $Id$
+% no kwfiles
+% untracked candidates
+k a
 % addremove
 adding a
 adding b
@@ -181,6 +184,14 @@
 % kwfiles
 a
 c
+% ignored files
+I b
+I sym
+% all files
+K a
+K c
+I b
+I sym
 % diff --rev
 diff -r ef63ca68695b c
 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
--- a/tests/test-notify.out	Sat Oct 03 23:36:08 2009 +0200
+++ b/tests/test-notify.out	Sat Oct 03 23:38:10 2009 +0200
@@ -35,6 +35,7 @@
   diffstat = True        # add a diffstat before the diff content
   sources = serve        # notify if source of incoming changes in this list
                          # (serve == ssh or http, push, pull, bundle)
+  merge = False          # send notification for merges (default True)
   [email]
   from = user@host.com   # email address to send as if none given
   [web]