merge with stable
authorMatt Mackall <mpm@selenic.com>
Thu, 21 Nov 2013 15:08:30 -0600
changeset 20079 d4838302305c
parent 20078 43e872a35f8a (diff)
parent 20076 faa4b3fc4197 (current diff)
child 20080 c845b1a95eed
merge with stable
mercurial/templatekw.py
tests/test-command-template.t
--- a/.hgignore	Thu Nov 21 11:30:52 2013 -0600
+++ b/.hgignore	Thu Nov 21 15:08:30 2013 -0600
@@ -1,6 +1,7 @@
 syntax: glob
 
 *.elc
+*.tmp
 *.orig
 *.rej
 *~
--- a/Makefile	Thu Nov 21 11:30:52 2013 -0600
+++ b/Makefile	Thu Nov 21 15:08:30 2013 -0600
@@ -53,7 +53,8 @@
 
 clean:
 	-$(PYTHON) setup.py clean --all # ignore errors from this command
-	find . \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
+	find contrib doc hgext i18n mercurial tests \
+		\( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
 	rm -f $(addprefix mercurial/,$(notdir $(wildcard mercurial/pure/[a-z]*.py)))
 	rm -f MANIFEST MANIFEST.in mercurial/__version__.py tests/*.err
 	rm -rf build mercurial/locale
@@ -123,7 +124,10 @@
 	$(PYTHON) i18n/posplit i18n/hg.pot
 
 %.po: i18n/hg.pot
-	msgmerge --no-location --update $@ $^
+        # work on a temporary copy for never having a half completed target
+	cp $@ $@.tmp
+	msgmerge --no-location --update $@.tmp $^
+	mv -f $@~ $@
 
 .PHONY: help all local build doc clean install install-bin install-doc \
 	install-home install-home-bin install-home-doc dist dist-notests tests \
--- a/contrib/check-code.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/contrib/check-code.py	Thu Nov 21 15:08:30 2013 -0600
@@ -141,17 +141,15 @@
     (r'^  saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
     (r'^  changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
      winglobmsg),
-    (r'^  pulling from \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
-    (r'^  reverting .*/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
-    (r'^  cloning subrepo \S+/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
-    (r'^  pushing to \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
-    (r'^  pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg,
-     '\$TESTTMP/unix-repo$'),
+    (r'^  pulling from \$TESTTMP/.*[^)]$', winglobmsg,
+     '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
+    (r'^  reverting .*/.*[^)]$', winglobmsg),
+    (r'^  cloning subrepo \S+/.*[^)]$', winglobmsg),
+    (r'^  pushing to \$TESTTMP/.*[^)]$', winglobmsg),
+    (r'^  pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
     (r'^  moving \S+/.*[^)]$', winglobmsg),
-    (r'^  no changes made to subrepo since.*/.*[^)]$',
-     winglobmsg, '\$TESTTMP/unix-repo$'),
-    (r'^  .*: largefile \S+ not available from file:.*/.*[^)]$',
-     winglobmsg, '\$TESTTMP/unix-repo$'),
+    (r'^  no changes made to subrepo since.*/.*[^)]$', winglobmsg),
+    (r'^  .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
   ],
   # warnings
   [
@@ -264,7 +262,7 @@
     (r'[\s\(](open|file)\([^)]*\)\.read\(',
      "use util.readfile() instead"),
     (r'[\s\(](open|file)\([^)]*\)\.write\(',
-     "use util.readfile() instead"),
+     "use util.writefile() instead"),
     (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
      "always assign an opened file to a variable, and close it afterwards"),
     (r'[\s\(](open|file)\([^)]*\)\.',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/editmerge	Thu Nov 21 15:08:30 2013 -0600
@@ -0,0 +1,58 @@
+#!/bin/bash
+# A simple script for opening merge conflicts in the editor.
+# Use the following Mercurial settings to enable it.
+#
+# [ui]
+# merge = editmerge
+#
+# [merge-tools]
+# editmerge.args=$output
+# editmerge.check=changed
+# editmerge.premerge=keep
+
+FILE=$1
+
+getlines() {
+  grep -n "<<<<<<" $FILE | cut -f1 -d:
+}
+
+# editor preference loosely based on http://mercurial.selenic.com/wiki/editor
+# hg showconfig is at the bottom though, since it's slow to run (0.15 seconds)
+ED=$HGEDITOR
+if [ "$ED" = "" ] ; then
+  ED=$VISUAL
+fi
+if [ "$ED" = "" ] ; then
+  ED=$EDITOR
+fi
+if [ "$ED" = "" ] ; then
+  ED=$(hg showconfig ui.editor)
+fi
+if [ "$ED" = "" ] ; then
+  echo "merge failed - unable to find editor"
+  exit 1
+fi
+
+if [ "$ED" = "emacs" ] || [ "$ED" = "nano" ] || [ "$ED" = "vim" ] ; then
+  FIRSTLINE=$(getlines | head -n 1)
+  PREVIOUSLINE=""
+
+  # open the editor to the first conflict until there are no more
+  # or the user stops editing the file
+  while [ ! "$FIRSTLINE" = "" ] && [ ! "$FIRSTLINE" = "$PREVIOUSLINE" ] ; do
+    $ED +$FIRSTLINE $FILE
+    PREVIOUSLINE=$FIRSTLINE
+    FIRSTLINE=$(getlines | head -n 1)
+  done
+else
+  $ED $FILE
+fi
+
+# get the line numbers of the remaining conflicts
+CONFLICTS=$(getlines | sed ':a;N;$!ba;s/\n/, /g')
+if [ ! "$CONFLICTS" = "" ] ; then
+  echo "merge failed - resolve the conflicts (line $CONFLICTS) then use 'hg resolve --mark'"
+  exit 1
+fi
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/import-checker.py	Thu Nov 21 15:08:30 2013 -0600
@@ -0,0 +1,221 @@
+import ast
+import os
+import sys
+
+def dotted_name_of_path(path):
+    """Given a relative path to a source file, return its dotted module name.
+
+
+    >>> dotted_name_of_path('mercurial/error.py')
+    'mercurial.error'
+    """
+    parts = path.split('/')
+    parts[-1] = parts[-1][:-3] # remove .py
+    return '.'.join(parts)
+
+
+def list_stdlib_modules():
+    """List the modules present in the stdlib.
+
+    >>> mods = set(list_stdlib_modules())
+    >>> 'BaseHTTPServer' in mods
+    True
+
+    os.path isn't really a module, so it's missing:
+
+    >>> 'os.path' in mods
+    False
+
+    sys requires special treatment, because it's baked into the
+    interpreter, but it should still appear:
+
+    >>> 'sys' in mods
+    True
+
+    >>> 'collections' in mods
+    True
+
+    >>> 'cStringIO' in mods
+    True
+    """
+    for m in sys.builtin_module_names:
+        yield m
+    # These modules only exist on windows, but we should always
+    # consider them stdlib.
+    for m in ['msvcrt', '_winreg']:
+        yield m
+    # These get missed too
+    for m in 'ctypes', 'email':
+        yield m
+    yield 'builtins' # python3 only
+    for libpath in sys.path:
+        # We want to walk everything in sys.path that starts with
+        # either sys.prefix or sys.exec_prefix.
+        if not (libpath.startswith(sys.prefix)
+                or libpath.startswith(sys.exec_prefix)):
+            continue
+        if 'site-packages' in libpath:
+            continue
+        for top, dirs, files in os.walk(libpath):
+            for name in files:
+                if name == '__init__.py':
+                    continue
+                if not (name.endswith('.py') or name.endswith('.so')):
+                    continue
+                full_path = os.path.join(top, name)
+                if 'site-packages' in full_path:
+                    continue
+                rel_path = full_path[len(libpath) + 1:]
+                mod = dotted_name_of_path(rel_path)
+                yield mod
+
+stdlib_modules = set(list_stdlib_modules())
+
+def imported_modules(source, ignore_nested=False):
+    """Given the source of a file as a string, yield the names
+    imported by that file.
+
+    Args:
+      source: The python source to examine as a string.
+      ignore_nested: If true, import statements that do not start in
+                     column zero will be ignored.
+
+    Returns:
+      A list of module names imported by the given source.
+
+    >>> sorted(imported_modules(
+    ...         'import foo ; from baz import bar; import foo.qux'))
+    ['baz.bar', 'foo', 'foo.qux']
+    >>> sorted(imported_modules(
+    ... '''import foo
+    ... def wat():
+    ...     import bar
+    ... ''', ignore_nested=True))
+    ['foo']
+    """
+    for node in ast.walk(ast.parse(source)):
+        if ignore_nested and getattr(node, 'col_offset', 0) > 0:
+            continue
+        if isinstance(node, ast.Import):
+            for n in node.names:
+                yield n.name
+        elif isinstance(node, ast.ImportFrom):
+            prefix = node.module + '.'
+            for n in node.names:
+                yield prefix + n.name
+
+def verify_stdlib_on_own_line(source):
+    """Given some python source, verify that stdlib imports are done
+    in separate statements from relative local module imports.
+
+    Observing this limitation is important as it works around an
+    annoying lib2to3 bug in relative import rewrites:
+    http://bugs.python.org/issue19510.
+
+    >>> list(verify_stdlib_on_own_line('import sys, foo'))
+    ['mixed stdlib and relative imports:\\n   foo, sys']
+    >>> list(verify_stdlib_on_own_line('import sys, os'))
+    []
+    >>> list(verify_stdlib_on_own_line('import foo, bar'))
+    []
+    """
+    for node in ast.walk(ast.parse(source)):
+        if isinstance(node, ast.Import):
+            from_stdlib = {}
+            for n in node.names:
+                from_stdlib[n.name] = n.name in stdlib_modules
+            num_std = len([x for x in from_stdlib.values() if x])
+            if num_std not in (len(from_stdlib.values()), 0):
+                yield ('mixed stdlib and relative imports:\n   %s' %
+                       ', '.join(sorted(from_stdlib.iterkeys())))
+
+class CircularImport(Exception):
+    pass
+
+
+def cyclekey(names):
+    return tuple(sorted(set(names)))
+
+def check_one_mod(mod, imports, path=None, ignore=None):
+    if path is None:
+        path = []
+    if ignore is None:
+        ignore = []
+    path = path + [mod]
+    for i in sorted(imports.get(mod, [])):
+        if i not in stdlib_modules:
+            i = mod.rsplit('.', 1)[0] + '.' + i
+        if i in path:
+            firstspot = path.index(i)
+            cycle = path[firstspot:] + [i]
+            if cyclekey(cycle) not in ignore:
+                raise CircularImport(cycle)
+            continue
+        check_one_mod(i, imports, path=path, ignore=ignore)
+
+def rotatecycle(cycle):
+    """arrange a cycle so that the lexicographically first module listed first
+
+    >>> rotatecycle(['foo', 'bar', 'foo'])
+    ['bar', 'foo', 'bar']
+    """
+    lowest = min(cycle)
+    idx = cycle.index(lowest)
+    return cycle[idx:] + cycle[1:idx] + [lowest]
+
+def find_cycles(imports):
+    """Find cycles in an already-loaded import graph.
+
+    >>> imports = {'top.foo': ['bar', 'os.path', 'qux'],
+    ...            'top.bar': ['baz', 'sys'],
+    ...            'top.baz': ['foo'],
+    ...            'top.qux': ['foo']}
+    >>> print '\\n'.join(sorted(find_cycles(imports)))
+    top.bar -> top.baz -> top.foo -> top.bar -> top.bar
+    top.foo -> top.qux -> top.foo -> top.foo
+    """
+    cycles = {}
+    for mod in sorted(imports.iterkeys()):
+        try:
+            check_one_mod(mod, imports, ignore=cycles)
+        except CircularImport, e:
+            cycle = e.args[0]
+            cycles[cyclekey(cycle)] = ' -> '.join(rotatecycle(cycle))
+    return cycles.values()
+
+def _cycle_sortkey(c):
+    return len(c), c
+
+def main(argv):
+    if len(argv) < 2:
+        print 'Usage: %s file [file] [file] ...'
+        return 1
+    used_imports = {}
+    any_errors = False
+    for source_path in argv[1:]:
+        f = open(source_path)
+        modname = dotted_name_of_path(source_path)
+        src = f.read()
+        used_imports[modname] = sorted(
+            imported_modules(src, ignore_nested=True))
+        for error in verify_stdlib_on_own_line(src):
+            any_errors = True
+            print source_path, error
+        f.close()
+    cycles = find_cycles(used_imports)
+    if cycles:
+        firstmods = set()
+        for c in sorted(cycles, key=_cycle_sortkey):
+            first = c.split()[0]
+            # As a rough cut, ignore any cycle that starts with the
+            # same module as some other cycle. Otherwise we see lots
+            # of cycles that are effectively duplicates.
+            if first in firstmods:
+                continue
+            print 'Import cycle:', c
+            firstmods.add(first)
+        any_errors = True
+    return not any_errors
+
+if __name__ == '__main__':
+    sys.exit(int(main(sys.argv)))
--- a/contrib/perf.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/contrib/perf.py	Thu Nov 21 15:08:30 2013 -0600
@@ -386,7 +386,7 @@
     allfilters = []
     while possiblefilters:
         for name in possiblefilters:
-            subset = repoview.subsettable.get(name)
+            subset = branchmap.subsettable.get(name)
             if subset not in possiblefilters:
                 break
         else:
--- a/hgext/convert/filemap.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/hgext/convert/filemap.py	Thu Nov 21 15:08:30 2013 -0600
@@ -10,12 +10,20 @@
 from mercurial import util, error
 from common import SKIPREV, converter_source
 
-def rpairs(name):
-    e = len(name)
-    while e != -1:
-        yield name[:e], name[e + 1:]
-        e = name.rfind('/', 0, e)
-    yield '.', name
+def rpairs(path):
+    '''Yield tuples with path split at '/', starting with the full path.
+    No leading, trailing or double '/', please.
+    >>> for x in rpairs('foo/bar/baz'): print x
+    ('foo/bar/baz', '')
+    ('foo/bar', 'baz')
+    ('foo', 'bar/baz')
+    ('.', 'foo/bar/baz')
+    '''
+    i = len(path)
+    while i != -1:
+        yield path[:i], path[i + 1:]
+        i = path.rfind('/', 0, i)
+    yield '.', path
 
 def normalize(path):
     ''' We use posixpath.normpath to support cross-platform path format.
--- a/hgext/keyword.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/hgext/keyword.py	Thu Nov 21 15:08:30 2013 -0600
@@ -84,7 +84,7 @@
 
 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
 from mercurial import localrepo, match, patch, templatefilters, templater, util
-from mercurial import scmutil
+from mercurial import scmutil, pathutil
 from mercurial.hgweb import webcommands
 from mercurial.i18n import _
 import os, re, shutil, tempfile
@@ -673,7 +673,7 @@
                 expansion. '''
                 source = repo.dirstate.copied(dest)
                 if 'l' in wctx.flags(source):
-                    source = scmutil.canonpath(repo.root, cwd,
+                    source = pathutil.canonpath(repo.root, cwd,
                                                os.path.realpath(source))
                 return kwt.match(source)
 
--- a/hgext/largefiles/overrides.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/hgext/largefiles/overrides.py	Thu Nov 21 15:08:30 2013 -0600
@@ -12,7 +12,7 @@
 import copy
 
 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
-    node, archival, error, merge, discovery
+    node, archival, error, merge, discovery, pathutil
 from mercurial.i18n import _
 from mercurial.node import hex
 from hgext import rebase
@@ -469,7 +469,7 @@
         return orig(ui, repo, pats, opts, rename)
 
     def makestandin(relpath):
-        path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
+        path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
         return os.path.join(repo.wjoin(lfutil.standin(path)))
 
     fullpats = scmutil.expandpats(pats)
--- a/hgext/mq.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/hgext/mq.py	Thu Nov 21 15:08:30 2013 -0600
@@ -2565,8 +2565,10 @@
         ph = patchheader(q.join(parent), q.plainmode)
         message, user = ph.message, ph.user
         for msg in messages:
-            message.append('* * *')
-            message.extend(msg)
+            if msg:
+                if message:
+                    message.append('* * *')
+                message.extend(msg)
         message = '\n'.join(message)
 
     if opts.get('edit'):
--- a/hgext/transplant.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/hgext/transplant.py	Thu Nov 21 15:08:30 2013 -0600
@@ -154,7 +154,7 @@
                     # transplants before them fail.
                     domerge = True
                     if not hasnode(repo, node):
-                        repo.pull(source, heads=[node])
+                        repo.pull(source.peer(), heads=[node])
 
                 skipmerge = False
                 if parents[1] != revlog.nullid:
--- a/mercurial/ancestor.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/ancestor.py	Thu Nov 21 15:08:30 2013 -0600
@@ -5,7 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import heapq, util
+import heapq
+import util
 from node import nullrev
 
 def ancestors(pfunc, *orignodes):
--- a/mercurial/bookmarks.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/bookmarks.py	Thu Nov 21 15:08:30 2013 -0600
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 from mercurial.i18n import _
-from mercurial.node import hex
+from mercurial.node import hex, bin
 from mercurial import encoding, error, util, obsolete
 import errno
 
@@ -239,49 +239,176 @@
     finally:
         w.release()
 
+def compare(repo, srcmarks, dstmarks,
+            srchex=None, dsthex=None, targets=None):
+    '''Compare bookmarks between srcmarks and dstmarks
+
+    This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
+    differ, invalid)", each are list of bookmarks below:
+
+    :addsrc:  added on src side (removed on dst side, perhaps)
+    :adddst:  added on dst side (removed on src side, perhaps)
+    :advsrc:  advanced on src side
+    :advdst:  advanced on dst side
+    :diverge: diverge
+    :differ:  changed, but changeset referred on src is unknown on dst
+    :invalid: unknown on both side
+
+    Each elements of lists in result tuple is tuple "(bookmark name,
+    changeset ID on source side, changeset ID on destination
+    side)". Each changeset IDs are 40 hexadecimal digit string or
+    None.
+
+    Changeset IDs of tuples in "addsrc", "adddst", "differ" or
+     "invalid" list may be unknown for repo.
+
+    This function expects that "srcmarks" and "dstmarks" return
+    changeset ID in 40 hexadecimal digit string for specified
+    bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
+    binary value), "srchex" or "dsthex" should be specified to convert
+    into such form.
+
+    If "targets" is specified, only bookmarks listed in it are
+    examined.
+    '''
+    if not srchex:
+        srchex = lambda x: x
+    if not dsthex:
+        dsthex = lambda x: x
+
+    if targets:
+        bset = set(targets)
+    else:
+        srcmarkset = set(srcmarks)
+        dstmarkset = set(dstmarks)
+        bset = srcmarkset ^ dstmarkset
+        for b in srcmarkset & dstmarkset:
+            if srchex(srcmarks[b]) != dsthex(dstmarks[b]):
+                bset.add(b)
+
+    results = ([], [], [], [], [], [], [])
+    addsrc = results[0].append
+    adddst = results[1].append
+    advsrc = results[2].append
+    advdst = results[3].append
+    diverge = results[4].append
+    differ = results[5].append
+    invalid = results[6].append
+
+    for b in sorted(bset):
+        if b not in srcmarks:
+            if b in dstmarks:
+                adddst((b, None, dsthex(dstmarks[b])))
+            else:
+                invalid((b, None, None))
+        elif b not in dstmarks:
+            addsrc((b, srchex(srcmarks[b]), None))
+        else:
+            scid = srchex(srcmarks[b])
+            dcid = dsthex(dstmarks[b])
+            if scid in repo and dcid in repo:
+                sctx = repo[scid]
+                dctx = repo[dcid]
+                if sctx.rev() < dctx.rev():
+                    if validdest(repo, sctx, dctx):
+                        advdst((b, scid, dcid))
+                    else:
+                        diverge((b, scid, dcid))
+                else:
+                    if validdest(repo, dctx, sctx):
+                        advsrc((b, scid, dcid))
+                    else:
+                        diverge((b, scid, dcid))
+            else:
+                # it is too expensive to examine in detail, in this case
+                differ((b, scid, dcid))
+
+    return results
+
+def _diverge(ui, b, path, localmarks):
+    if b == '@':
+        b = ''
+    # find a unique @ suffix
+    for x in range(1, 100):
+        n = '%s@%d' % (b, x)
+        if n not in localmarks:
+            break
+    # try to use an @pathalias suffix
+    # if an @pathalias already exists, we overwrite (update) it
+    for p, u in ui.configitems("paths"):
+        if path == u:
+            n = '%s@%s' % (b, p)
+    return n
+
 def updatefromremote(ui, repo, remotemarks, path):
     ui.debug("checking for updated bookmarks\n")
-    changed = False
     localmarks = repo._bookmarks
-    for k in sorted(remotemarks):
-        if k in localmarks:
-            nr, nl = remotemarks[k], localmarks[k]
-            if nr in repo:
-                cr = repo[nr]
-                cl = repo[nl]
-                if cl.rev() >= cr.rev():
-                    continue
-                if validdest(repo, cl, cr):
-                    localmarks[k] = cr.node()
-                    changed = True
-                    ui.status(_("updating bookmark %s\n") % k)
-                else:
-                    if k == '@':
-                        kd = ''
-                    else:
-                        kd = k
-                    # find a unique @ suffix
-                    for x in range(1, 100):
-                        n = '%s@%d' % (kd, x)
-                        if n not in localmarks:
-                            break
-                    # try to use an @pathalias suffix
-                    # if an @pathalias already exists, we overwrite (update) it
-                    for p, u in ui.configitems("paths"):
-                        if path == u:
-                            n = '%s@%s' % (kd, p)
+    (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
+     ) = compare(repo, remotemarks, localmarks, dsthex=hex)
+
+    changed = []
+    for b, scid, dcid in addsrc:
+        if scid in repo: # add remote bookmarks for changes we already have
+            changed.append((b, bin(scid), ui.status,
+                            _("adding remote bookmark %s\n") % (b)))
+    for b, scid, dcid in advsrc:
+        changed.append((b, bin(scid), ui.status,
+                        _("updating bookmark %s\n") % (b)))
+    for b, scid, dcid in diverge:
+        db = _diverge(ui, b, path, localmarks)
+        changed.append((db, bin(scid), ui.warn,
+                        _("divergent bookmark %s stored as %s\n") % (b, db)))
+    if changed:
+        for b, node, writer, msg in sorted(changed):
+            localmarks[b] = node
+            writer(msg)
+        localmarks.write()
+
+def updateremote(ui, repo, remote, revs):
+    ui.debug("checking for updated bookmarks\n")
+    revnums = map(repo.changelog.rev, revs or [])
+    ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
+    (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
+     ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
+                 srchex=hex)
 
-                    localmarks[n] = cr.node()
-                    changed = True
-                    ui.warn(_("divergent bookmark %s stored as %s\n") % (k, n))
-        elif remotemarks[k] in repo:
-            # add remote bookmarks for changes we already have
-            localmarks[k] = repo[remotemarks[k]].node()
-            changed = True
-            ui.status(_("adding remote bookmark %s\n") % k)
+    for b, scid, dcid in advsrc:
+        if ancestors and repo[scid].rev() not in ancestors:
+            continue
+        if remote.pushkey('bookmarks', b, dcid, scid):
+            ui.status(_("updating bookmark %s\n") % b)
+        else:
+            ui.warn(_('updating bookmark %s failed!\n') % b)
+
+def pushtoremote(ui, repo, remote, targets):
+    (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
+     ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
+                 srchex=hex, targets=targets)
+    if invalid:
+        b, scid, dcid = invalid[0]
+        ui.warn(_('bookmark %s does not exist on the local '
+                  'or remote repository!\n') % b)
+        return 2
 
-    if changed:
-        localmarks.write()
+    def push(b, old, new):
+        r = remote.pushkey('bookmarks', b, old, new)
+        if not r:
+            ui.warn(_('updating bookmark %s failed!\n') % b)
+            return 1
+        return 0
+    failed = 0
+    for b, scid, dcid in sorted(addsrc + advsrc + advdst + diverge + differ):
+        ui.status(_("exporting bookmark %s\n") % b)
+        if dcid is None:
+            dcid = ''
+        failed += push(b, dcid, scid)
+    for b, scid, dcid in adddst:
+        # treat as "deleted locally"
+        ui.status(_("deleting remote bookmark %s\n") % b)
+        failed += push(b, dcid, '')
+
+    if failed:
+        return 1
 
 def diff(ui, dst, src):
     ui.status(_("searching for changed bookmarks\n"))
--- a/mercurial/branchmap.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/branchmap.py	Thu Nov 21 15:08:30 2013 -0600
@@ -7,7 +7,7 @@
 
 from node import bin, hex, nullid, nullrev
 import encoding
-import util, repoview
+import util
 
 def _filename(repo):
     """name of a branchcache file for a given repo or repoview"""
@@ -58,6 +58,17 @@
 
 
 
+### Nearest subset relation
+# Nearest subset of filter X is a filter Y so that:
+# * Y is included in X,
+# * X - Y is as small as possible.
+# This create and ordering used for branchmap purpose.
+# the ordering may be partial
+subsettable = {None: 'visible',
+               'visible': 'served',
+               'served': 'immutable',
+               'immutable': 'base'}
+
 def updatecache(repo):
     cl = repo.changelog
     filtername = repo.filtername
@@ -67,7 +78,7 @@
     if partial is None or not partial.validfor(repo):
         partial = read(repo)
         if partial is None:
-            subsetname = repoview.subsettable.get(filtername)
+            subsetname = subsettable.get(filtername)
             if subsetname is None:
                 partial = branchcache()
             else:
--- a/mercurial/cmdutil.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/cmdutil.py	Thu Nov 21 15:08:30 2013 -0600
@@ -10,7 +10,7 @@
 import os, sys, errno, re, tempfile
 import util, scmutil, templater, patch, error, templatekw, revlog, copies
 import match as matchmod
-import subrepo, context, repair, graphmod, revset, phases, obsolete
+import subrepo, context, repair, graphmod, revset, phases, obsolete, pathutil
 import changelog
 import bookmarks
 import lock as lockmod
@@ -274,7 +274,7 @@
     # relsrc: ossep
     # otarget: ossep
     def copyfile(abssrc, relsrc, otarget, exact):
-        abstarget = scmutil.canonpath(repo.root, cwd, otarget)
+        abstarget = pathutil.canonpath(repo.root, cwd, otarget)
         if '/' in abstarget:
             # We cannot normalize abstarget itself, this would prevent
             # case only renames, like a => A.
@@ -367,7 +367,7 @@
     # return: function that takes hgsep and returns ossep
     def targetpathfn(pat, dest, srcs):
         if os.path.isdir(pat):
-            abspfx = scmutil.canonpath(repo.root, cwd, pat)
+            abspfx = pathutil.canonpath(repo.root, cwd, pat)
             abspfx = util.localpath(abspfx)
             if destdirexists:
                 striplen = len(os.path.split(abspfx)[0])
@@ -393,7 +393,7 @@
             res = lambda p: os.path.join(dest,
                                          os.path.basename(util.localpath(p)))
         else:
-            abspfx = scmutil.canonpath(repo.root, cwd, pat)
+            abspfx = pathutil.canonpath(repo.root, cwd, pat)
             if len(abspfx) < len(srcs[0][0]):
                 # A directory. Either the target path contains the last
                 # component of the source path or it does not.
@@ -2063,7 +2063,7 @@
                 fc = ctx[f]
                 repo.wwrite(f, fc.data(), fc.flags())
 
-            audit_path = scmutil.pathauditor(repo.root)
+            audit_path = pathutil.pathauditor(repo.root)
             for f in remove[0]:
                 if repo.dirstate[f] == 'a':
                     repo.dirstate.drop(f)
--- a/mercurial/commands.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/commands.py	Thu Nov 21 15:08:30 2013 -0600
@@ -17,7 +17,8 @@
 import merge as mergemod
 import minirst, revset, fileset
 import dagparser, context, simplemerge, graphmod
-import random, setdiscovery, treediscovery, dagutil, pvec, localrepo
+import random
+import setdiscovery, treediscovery, dagutil, pvec, localrepo
 import phases, obsolete
 
 table = {}
@@ -700,7 +701,7 @@
                 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
                 check_state(state, interactive=False)
                 # bisect
-                nodes, changesets, good = hbisect.bisect(repo.changelog, state)
+                nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
                 # update to next check
                 node = nodes[0]
                 if not noupdate:
@@ -709,7 +710,7 @@
         finally:
             state['current'] = [node]
             hbisect.save_state(repo, state)
-        print_result(nodes, good)
+        print_result(nodes, bgood)
         return
 
     # update state
@@ -3760,12 +3761,12 @@
                                         files, eolmode=None)
                     except patch.PatchError, e:
                         raise util.Abort(str(e))
-                    memctx = patch.makememctx(repo, (p1.node(), p2.node()),
-                                              message,
-                                              opts.get('user') or user,
-                                              opts.get('date') or date,
-                                              branch, files, store,
-                                              editor=cmdutil.commiteditor)
+                    memctx = context.makememctx(repo, (p1.node(), p2.node()),
+                                                message,
+                                                opts.get('user') or user,
+                                                opts.get('date') or date,
+                                                branch, files, store,
+                                                editor=cmdutil.commiteditor)
                     repo.savecommitmessage(memctx.description())
                     n = memctx.commit()
                 finally:
@@ -4711,25 +4712,11 @@
     result = not result
 
     if opts.get('bookmark'):
-        rb = other.listkeys('bookmarks')
-        for b in opts['bookmark']:
-            # explicit push overrides remote bookmark if any
-            if b in repo._bookmarks:
-                ui.status(_("exporting bookmark %s\n") % b)
-                new = repo[b].hex()
-            elif b in rb:
-                ui.status(_("deleting remote bookmark %s\n") % b)
-                new = '' # delete
-            else:
-                ui.warn(_('bookmark %s does not exist on the local '
-                          'or remote repository!\n') % b)
-                return 2
-            old = rb.get(b, '')
-            r = other.pushkey('bookmarks', b, old, new)
-            if not r:
-                ui.warn(_('updating bookmark %s failed!\n') % b)
-                if not result:
-                    result = 2
+        bresult = bookmarks.pushtoremote(ui, repo, other, opts['bookmark'])
+        if bresult == 2:
+            return 2
+        if not result and bresult:
+            result = 2
 
     return result
 
--- a/mercurial/context.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/context.py	Thu Nov 21 15:08:30 2013 -0600
@@ -195,6 +195,21 @@
     def dirty(self):
         return False
 
+def makememctx(repo, parents, text, user, date, branch, files, store,
+               editor=None):
+    def getfilectx(repo, memctx, path):
+        data, (islink, isexec), copied = store.getfile(path)
+        return memfilectx(path, data, islink=islink, isexec=isexec,
+                                  copied=copied)
+    extra = {}
+    if branch:
+        extra['branch'] = encoding.fromlocal(branch)
+    ctx =  memctx(repo, parents, text, files, getfilectx, user,
+                          date, extra)
+    if editor:
+        ctx._text = editor(repo, ctx, [])
+    return ctx
+
 class changectx(basectx):
     """A changecontext object makes access to data related to a particular
     changeset convenient. It represents a read-only context already present in
--- a/mercurial/dirstate.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/dirstate.py	Thu Nov 21 15:08:30 2013 -0600
@@ -8,7 +8,7 @@
 
 from node import nullid
 from i18n import _
-import scmutil, util, ignore, osutil, parsers, encoding
+import scmutil, util, ignore, osutil, parsers, encoding, pathutil
 import os, stat, errno, gc
 
 propertycache = util.propertycache
@@ -736,7 +736,7 @@
                 # unknown == True means we walked the full directory tree above.
                 # So if a file is not seen it was either a) not matching matchfn
                 # b) ignored, c) missing, or d) under a symlink directory.
-                audit_path = scmutil.pathauditor(self._root)
+                audit_path = pathutil.pathauditor(self._root)
 
                 for nf in iter(visit):
                     # Report ignored items in the dmap as long as they are not
--- a/mercurial/discovery.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/discovery.py	Thu Nov 21 15:08:30 2013 -0600
@@ -313,8 +313,8 @@
             if 1 < len(newhs):
                 dhs = list(newhs)
                 if error is None:
-                    error = (_("push creates multiple headed new branch '%s'")
-                             % (branch))
+                    error = (_("push creates new branch '%s' "
+                               "with multiple heads") % (branch))
                     hint = _("merge or"
                              " see \"hg help push\" for details about"
                              " pushing new heads")
@@ -337,10 +337,12 @@
                     hint = _("merge or"
                              " see \"hg help push\" for details about"
                              " pushing new heads")
-            if branch is not None:
-                repo.ui.note(_("new remote heads on branch '%s'\n") % branch)
+            if branch is None:
+                repo.ui.note(_("new remote heads:\n"))
+            else:
+                repo.ui.note(_("new remote heads on branch '%s':\n") % branch)
             for h in dhs:
-                repo.ui.note(_("new remote head %s\n") % short(h))
+                repo.ui.note((" %s\n") % short(h))
     if error:
         raise util.Abort(error, hint=hint)
 
--- a/mercurial/fancyopts.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/fancyopts.py	Thu Nov 21 15:08:30 2013 -0600
@@ -5,7 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import getopt, util
+import getopt
+import util
 from i18n import _
 
 def gnugetopt(args, options, longoptions):
--- a/mercurial/fileset.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/fileset.py	Thu Nov 21 15:08:30 2013 -0600
@@ -5,7 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import parser, error, util, merge, re
+import re
+import parser, error, util, merge
 from i18n import _
 
 elements = {
--- a/mercurial/hbisect.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/hbisect.py	Thu Nov 21 15:08:30 2013 -0600
@@ -8,7 +8,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import os, error
+import os
+import error
 from i18n import _
 from node import short, hex
 import util
--- a/mercurial/help.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/help.py	Thu Nov 21 15:08:30 2013 -0600
@@ -6,7 +6,8 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import gettext, _
-import itertools, sys, os, error
+import itertools, sys, os
+import error
 import extensions, revset, fileset, templatekw, templatefilters, filemerge
 import encoding, util, minirst
 import cmdutil
--- a/mercurial/help/templates.txt	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/help/templates.txt	Thu Nov 21 15:08:30 2013 -0600
@@ -102,3 +102,7 @@
 - Invert the firstline filter, i.e. everything but the first line::
 
    $ hg log -r 0 --template "{sub(r'^.*\n?\n?', '', desc)}\n"
+
+- Display the contents of the 'extra' field, one per line::
+
+  $ hg log -r 0 --template "{join(extras, '\n')}\n"
--- a/mercurial/hgweb/hgweb_mod.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/hgweb/hgweb_mod.py	Thu Nov 21 15:08:30 2013 -0600
@@ -6,7 +6,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import os
+import os, re
 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
 from mercurial.templatefilters import websub
 from mercurial.i18n import _
@@ -14,7 +14,7 @@
 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
 from request import wsgirequest
-import webcommands, protocol, webutil, re
+import webcommands, protocol, webutil
 
 perms = {
     'changegroup': 'pull',
--- a/mercurial/hgweb/webcommands.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/hgweb/webcommands.py	Thu Nov 21 15:08:30 2013 -0600
@@ -836,15 +836,11 @@
     end = min(count, start + revcount) # last rev on this page
     parity = paritygen(web.stripecount, offset=start - end)
 
-    def entries(latestonly, **map):
+    def entries():
         l = []
 
         repo = web.repo
-        revs = repo.changelog.revs(start, end - 1)
-        if latestonly:
-            for r in revs:
-                pass
-            revs = (r,)
+        revs = fctx.filelog().revs(start, end - 1)
         for i in revs:
             iterfctx = fctx.filectx(i)
 
@@ -868,11 +864,14 @@
         for e in reversed(l):
             yield e
 
+    entries = list(entries())
+    latestentry = entries[:1]
+
     revnav = webutil.filerevnav(web.repo, fctx.path())
     nav = revnav.gen(end - 1, revcount, count)
     return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
-                entries=lambda **x: entries(latestonly=False, **x),
-                latestentry=lambda **x: entries(latestonly=True, **x),
+                entries=entries,
+                latestentry=latestentry,
                 revcount=revcount, morevars=morevars, lessvars=lessvars)
 
 def archive(web, req, tmpl):
--- a/mercurial/hgweb/webutil.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/hgweb/webutil.py	Thu Nov 21 15:08:30 2013 -0600
@@ -7,7 +7,7 @@
 # GNU General Public License version 2 or any later version.
 
 import os, copy
-from mercurial import match, patch, scmutil, error, ui, util
+from mercurial import match, patch, error, ui, util, pathutil
 from mercurial.i18n import _
 from mercurial.node import hex, nullid
 from common import ErrorResponse
@@ -196,7 +196,7 @@
 
 def cleanpath(repo, path):
     path = path.lstrip('/')
-    return scmutil.canonpath(repo.root, '', path)
+    return pathutil.canonpath(repo.root, '', path)
 
 def changeidctx (repo, changeid):
     try:
--- a/mercurial/localrepo.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/localrepo.py	Thu Nov 21 15:08:30 2013 -0600
@@ -15,7 +15,7 @@
 import tags as tagsmod
 from lock import release
 import weakref, errno, os, time, inspect
-import branchmap
+import branchmap, pathutil
 propertycache = util.propertycache
 filecache = scmutil.filecache
 
@@ -166,7 +166,7 @@
         self.root = self.wvfs.base
         self.path = self.wvfs.join(".hg")
         self.origroot = path
-        self.auditor = scmutil.pathauditor(self.root, self._checknested)
+        self.auditor = pathutil.pathauditor(self.root, self._checknested)
         self.vfs = scmutil.vfs(self.path)
         self.opener = self.vfs
         self.baseui = baseui
@@ -1976,27 +1976,7 @@
             if locallock is not None:
                 locallock.release()
 
-        self.ui.debug("checking for updated bookmarks\n")
-        rb = remote.listkeys('bookmarks')
-        revnums = map(unfi.changelog.rev, revs or [])
-        ancestors = [
-            a for a in unfi.changelog.ancestors(revnums, inclusive=True)]
-        for k in rb.keys():
-            if k in unfi._bookmarks:
-                nr, nl = rb[k], hex(self._bookmarks[k])
-                if nr in unfi:
-                    cr = unfi[nr]
-                    cl = unfi[nl]
-                    if bookmarks.validdest(unfi, cr, cl):
-                        if ancestors and cl.rev() not in ancestors:
-                            continue
-                        r = remote.pushkey('bookmarks', k, nr, nl)
-                        if r:
-                            self.ui.status(_("updating bookmark %s\n") % k)
-                        else:
-                            self.ui.warn(_('updating bookmark %s'
-                                           ' failed!\n') % k)
-
+        bookmarks.updateremote(self.ui, unfi, remote, revs)
         return ret
 
     def changegroupinfo(self, nodes, source):
--- a/mercurial/manifest.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/manifest.py	Thu Nov 21 15:08:30 2013 -0600
@@ -30,8 +30,9 @@
 
 class manifest(revlog.revlog):
     def __init__(self, opener):
-        # we expect to deal with not more than three revs at a time in merge
-        self._mancache = util.lrucachedict(3)
+        # we expect to deal with not more than four revs at a time,
+        # during a commit --amend
+        self._mancache = util.lrucachedict(4)
         revlog.revlog.__init__(self, opener, "00manifest.i")
 
     def parse(self, lines):
--- a/mercurial/match.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/match.py	Thu Nov 21 15:08:30 2013 -0600
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 import re
-import scmutil, util, fileset
+import util, fileset, pathutil
 from i18n import _
 
 def _rematcher(pat):
@@ -317,7 +317,7 @@
     pats = []
     for kind, name in [_patsplit(p, default) for p in names]:
         if kind in ('glob', 'relpath'):
-            name = scmutil.canonpath(root, cwd, name, auditor)
+            name = pathutil.canonpath(root, cwd, name, auditor)
         elif kind in ('relglob', 'path'):
             name = util.normpath(name)
         elif kind in ('listfile', 'listfile0'):
--- a/mercurial/mdiff.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/mdiff.py	Thu Nov 21 15:08:30 2013 -0600
@@ -6,8 +6,8 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import _
-import bdiff, mpatch, util
-import re, struct, base85, zlib
+import bdiff, mpatch, util, base85
+import re, struct, zlib
 
 def splitnewlines(text):
     '''like str.splitlines, but only split on newlines.'''
--- a/mercurial/obsolete.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/obsolete.py	Thu Nov 21 15:08:30 2013 -0600
@@ -196,6 +196,14 @@
         self._data = data
         self._decodedmeta = None
 
+    def __hash__(self):
+        return hash(self._data)
+
+    def __eq__(self, other):
+        if type(other) != type(self):
+            return False
+        return self._data == other._data
+
     def precnode(self):
         """Precursor changeset node identifier"""
         return self._data[0]
@@ -268,7 +276,11 @@
         if not _enabled:
             raise util.Abort('obsolete feature is not enabled on this repo')
         known = set(self._all)
-        new = [m for m in markers if m not in known]
+        new = []
+        for m in markers:
+            if m not in known:
+                known.add(m)
+                new.append(m)
         if new:
             f = self.sopener('obsstore', 'ab')
             try:
--- a/mercurial/patch.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/patch.py	Thu Nov 21 15:08:30 2013 -0600
@@ -16,7 +16,6 @@
 from i18n import _
 from node import hex, short
 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
-import context
 
 gitre = re.compile('diff --git a/(.*) b/(.*)')
 
@@ -1441,21 +1440,6 @@
     backend = repobackend(ui, repo, ctx, store)
     return patchbackend(ui, backend, patchobj, strip, files, eolmode)
 
-def makememctx(repo, parents, text, user, date, branch, files, store,
-               editor=None):
-    def getfilectx(repo, memctx, path):
-        data, (islink, isexec), copied = store.getfile(path)
-        return context.memfilectx(path, data, islink=islink, isexec=isexec,
-                                  copied=copied)
-    extra = {}
-    if branch:
-        extra['branch'] = encoding.fromlocal(branch)
-    ctx =  context.memctx(repo, parents, text, files, getfilectx, user,
-                          date, extra)
-    if editor:
-        ctx._text = editor(repo, ctx, [])
-    return ctx
-
 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
           similarity=0):
     """Apply <patchname> to the working directory.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/pathutil.py	Thu Nov 21 15:08:30 2013 -0600
@@ -0,0 +1,144 @@
+import os, errno, stat
+
+import util
+from i18n import _
+
+class pathauditor(object):
+    '''ensure that a filesystem path contains no banned components.
+    the following properties of a path are checked:
+
+    - ends with a directory separator
+    - under top-level .hg
+    - starts at the root of a windows drive
+    - contains ".."
+    - traverses a symlink (e.g. a/symlink_here/b)
+    - inside a nested repository (a callback can be used to approve
+      some nested repositories, e.g., subrepositories)
+    '''
+
+    def __init__(self, root, callback=None):
+        self.audited = set()
+        self.auditeddir = set()
+        self.root = root
+        self.callback = callback
+        if os.path.lexists(root) and not util.checkcase(root):
+            self.normcase = util.normcase
+        else:
+            self.normcase = lambda x: x
+
+    def __call__(self, path):
+        '''Check the relative path.
+        path may contain a pattern (e.g. foodir/**.txt)'''
+
+        path = util.localpath(path)
+        normpath = self.normcase(path)
+        if normpath in self.audited:
+            return
+        # AIX ignores "/" at end of path, others raise EISDIR.
+        if util.endswithsep(path):
+            raise util.Abort(_("path ends in directory separator: %s") % path)
+        parts = util.splitpath(path)
+        if (os.path.splitdrive(path)[0]
+            or parts[0].lower() in ('.hg', '.hg.', '')
+            or os.pardir in parts):
+            raise util.Abort(_("path contains illegal component: %s") % path)
+        if '.hg' in path.lower():
+            lparts = [p.lower() for p in parts]
+            for p in '.hg', '.hg.':
+                if p in lparts[1:]:
+                    pos = lparts.index(p)
+                    base = os.path.join(*parts[:pos])
+                    raise util.Abort(_("path '%s' is inside nested repo %r")
+                                     % (path, base))
+
+        normparts = util.splitpath(normpath)
+        assert len(parts) == len(normparts)
+
+        parts.pop()
+        normparts.pop()
+        prefixes = []
+        while parts:
+            prefix = os.sep.join(parts)
+            normprefix = os.sep.join(normparts)
+            if normprefix in self.auditeddir:
+                break
+            curpath = os.path.join(self.root, prefix)
+            try:
+                st = os.lstat(curpath)
+            except OSError, err:
+                # EINVAL can be raised as invalid path syntax under win32.
+                # They must be ignored for patterns can be checked too.
+                if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
+                    raise
+            else:
+                if stat.S_ISLNK(st.st_mode):
+                    raise util.Abort(
+                        _('path %r traverses symbolic link %r')
+                        % (path, prefix))
+                elif (stat.S_ISDIR(st.st_mode) and
+                      os.path.isdir(os.path.join(curpath, '.hg'))):
+                    if not self.callback or not self.callback(curpath):
+                        raise util.Abort(_("path '%s' is inside nested "
+                                           "repo %r")
+                                         % (path, prefix))
+            prefixes.append(normprefix)
+            parts.pop()
+            normparts.pop()
+
+        self.audited.add(normpath)
+        # only add prefixes to the cache after checking everything: we don't
+        # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
+        self.auditeddir.update(prefixes)
+
+    def check(self, path):
+        try:
+            self(path)
+            return True
+        except (OSError, util.Abort):
+            return False
+
+def canonpath(root, cwd, myname, auditor=None):
+    '''return the canonical path of myname, given cwd and root'''
+    if util.endswithsep(root):
+        rootsep = root
+    else:
+        rootsep = root + os.sep
+    name = myname
+    if not os.path.isabs(name):
+        name = os.path.join(root, cwd, name)
+    name = os.path.normpath(name)
+    if auditor is None:
+        auditor = pathauditor(root)
+    if name != rootsep and name.startswith(rootsep):
+        name = name[len(rootsep):]
+        auditor(name)
+        return util.pconvert(name)
+    elif name == root:
+        return ''
+    else:
+        # Determine whether `name' is in the hierarchy at or beneath `root',
+        # by iterating name=dirname(name) until that causes no change (can't
+        # check name == '/', because that doesn't work on windows). The list
+        # `rel' holds the reversed list of components making up the relative
+        # file name we want.
+        rel = []
+        while True:
+            try:
+                s = util.samefile(name, root)
+            except OSError:
+                s = False
+            if s:
+                if not rel:
+                    # name was actually the same as root (maybe a symlink)
+                    return ''
+                rel.reverse()
+                name = os.path.join(*rel)
+                auditor(name)
+                return util.pconvert(name)
+            dirname, basename = util.split(name)
+            rel.append(basename)
+            if dirname == name:
+                break
+            name = dirname
+
+        raise util.Abort(_("%s not under root '%s'") % (myname, root))
--- a/mercurial/repair.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/repair.py	Thu Nov 21 15:08:30 2013 -0600
@@ -38,16 +38,8 @@
     """return the changesets which will be broken by the truncation"""
     s = set()
     def collectone(revlog):
-        linkgen = (revlog.linkrev(i) for i in revlog)
-        # find the truncation point of the revlog
-        for lrev in linkgen:
-            if lrev >= striprev:
-                break
-        # see if any revision after this point has a linkrev
-        # less than striprev (those will be broken by strip)
-        for lrev in linkgen:
-            if lrev < striprev:
-                s.add(lrev)
+        _, brokenset = revlog.getstrippoint(striprev)
+        s.update([revlog.linkrev(r) for r in brokenset])
 
     collectone(repo.manifest)
     for fname in files:
--- a/mercurial/repoview.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/repoview.py	Thu Nov 21 15:08:30 2013 -0600
@@ -98,16 +98,6 @@
                'served': computeunserved,
                'immutable':  computemutable,
                'base':  computeimpactable}
-### Nearest subset relation
-# Nearest subset of filter X is a filter Y so that:
-# * Y is included in X,
-# * X - Y is as small as possible.
-# This create and ordering used for branchmap purpose.
-# the ordering may be partial
-subsettable = {None: 'visible',
-               'visible': 'served',
-               'served': 'immutable',
-               'immutable': 'base'}
 
 def filterrevs(repo, filtername):
     """returns set of filtered revision for this filter name"""
@@ -215,4 +205,3 @@
     @property
     def requirements(self):
         return self._unfilteredrepo.requirements
-
--- a/mercurial/revlog.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/revlog.py	Thu Nov 21 15:08:30 2013 -0600
@@ -401,7 +401,29 @@
         heads = [self.rev(n) for n in heads]
 
         # we want the ancestors, but inclusive
-        has = set(self.ancestors(common))
+        class lazyset(object):
+            def __init__(self, lazyvalues):
+                self.addedvalues = set()
+                self.lazyvalues = lazyvalues
+
+            def __contains__(self, value):
+                return value in self.addedvalues or value in self.lazyvalues
+
+            def __iter__(self):
+                added = self.addedvalues
+                for r in added:
+                    yield r
+                for r in self.lazyvalues:
+                    if not r in added:
+                        yield r
+
+            def add(self, value):
+                self.addedvalues.add(value)
+
+            def update(self, values):
+                self.addedvalues.update(values)
+
+        has = lazyset(self.ancestors(common))
         has.add(nullrev)
         has.update(common)
 
@@ -1263,6 +1285,46 @@
 
         return content
 
+    def getstrippoint(self, minlink):
+        """find the minimum rev that must be stripped to strip the linkrev
+
+        Returns a tuple containing the minimum rev and a set of all revs that
+        have linkrevs that will be broken by this strip.
+        """
+        brokenrevs = set()
+        strippoint = len(self)
+
+        heads = {}
+        futurelargelinkrevs = set()
+        for head in self.headrevs():
+            headlinkrev = self.linkrev(head)
+            heads[head] = headlinkrev
+            if headlinkrev >= minlink:
+                futurelargelinkrevs.add(headlinkrev)
+
+        # This algorithm involves walking down the rev graph, starting at the
+        # heads. Since the revs are topologically sorted according to linkrev,
+        # once all head linkrevs are below the minlink, we know there are
+        # no more revs that could have a linkrev greater than minlink.
+        # So we can stop walking.
+        while futurelargelinkrevs:
+            strippoint -= 1
+            linkrev = heads.pop(strippoint)
+
+            if linkrev < minlink:
+                brokenrevs.add(strippoint)
+            else:
+                futurelargelinkrevs.remove(linkrev)
+
+            for p in self.parentrevs(strippoint):
+                if p != nullrev:
+                    plinkrev = self.linkrev(p)
+                    heads[p] = plinkrev
+                    if plinkrev >= minlink:
+                        futurelargelinkrevs.add(plinkrev)
+
+        return strippoint, brokenrevs
+
     def strip(self, minlink, transaction):
         """truncate the revlog on the first revision with a linkrev >= minlink
 
@@ -1280,10 +1342,8 @@
         if len(self) == 0:
             return
 
-        for rev in self:
-            if self.index[rev][4] >= minlink:
-                break
-        else:
+        rev, _ = self.getstrippoint(minlink)
+        if rev == len(self):
             return
 
         # first truncate the files on disk
--- a/mercurial/scmutil.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/scmutil.py	Thu Nov 21 15:08:30 2013 -0600
@@ -8,8 +8,9 @@
 from i18n import _
 from mercurial.node import nullrev
 import util, error, osutil, revset, similar, encoding, phases, parsers
+import pathutil
 import match as matchmod
-import os, errno, re, stat, glob
+import os, errno, re, glob
 
 if os.name == 'nt':
     import scmwindows as scmplatform
@@ -97,9 +98,10 @@
         self._newfiles = set()
 
     def __call__(self, f):
+        if f in self._newfiles:
+            return
         fl = encoding.lower(f)
-        if (fl in self._loweredfiles and f not in self._dirstate and
-            f not in self._newfiles):
+        if fl in self._loweredfiles and f not in self._dirstate:
             msg = _('possible case-folding collision for %s') % f
             if self._abort:
                 raise util.Abort(msg)
@@ -107,100 +109,6 @@
         self._loweredfiles.add(fl)
         self._newfiles.add(f)
 
-class pathauditor(object):
-    '''ensure that a filesystem path contains no banned components.
-    the following properties of a path are checked:
-
-    - ends with a directory separator
-    - under top-level .hg
-    - starts at the root of a windows drive
-    - contains ".."
-    - traverses a symlink (e.g. a/symlink_here/b)
-    - inside a nested repository (a callback can be used to approve
-      some nested repositories, e.g., subrepositories)
-    '''
-
-    def __init__(self, root, callback=None):
-        self.audited = set()
-        self.auditeddir = set()
-        self.root = root
-        self.callback = callback
-        if os.path.lexists(root) and not util.checkcase(root):
-            self.normcase = util.normcase
-        else:
-            self.normcase = lambda x: x
-
-    def __call__(self, path):
-        '''Check the relative path.
-        path may contain a pattern (e.g. foodir/**.txt)'''
-
-        path = util.localpath(path)
-        normpath = self.normcase(path)
-        if normpath in self.audited:
-            return
-        # AIX ignores "/" at end of path, others raise EISDIR.
-        if util.endswithsep(path):
-            raise util.Abort(_("path ends in directory separator: %s") % path)
-        parts = util.splitpath(path)
-        if (os.path.splitdrive(path)[0]
-            or parts[0].lower() in ('.hg', '.hg.', '')
-            or os.pardir in parts):
-            raise util.Abort(_("path contains illegal component: %s") % path)
-        if '.hg' in path.lower():
-            lparts = [p.lower() for p in parts]
-            for p in '.hg', '.hg.':
-                if p in lparts[1:]:
-                    pos = lparts.index(p)
-                    base = os.path.join(*parts[:pos])
-                    raise util.Abort(_("path '%s' is inside nested repo %r")
-                                     % (path, base))
-
-        normparts = util.splitpath(normpath)
-        assert len(parts) == len(normparts)
-
-        parts.pop()
-        normparts.pop()
-        prefixes = []
-        while parts:
-            prefix = os.sep.join(parts)
-            normprefix = os.sep.join(normparts)
-            if normprefix in self.auditeddir:
-                break
-            curpath = os.path.join(self.root, prefix)
-            try:
-                st = os.lstat(curpath)
-            except OSError, err:
-                # EINVAL can be raised as invalid path syntax under win32.
-                # They must be ignored for patterns can be checked too.
-                if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
-                    raise
-            else:
-                if stat.S_ISLNK(st.st_mode):
-                    raise util.Abort(
-                        _('path %r traverses symbolic link %r')
-                        % (path, prefix))
-                elif (stat.S_ISDIR(st.st_mode) and
-                      os.path.isdir(os.path.join(curpath, '.hg'))):
-                    if not self.callback or not self.callback(curpath):
-                        raise util.Abort(_("path '%s' is inside nested "
-                                           "repo %r")
-                                         % (path, prefix))
-            prefixes.append(normprefix)
-            parts.pop()
-            normparts.pop()
-
-        self.audited.add(normpath)
-        # only add prefixes to the cache after checking everything: we don't
-        # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
-        self.auditeddir.update(prefixes)
-
-    def check(self, path):
-        try:
-            self(path)
-            return True
-        except (OSError, util.Abort):
-            return False
-
 class abstractvfs(object):
     """Abstract base class; cannot be instantiated"""
 
@@ -309,7 +217,7 @@
     def _setmustaudit(self, onoff):
         self._audit = onoff
         if onoff:
-            self.audit = pathauditor(self.base)
+            self.audit = pathutil.pathauditor(self.base)
         else:
             self.audit = util.always
 
@@ -444,52 +352,6 @@
         return self.vfs(path, mode, *args, **kw)
 
 
-def canonpath(root, cwd, myname, auditor=None):
-    '''return the canonical path of myname, given cwd and root'''
-    if util.endswithsep(root):
-        rootsep = root
-    else:
-        rootsep = root + os.sep
-    name = myname
-    if not os.path.isabs(name):
-        name = os.path.join(root, cwd, name)
-    name = os.path.normpath(name)
-    if auditor is None:
-        auditor = pathauditor(root)
-    if name != rootsep and name.startswith(rootsep):
-        name = name[len(rootsep):]
-        auditor(name)
-        return util.pconvert(name)
-    elif name == root:
-        return ''
-    else:
-        # Determine whether `name' is in the hierarchy at or beneath `root',
-        # by iterating name=dirname(name) until that causes no change (can't
-        # check name == '/', because that doesn't work on windows). The list
-        # `rel' holds the reversed list of components making up the relative
-        # file name we want.
-        rel = []
-        while True:
-            try:
-                s = util.samefile(name, root)
-            except OSError:
-                s = False
-            if s:
-                if not rel:
-                    # name was actually the same as root (maybe a symlink)
-                    return ''
-                rel.reverse()
-                name = os.path.join(*rel)
-                auditor(name)
-                return util.pconvert(name)
-            dirname, basename = util.split(name)
-            rel.append(basename)
-            if dirname == name:
-                break
-            name = dirname
-
-        raise util.Abort(_("%s not under root '%s'") % (myname, root))
-
 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
     '''yield every hg repository under path, always recursively.
     The recurse flag will only control recursion into repo working dirs'''
@@ -767,7 +629,7 @@
     This is different from dirstate.status because it doesn't care about
     whether files are modified or clean.'''
     added, unknown, deleted, removed = [], [], [], []
-    audit_path = pathauditor(repo.root)
+    audit_path = pathutil.pathauditor(repo.root)
 
     ctx = repo[None]
     dirstate = repo.dirstate
@@ -851,14 +713,14 @@
               "Mercurial)") % "', '".join(missings))
     return requirements
 
-class filecacheentry(object):
-    def __init__(self, path, stat=True):
+class filecachesubentry(object):
+    def __init__(self, path, stat):
         self.path = path
         self.cachestat = None
         self._cacheable = None
 
         if stat:
-            self.cachestat = filecacheentry.stat(self.path)
+            self.cachestat = filecachesubentry.stat(self.path)
 
             if self.cachestat:
                 self._cacheable = self.cachestat.cacheable()
@@ -868,7 +730,7 @@
 
     def refresh(self):
         if self.cacheable():
-            self.cachestat = filecacheentry.stat(self.path)
+            self.cachestat = filecachesubentry.stat(self.path)
 
     def cacheable(self):
         if self._cacheable is not None:
@@ -882,7 +744,7 @@
         if not self.cacheable():
             return True
 
-        newstat = filecacheentry.stat(self.path)
+        newstat = filecachesubentry.stat(self.path)
 
         # we may not know if it's cacheable yet, check again now
         if newstat and self._cacheable is None:
@@ -906,24 +768,44 @@
             if e.errno != errno.ENOENT:
                 raise
 
+class filecacheentry(object):
+    def __init__(self, paths, stat=True):
+        self._entries = []
+        for path in paths:
+            self._entries.append(filecachesubentry(path, stat))
+
+    def changed(self):
+        '''true if any entry has changed'''
+        for entry in self._entries:
+            if entry.changed():
+                return True
+        return False
+
+    def refresh(self):
+        for entry in self._entries:
+            entry.refresh()
+
 class filecache(object):
-    '''A property like decorator that tracks a file under .hg/ for updates.
+    '''A property like decorator that tracks files under .hg/ for updates.
 
     Records stat info when called in _filecache.
 
-    On subsequent calls, compares old stat info with new info, and recreates
-    the object when needed, updating the new stat info in _filecache.
+    On subsequent calls, compares old stat info with new info, and recreates the
+    object when any of the files changes, updating the new stat info in
+    _filecache.
 
     Mercurial either atomic renames or appends for files under .hg,
     so to ensure the cache is reliable we need the filesystem to be able
     to tell us if a file has been replaced. If it can't, we fallback to
     recreating the object on every call (essentially the same behaviour as
-    propertycache).'''
-    def __init__(self, path):
-        self.path = path
+    propertycache).
+
+    '''
+    def __init__(self, *paths):
+        self.paths = paths
 
     def join(self, obj, fname):
-        """Used to compute the runtime path of the cached file.
+        """Used to compute the runtime path of a cached file.
 
         Users should subclass filecache and provide their own version of this
         function to call the appropriate join function on 'obj' (an instance
@@ -948,11 +830,11 @@
             if entry.changed():
                 entry.obj = self.func(obj)
         else:
-            path = self.join(obj, self.path)
+            paths = [self.join(obj, path) for path in self.paths]
 
             # We stat -before- creating the object so our cache doesn't lie if
             # a writer modified between the time we read and stat
-            entry = filecacheentry(path)
+            entry = filecacheentry(paths, True)
             entry.obj = self.func(obj)
 
             obj._filecache[self.name] = entry
@@ -964,7 +846,8 @@
         if self.name not in obj._filecache:
             # we add an entry for the missing value because X in __dict__
             # implies X in _filecache
-            ce = filecacheentry(self.join(obj, self.path), False)
+            paths = [self.join(obj, path) for path in self.paths]
+            ce = filecacheentry(paths, False)
             obj._filecache[self.name] = ce
         else:
             ce = obj._filecache[self.name]
--- a/mercurial/setdiscovery.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/setdiscovery.py	Thu Nov 21 15:08:30 2013 -0600
@@ -8,7 +8,8 @@
 
 from node import nullid
 from i18n import _
-import random, util, dagutil
+import random
+import util, dagutil
 
 def _updatesample(dag, nodes, sample, always, quicksamplesize=0):
     # if nodes is empty we scan the entire graph
--- a/mercurial/statichttprepo.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/statichttprepo.py	Thu Nov 21 15:08:30 2013 -0600
@@ -54,8 +54,10 @@
             data = data[:bytes]
         self.pos += len(data)
         return data
+    def readlines(self):
+        return self.read().splitlines(True)
     def __iter__(self):
-        return iter(self.read().splitlines(1))
+        return iter(self.readlines())
     def close(self):
         pass
 
--- a/mercurial/subrepo.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/subrepo.py	Thu Nov 21 15:08:30 2013 -0600
@@ -9,7 +9,8 @@
 import xml.dom.minidom
 import stat, subprocess, tarfile
 from i18n import _
-import config, scmutil, util, node, error, cmdutil, bookmarks, match as matchmod
+import config, util, node, error, cmdutil, bookmarks, match as matchmod
+import pathutil
 hg = None
 propertycache = util.propertycache
 
@@ -332,7 +333,7 @@
     import hg as h
     hg = h
 
-    scmutil.pathauditor(ctx._repo.root)(path)
+    pathutil.pathauditor(ctx._repo.root)(path)
     state = ctx.substate[path]
     if state[2] not in types:
         raise util.Abort(_('unknown subrepo type %s') % state[2])
--- a/mercurial/templatekw.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/templatekw.py	Thu Nov 21 15:08:30 2013 -0600
@@ -220,11 +220,10 @@
     return '%s: +%s/-%s' % (len(stats), adds, removes)
 
 def showextras(**args):
-    templ = args['templ']
-    for key, value in sorted(args['ctx'].extra().items()):
-        args = args.copy()
-        args.update(dict(key=key, value=value))
-        yield templ('extra', **args)
+    """:extras: List of dicts with key, value entries of the 'extras'
+    field of this changeset."""
+    yield showlist('extra', sorted(dict(key=a, value=b)
+                   for (a, b) in args['ctx'].extra().items()), **args)
 
 def showfileadds(**args):
     """:file_adds: List of strings. Files added by this changeset."""
@@ -392,6 +391,7 @@
     'parents': _showparents,
 }
 dockeywords.update(keywords)
+del dockeywords['branches']
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = dockeywords.values()
--- a/mercurial/util.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/util.py	Thu Nov 21 15:08:30 2013 -0600
@@ -14,9 +14,9 @@
 """
 
 from i18n import _
-import error, osutil, encoding, collections
+import error, osutil, encoding
 import errno, re, shutil, sys, tempfile, traceback
-import os, time, datetime, calendar, textwrap, signal
+import os, time, datetime, calendar, textwrap, signal, collections
 import imp, socket, urllib
 
 if os.name == 'nt':
@@ -1033,9 +1033,10 @@
     if t < 0:
         t = 0   # time.gmtime(lt) fails on Windows for lt < -43200
         tz = 0
-    if "%1" in format or "%2" in format:
+    if "%1" in format or "%2" in format or "%z" in format:
         sign = (tz > 0) and "-" or "+"
         minutes = abs(tz) // 60
+        format = format.replace("%z", "%1%2")
         format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
         format = format.replace("%2", "%02d" % (minutes % 60))
     try:
--- a/mercurial/worker.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/mercurial/worker.py	Thu Nov 21 15:08:30 2013 -0600
@@ -6,7 +6,8 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import _
-import errno, os, signal, sys, threading, util
+import errno, os, signal, sys, threading
+import util
 
 def countcpus():
     '''try to count the number of CPUs on the system'''
--- a/tests/hghave.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/hghave.py	Thu Nov 21 15:08:30 2013 -0600
@@ -233,6 +233,9 @@
     finally:
         os.rmdir(d)
 
+def has_root():
+    return os.geteuid() == 0
+
 def has_pyflakes():
     return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
                        r"<stdin>:1: 're' imported but unused",
@@ -312,6 +315,7 @@
     "p4": (has_p4, "Perforce server and client"),
     "pyflakes": (has_pyflakes, "Pyflakes python linter"),
     "pygments": (has_pygments, "Pygments source highlighting library"),
+    "root": (has_root, "root permissions"),
     "serve": (has_serve, "platform and python can manage 'hg serve -d'"),
     "ssl": (has_ssl, "python >= 2.6 ssl module and python OpenSSL"),
     "svn": (has_svn, "subversion client and admin tools"),
--- a/tests/run-tests.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/run-tests.py	Thu Nov 21 15:08:30 2013 -0600
@@ -103,6 +103,7 @@
 
 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
                  "gunzip", "bunzip2", "sed"]
+createdfiles = []
 
 defaults = {
     'jobs': ('HGTEST_JOBS', 1),
@@ -420,6 +421,11 @@
     if not options.keep_tmpdir:
         vlog("# Cleaning up HGTMP", HGTMP)
         shutil.rmtree(HGTMP, True)
+        for f in createdfiles:
+            try:
+                os.remove(f)
+            except OSError:
+                pass
 
 def usecorrectpython():
     # some tests run python interpreter. they must use same
@@ -439,6 +445,7 @@
         if findprogram(pyexename) != sys.executable:
             try:
                 os.symlink(sys.executable, mypython)
+                createdfiles.append(mypython)
             except OSError, err:
                 # child processes may race, which is harmless
                 if err.errno != errno.EEXIST:
@@ -498,18 +505,6 @@
 
     usecorrectpython()
 
-    vlog("# Installing dummy diffstat")
-    f = open(os.path.join(BINDIR, 'diffstat'), 'w')
-    f.write('#!' + sys.executable + '\n'
-            'import sys\n'
-            'files = 0\n'
-            'for line in sys.stdin:\n'
-            '    if line.startswith("diff "):\n'
-            '        files += 1\n'
-            'sys.stdout.write("files patched: %d\\n" % files)\n')
-    f.close()
-    os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
-
     if options.py3k_warnings and not options.anycoverage:
         vlog("# Updating hg command to enable Py3k Warnings switch")
         f = open(os.path.join(BINDIR, 'hg'), 'r')
@@ -1139,6 +1134,8 @@
         _checkhglib("Tested")
         print "# Ran %d tests, %d skipped, %d failed." % (
             tested, skipped + ignored, failed)
+        if results['!']:
+            print 'python hash seed:', os.environ['PYTHONHASHSEED']
         if options.time:
             outputtimes(options)
 
@@ -1190,7 +1187,6 @@
         # use a random python hash seed all the time
         # we do the randomness ourself to know what seed is used
         os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
-        print 'python hash seed:', os.environ['PYTHONHASHSEED']
 
     global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
     TESTDIR = os.environ["TESTDIR"] = os.getcwd()
--- a/tests/test-blackbox.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-blackbox.t	Thu Nov 21 15:08:30 2013 -0600
@@ -65,7 +65,7 @@
   $ hg rollback
   repository tip rolled back to revision 1 (undo pull)
 
-#if unix-permissions
+#if unix-permissions no-root
   $ chmod 000 .hg/blackbox.log
   $ hg --debug incoming
   warning: cannot write to blackbox.log: Permission denied
@@ -98,7 +98,7 @@
   (run 'hg update' to get a working copy)
 
 a failure reading from the log is fine
-#if unix-permissions
+#if unix-permissions no-root
   $ hg blackbox -l 3
   abort: Permission denied: $TESTTMP/blackboxtest2/.hg/blackbox.log
   [255]
--- a/tests/test-clone.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-clone.t	Thu Nov 21 15:08:30 2013 -0600
@@ -543,7 +543,7 @@
   $ rm -rf b # work around bug with http clone
 
 
-#if unix-permissions
+#if unix-permissions no-root
 
 Inaccessible source
 
@@ -596,7 +596,7 @@
   [255]
 
 
-#if unix-permissions
+#if unix-permissions no-root
 
 leave existing directory in place after clone failure
 
--- a/tests/test-command-template.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-command-template.t	Thu Nov 21 15:08:30 2013 -0600
@@ -447,7 +447,7 @@
 
 Error if style not readable:
 
-#if unix-permissions
+#if unix-permissions no-root
   $ touch q
   $ chmod 0 q
   $ hg log --style ./q
@@ -479,7 +479,7 @@
 Error if include fails:
 
   $ echo 'changeset = q' >> t
-#if unix-permissions
+#if unix-permissions no-root
   $ hg log --style ./t
   abort: template file ./q: Permission denied
   [255]
@@ -1445,7 +1445,7 @@
   $ hg ci -m h2e -d '4 0'
 
   $ hg merge -q
-  $ hg ci -m merge -d '5 0'
+  $ hg ci -m merge -d '5 -3600'
 
 No tag set:
 
@@ -1533,7 +1533,7 @@
   > EOF
 
   $ hg -R latesttag tip
-  test 10:dee8f28249af
+  test 10:9b4a630e5f5f
 
 Test recursive showlist template (issue1989):
 
@@ -1587,6 +1587,21 @@
   b
   a
 
+Test date format:
+
+  $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
+  date: 70 01 01 10 +0000
+  date: 70 01 01 09 +0000
+  date: 70 01 01 08 +0000
+  date: 70 01 01 07 +0000
+  date: 70 01 01 06 +0000
+  date: 70 01 01 05 +0100
+  date: 70 01 01 04 +0000
+  date: 70 01 01 03 +0000
+  date: 70 01 01 02 +0000
+  date: 70 01 01 01 +0000
+  date: 70 01 01 00 +0000
+
 Test string escaping:
 
   $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
--- a/tests/test-convert.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-convert.t	Thu Nov 21 15:08:30 2013 -0600
@@ -310,7 +310,7 @@
   abort: cannot create new bundle repository
   [255]
 
-#if unix-permissions
+#if unix-permissions no-root
 
 conversion to dir without permissions should fail
 
--- a/tests/test-doctest.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-doctest.py	Thu Nov 21 15:08:30 2013 -0600
@@ -1,49 +1,29 @@
 # this is hack to make sure no escape characters are inserted into the output
-import os
+import os, sys
 if 'TERM' in os.environ:
     del os.environ['TERM']
 import doctest
 
-import mercurial.util
-doctest.testmod(mercurial.util)
-# Only run doctests for the current platform
-doctest.testmod(mercurial.util.platform)
-
-import mercurial.changelog
-doctest.testmod(mercurial.changelog)
-
-import mercurial.dagparser
-doctest.testmod(mercurial.dagparser, optionflags=doctest.NORMALIZE_WHITESPACE)
-
-import mercurial.match
-doctest.testmod(mercurial.match)
-
-import mercurial.store
-doctest.testmod(mercurial.store)
-
-import mercurial.ui
-doctest.testmod(mercurial.ui)
+def testmod(name, optionflags=0, testtarget=None):
+    __import__(name)
+    mod = sys.modules[name]
+    if testtarget is not None:
+        mod = getattr(mod, testtarget)
+    doctest.testmod(mod, optionflags=optionflags)
 
-import mercurial.url
-doctest.testmod(mercurial.url)
-
-import mercurial.dispatch
-doctest.testmod(mercurial.dispatch)
-
-import mercurial.encoding
-doctest.testmod(mercurial.encoding)
-
-import mercurial.hgweb.hgwebdir_mod
-doctest.testmod(mercurial.hgweb.hgwebdir_mod)
-
-import hgext.convert.cvsps
-doctest.testmod(hgext.convert.cvsps)
-
-import mercurial.revset
-doctest.testmod(mercurial.revset)
-
-import mercurial.minirst
-doctest.testmod(mercurial.minirst)
-
-import mercurial.templatefilters
-doctest.testmod(mercurial.templatefilters)
+testmod('mercurial.changelog')
+testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
+testmod('mercurial.dispatch')
+testmod('mercurial.encoding')
+testmod('mercurial.hgweb.hgwebdir_mod')
+testmod('mercurial.match')
+testmod('mercurial.minirst')
+testmod('mercurial.revset')
+testmod('mercurial.store')
+testmod('mercurial.templatefilters')
+testmod('mercurial.ui')
+testmod('mercurial.url')
+testmod('mercurial.util')
+testmod('mercurial.util', testtarget='platform')
+testmod('hgext.convert.cvsps')
+testmod('hgext.convert.filemap')
--- a/tests/test-filecache.py	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-filecache.py	Thu Nov 21 15:08:30 2013 -0600
@@ -18,9 +18,10 @@
     def sjoin(self, p):
         return p
 
-    @filecache('x')
+    @filecache('x', 'y')
     def cached(self):
         print 'creating'
+        return 'string from function'
 
     def invalidate(self):
         for k in self._filecache:
@@ -30,17 +31,20 @@
                 pass
 
 def basic(repo):
-    # file doesn't exist, calls function
+    print "* neither file exists"
+    # calls function
     repo.cached
 
     repo.invalidate()
-    # file still doesn't exist, uses cache
+    print "* neither file still exists"
+    # uses cache
     repo.cached
 
     # create empty file
     f = open('x', 'w')
     f.close()
     repo.invalidate()
+    print "* empty file x created"
     # should recreate the object
     repo.cached
 
@@ -48,11 +52,13 @@
     f.write('a')
     f.close()
     repo.invalidate()
+    print "* file x changed size"
     # should recreate the object
     repo.cached
 
     repo.invalidate()
-    # stats file again, nothing changed, reuses object
+    print "* nothing changed with either file"
+    # stats file again, reuses object
     repo.cached
 
     # atomic replace file, size doesn't change
@@ -63,6 +69,42 @@
     f.close()
 
     repo.invalidate()
+    print "* file x changed inode"
+    repo.cached
+
+    # create empty file y
+    f = open('y', 'w')
+    f.close()
+    repo.invalidate()
+    print "* empty file y created"
+    # should recreate the object
+    repo.cached
+
+    f = open('y', 'w')
+    f.write('A')
+    f.close()
+    repo.invalidate()
+    print "* file y changed size"
+    # should recreate the object
+    repo.cached
+
+    f = scmutil.opener('.')('y', 'w', atomictemp=True)
+    f.write('B')
+    f.close()
+
+    repo.invalidate()
+    print "* file y changed inode"
+    repo.cached
+
+    f = scmutil.opener('.')('x', 'w', atomictemp=True)
+    f.write('c')
+    f.close()
+    f = scmutil.opener('.')('y', 'w', atomictemp=True)
+    f.write('C')
+    f.close()
+
+    repo.invalidate()
+    print "* both files changed inode"
     repo.cached
 
 def fakeuncacheable():
@@ -76,10 +118,11 @@
     origcacheable = extensions.wrapfunction(util.cachestat, 'cacheable',
                                             wrapcacheable)
 
-    try:
-        os.remove('x')
-    except OSError:
-        pass
+    for fn in ['x', 'y']:
+        try:
+            os.remove(fn)
+        except OSError:
+            pass
 
     basic(fakerepo())
 
@@ -103,13 +146,28 @@
 
 def setbeforeget(repo):
     os.remove('x')
-    repo.cached = 0
+    os.remove('y')
+    repo.cached = 'string set externally'
     repo.invalidate()
+    print "* neither file exists"
     print repo.cached
     repo.invalidate()
     f = open('x', 'w')
     f.write('a')
     f.close()
+    print "* file x created"
+    print repo.cached
+
+    repo.cached = 'string 2 set externally'
+    repo.invalidate()
+    print "* string set externally again"
+    print repo.cached
+
+    repo.invalidate()
+    f = open('y', 'w')
+    f.write('b')
+    f.close()
+    print "* file y created"
     print repo.cached
 
 print 'basic:'
--- a/tests/test-filecache.py.out	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-filecache.py.out	Thu Nov 21 15:08:30 2013 -0600
@@ -1,17 +1,45 @@
 basic:
 
+* neither file exists
 creating
+* neither file still exists
+* empty file x created
+creating
+* file x changed size
 creating
+* nothing changed with either file
+* file x changed inode
 creating
+* empty file y created
+creating
+* file y changed size
+creating
+* file y changed inode
+creating
+* both files changed inode
 creating
 
 fakeuncacheable:
 
+* neither file exists
 creating
+* neither file still exists
 creating
+* empty file x created
+creating
+* file x changed size
+creating
+* nothing changed with either file
 creating
+* file x changed inode
 creating
+* empty file y created
 creating
+* file y changed size
+creating
+* file y changed inode
+creating
+* both files changed inode
 creating
 repository tip rolled back to revision -1 (undo commit)
 working directory now based on revision -1
@@ -20,6 +48,13 @@
 
 setbeforeget:
 
-0
+* neither file exists
+string set externally
+* file x created
 creating
-None
+string from function
+* string set externally again
+string 2 set externally
+* file y created
+creating
+string from function
--- a/tests/test-journal-exists.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-journal-exists.t	Thu Nov 21 15:08:30 2013 -0600
@@ -22,7 +22,7 @@
 
 Check that zero-size journals are correctly aborted:
 
-#if unix-permissions
+#if unix-permissions no-root
   $ hg bundle -qa repo.hg
   $ chmod -w foo/.hg/store/00changelog.i
 
--- a/tests/test-lock-badness.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-lock-badness.t	Thu Nov 21 15:08:30 2013 -0600
@@ -1,5 +1,4 @@
-  $ "$TESTDIR/hghave" unix-permissions || exit 80
-
+#if unix-permissions no-root
   $ hg init a
   $ echo a > a/a
   $ hg -R a ci -A -m a
@@ -21,4 +20,4 @@
   [255]
 
   $ chmod 700 a/.hg/store
-
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-module-imports.t	Thu Nov 21 15:08:30 2013 -0600
@@ -0,0 +1,39 @@
+This code uses the ast module, which was new in 2.6, so we'll skip
+this test on anything earlier.
+  $ python -c 'import sys ; assert sys.version_info >= (2, 6)' || exit 80
+
+  $ import_checker="$TESTDIR"/../contrib/import-checker.py
+Run the doctests from the import checker, and make sure
+it's working correctly.
+  $ TERM=dumb
+  $ export TERM
+  $ python -m doctest $import_checker
+
+  $ cd "$TESTDIR"/..
+  $ if hg identify -q > /dev/null 2>&1; then :
+  > else
+  >     echo "skipped: not a Mercurial working dir" >&2
+  >     exit 80
+  > fi
+
+There are a handful of cases here that require renaming a module so it
+doesn't overlap with a stdlib module name. There are also some cycles
+here that we should still endeavor to fix, and some cycles will be
+hidden by deduplication algorithm in the cycle detector, so fixing
+these may expose other cycles.
+
+  $ hg locate 'mercurial/**.py' | xargs python "$import_checker"
+  mercurial/dispatch.py mixed stdlib and relative imports:
+     commands, error, extensions, fancyopts, hg, hook, util
+  mercurial/fileset.py mixed stdlib and relative imports:
+     error, merge, parser, util
+  mercurial/revset.py mixed stdlib and relative imports:
+     discovery, error, hbisect, parser, phases, util
+  mercurial/templater.py mixed stdlib and relative imports:
+     config, error, parser, templatefilters, util
+  mercurial/ui.py mixed stdlib and relative imports:
+     config, error, formatter, scmutil, util
+  Import cycle: mercurial.cmdutil -> mercurial.subrepo -> mercurial.cmdutil
+  Import cycle: mercurial.repoview -> mercurial.revset -> mercurial.repoview
+  Import cycle: mercurial.fileset -> mercurial.merge -> mercurial.subrepo -> mercurial.match -> mercurial.fileset
+  Import cycle: mercurial.filemerge -> mercurial.match -> mercurial.fileset -> mercurial.merge -> mercurial.filemerge
--- a/tests/test-obsolete-checkheads.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-obsolete-checkheads.t	Thu Nov 21 15:08:30 2013 -0600
@@ -261,9 +261,6 @@
 
 Push should not complain about new heads.
 
-It should not complain about "unsynced remote changes!" either but that's not
-handled yet.
-
   $ hg push --traceback
   pushing to $TESTTMP/remote (glob)
   searching for changes
--- a/tests/test-permissions.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-permissions.t	Thu Nov 21 15:08:30 2013 -0600
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" unix-permissions || exit 80
+#ifdef unix-permissions no-root
 
   $ hg init t
   $ cd t
@@ -70,3 +70,5 @@
   $ chmod +rx dir
 
   $ cd ..
+
+#endif
--- a/tests/test-phases-exchange.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-phases-exchange.t	Thu Nov 21 15:08:30 2013 -0600
@@ -1062,7 +1062,7 @@
   |
   o  0 public a-A - 054250a37db4
   
-#if unix-permissions
+#if unix-permissions no-root
 
 Pushing From an unlockable repo
 --------------------------------
--- a/tests/test-pull-permission.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-pull-permission.t	Thu Nov 21 15:08:30 2013 -0600
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" unix-permissions || exit 80
+#if unix-permissions no-root
 
   $ hg init a
   $ cd a
@@ -30,3 +30,5 @@
   1 files, 1 changesets, 1 total revisions
 
   $ cd ..
+
+#endif
--- a/tests/test-push-warn.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-push-warn.t	Thu Nov 21 15:08:30 2013 -0600
@@ -38,8 +38,8 @@
   query 2; still undecided: 1, sample size is: 1
   2 total queries
   listing keys for "bookmarks"
-  new remote heads on branch 'default'
-  new remote head 1e108cc5548c
+  new remote heads on branch 'default':
+   1e108cc5548c
   abort: push creates new remote head 1e108cc5548c!
   (pull and merge or see "hg help push" for details about pushing new heads)
   [255]
@@ -129,9 +129,9 @@
   $ hg push -v -r 3 -r 4 ../c
   pushing to ../c
   searching for changes
-  new remote heads on branch 'default'
-  new remote head a5dda829a167
-  new remote head ee8fbc7a0295
+  new remote heads on branch 'default':
+   a5dda829a167
+   ee8fbc7a0295
   abort: push creates new remote head a5dda829a167!
   (merge or see "hg help push" for details about pushing new heads)
   [255]
@@ -367,7 +367,7 @@
   $ hg push --branch f --new-branch ../f
   pushing to ../f
   searching for changes
-  abort: push creates multiple headed new branch 'f'
+  abort: push creates new branch 'f' with multiple heads
   (merge or see "hg help push" for details about pushing new heads)
   [255]
   $ hg push --branch f --new-branch --force ../f
--- a/tests/test-repair-strip.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-repair-strip.t	Thu Nov 21 15:08:30 2013 -0600
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" unix-permissions || exit 80
+#if unix-permissions no-root
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "mq=">> $HGRCPATH
@@ -130,3 +130,5 @@
   2 files, 2 changesets, 2 total revisions
 
   $ cd ..
+
+#endif
--- a/tests/test-serve.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-serve.t	Thu Nov 21 15:08:30 2013 -0600
@@ -45,12 +45,14 @@
 
 With -v and -p daytime (should fail because low port)
 
+#if no-root
   $ KILLQUIETLY=Y
   $ hgserve -p daytime
   abort: cannot start server at 'localhost:13': Permission denied
   abort: child process failed to start
   % errors
   $ KILLQUIETLY=N
+#endif
 
 With --prefix foo
 
--- a/tests/test-transplant.t	Thu Nov 21 11:30:52 2013 -0600
+++ b/tests/test-transplant.t	Thu Nov 21 15:08:30 2013 -0600
@@ -430,6 +430,20 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files
+
+test "--merge" causing pull from source repository on local host
+
+  $ hg --config extensions.mq= -q strip 2
+  $ hg transplant -s ../t --merge tip
+  searching for changes
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  applying a53251cdf717
+  4:a53251cdf717 merged at 4831f4dc831a
+
   $ cd ..