changeset 22173:d3702a822241

merge with stable
author Matt Mackall <mpm@selenic.com>
date Thu, 14 Aug 2014 16:25:47 -0500
parents 3ddfb9b3fdc6 (diff) 989c16c1b050 (current diff)
children 0cc2db64c335
files hgext/largefiles/overrides.py mercurial/cmdutil.py tests/test-run-tests.t
diffstat 186 files changed, 3426 insertions(+), 1404 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/check-code.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/contrib/check-code.py	Thu Aug 14 16:25:47 2014 -0500
@@ -94,7 +94,7 @@
     (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
     (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
     (r'echo -n', "don't use 'echo -n', use printf"),
-    (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
+    (r'(^| )\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
     (r'head -c', "don't use 'head -c', use 'dd'"),
     (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
     (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
@@ -179,12 +179,14 @@
 ]
 
 for i in [0, 1]:
-    for p, m in testpats[i]:
+    for tp in testpats[i]:
+        p = tp[0]
+        m = tp[1]
         if p.startswith(r'^'):
             p = r"^  [$>] (%s)" % p[1:]
         else:
             p = r"^  [$>] .*(%s)" % p
-        utestpats[i].append((p, m))
+        utestpats[i].append((p, m) + tp[2:])
 
 utestfilters = [
     (r"<<(\S+)((.|\n)*?\n  > \1)", rephere),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/check-commit	Thu Aug 14 16:25:47 2014 -0500
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 Matt Mackall <mpm@selenic.com>
+#
+# A tool/hook to run basic sanity checks on commits/patches for
+# submission to Mercurial. Install by adding the following to your
+# .hg/hgrc:
+#
+# [hooks]
+# pretxncommit = contrib/check-commit
+#
+# The hook can be temporarily bypassed with:
+#
+# $ BYPASS= hg commit
+#
+# See also: http://mercurial.selenic.com/wiki/ContributingChanges
+
+import re, sys, os
+
+errors = [
+    (r"[(]bc[)]", "(BC) needs to be uppercase"),
+    (r"[(]issue \d\d\d", "no space allowed between issue and number"),
+    (r"[(]bug", "use (issueDDDD) instead of bug"),
+    (r"^# User [^@\n]+$", "username is not an email address"),
+    (r"^# .*\n(?!merge with )[^#]\S+[^:] ",
+     "summary line doesn't start with 'topic: '"),
+    (r"^# .*\n[A-Z][a-z]\S+", "don't capitalize summary lines"),
+    (r"^# .*\n.*\.\s+$", "don't add trailing period on summary line"),
+    (r"^# .*\n.{78,}", "summary line too long"),
+    (r"^\+\n \n", "adds double empty line"),
+    (r"\+\s+def [a-z]+_[a-z]", "adds a function with foo_bar naming"),
+]
+
+node = os.environ.get("HG_NODE")
+
+if node:
+    commit = os.popen("hg export %s" % node).read()
+else:
+    commit = sys.stdin.read()
+
+exitcode = 0
+for exp, msg in errors:
+    m = re.search(exp, commit, re.MULTILINE)
+    if m:
+        pos = 0
+        for n, l in enumerate(commit.splitlines(True)):
+            pos += len(l)
+            if pos >= m.end():
+                print "%d: %s" % (n, msg)
+                print " %s" % l[:-1]
+                if "BYPASS" not in os.environ:
+                    exitcode = 1
+                break
+
+sys.exit(exitcode)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/hg-test-mode.el	Thu Aug 14 16:25:47 2014 -0500
@@ -0,0 +1,56 @@
+;; hg-test-mode.el - Major mode for editing Mercurial tests
+;;
+;; Copyright 2014 Matt Mackall <mpm@selenic.com>
+;; "I have no idea what I'm doing"
+;;
+;; This software may be used and distributed according to the terms of the
+;; GNU General Public License version 2 or any later version.
+;;
+;; To enable, add something like the following to your .emacs:
+;;
+;; (if (file-exists-p "~/hg/contrib/hg-test-mode.el")
+;;    (load "~/hg/contrib/hg-test-mode.el"))
+
+(defvar hg-test-mode-hook nil)
+
+(defvar hg-test-mode-map
+  (let ((map (make-keymap)))
+    (define-key map "\C-j" 'newline-and-indent)
+    map)
+  "Keymap for hg test major mode")
+
+(add-to-list 'auto-mode-alist '("\\.t\\'" . hg-test-mode))
+
+(defconst hg-test-font-lock-keywords-1
+  (list
+   '("^  \\(\\$\\|>>>\\) " 1 font-lock-builtin-face)
+   '("^  \\(>\\|\\.\\.\\.\\) " 1 font-lock-constant-face)
+   '("^  \\([[][0-9]+[]]\\)$" 1 font-lock-warning-face)
+   '("^  \\(.*?\\)\\(\\( [(][-a-z]+[)]\\)*\\)$" 1 font-lock-string-face)
+   '("\\$?\\(HG\\|TEST\\)\\w+=?" . font-lock-variable-name-face)
+   '("^  \\(.*?\\)\\(\\( [(][-a-z]+[)]\\)+\\)$" 2 font-lock-type-face)
+   '("^#.*" . font-lock-preprocessor-face)
+   '("^\\([^ ].*\\)$" 1 font-lock-comment-face)
+   )
+  "Minimal highlighting expressions for hg-test mode")
+
+(defvar hg-test-font-lock-keywords hg-test-font-lock-keywords-1
+  "Default highlighting expressions for hg-test mode")
+
+(defvar hg-test-mode-syntax-table
+  (let ((st (make-syntax-table)))
+    (modify-syntax-entry ?\" "w" st) ;; disable standard quoting
+    st)
+"Syntax table for hg-test mode")
+
+(defun hg-test-mode ()
+  (interactive)
+  (kill-all-local-variables)
+  (use-local-map hg-test-mode-map)
+  (set-syntax-table hg-test-mode-syntax-table)
+  (set (make-local-variable 'font-lock-defaults) '(hg-test-font-lock-keywords))
+  (setq major-mode 'hg-test-mode)
+  (setq mode-name "hg-test")
+  (run-hooks 'hg-test-mode-hook))
+
+(provide 'hg-test-mode)
--- a/contrib/revsetbenchmarks.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/contrib/revsetbenchmarks.py	Thu Aug 14 16:25:47 2014 -0500
@@ -19,8 +19,6 @@
 # cannot use argparse, python 2.7 only
 from optparse import OptionParser
 
-
-
 def check_output(*args, **kwargs):
     kwargs.setdefault('stderr', PIPE)
     kwargs.setdefault('stdout', PIPE)
--- a/contrib/simplemerge	Thu Aug 14 16:18:45 2014 -0500
+++ b/contrib/simplemerge	Thu Aug 14 16:25:47 2014 -0500
@@ -11,8 +11,7 @@
            ('a', 'text', None, _('treat all files as text')),
            ('p', 'print', None,
             _('print results instead of overwriting LOCAL')),
-           ('', 'no-minimal', None,
-            _('do not try to minimize conflict regions')),
+           ('', 'no-minimal', None, _('no effect (DEPRECATED)')),
            ('h', 'help', None, _('display help and exit')),
            ('q', 'quiet', None, _('suppress output'))]
 
--- a/hgext/fetch.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/fetch.py	Thu Aug 14 16:25:47 2014 -0500
@@ -143,8 +143,8 @@
                        ('Automated merge with %s' %
                         util.removeauth(other.url())))
             editopt = opts.get('edit') or opts.get('force_editor')
-            n = repo.commit(message, opts['user'], opts['date'],
-                            editor=cmdutil.getcommiteditor(edit=editopt))
+            editor = cmdutil.getcommiteditor(edit=editopt, editform='fetch')
+            n = repo.commit(message, opts['user'], opts['date'], editor=editor)
             ui.status(_('new changeset %d:%s merges remote changes '
                         'with local\n') % (repo.changelog.rev(n),
                                            short(n)))
--- a/hgext/gpg.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/gpg.py	Thu Aug 14 16:25:47 2014 -0500
@@ -277,8 +277,9 @@
                              % hgnode.short(n)
                              for n in nodes])
     try:
+        editor = cmdutil.getcommiteditor(editform='gpg.sign', **opts)
         repo.commit(message, opts['user'], opts['date'], match=msigs,
-                    editor=cmdutil.getcommiteditor(**opts))
+                    editor=editor)
     except ValueError, inst:
         raise util.Abort(str(inst))
 
--- a/hgext/histedit.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/histedit.py	Thu Aug 14 16:25:47 2014 -0500
@@ -36,6 +36,7 @@
  #  p, pick = use commit
  #  e, edit = use commit, but stop for amending
  #  f, fold = use commit, but combine it with the one above
+ #  r, roll = like fold, but discard this commit's description
  #  d, drop = remove commit from history
  #  m, mess = edit message without changing commit content
  #
@@ -57,6 +58,7 @@
  #  p, pick = use commit
  #  e, edit = use commit, but stop for amending
  #  f, fold = use commit, but combine it with the one above
+ #  r, roll = like fold, but discard this commit's description
  #  d, drop = remove commit from history
  #  m, mess = edit message without changing commit content
  #
@@ -179,6 +181,7 @@
 #  p, pick = use commit
 #  e, edit = use commit, but stop for amending
 #  f, fold = use commit, but combine it with the one above
+#  r, roll = like fold, but discard this commit's description
 #  d, drop = remove commit from history
 #  m, mess = edit message without changing commit content
 #
@@ -208,8 +211,6 @@
             repo.ui.restoreconfig(phasebackup)
     return commitfunc
 
-
-
 def applychanges(ui, repo, ctx, opts):
     """Merge changeset from ctx (only) in the current working directory"""
     wcpar = repo.dirstate.parents()[0]
@@ -293,6 +294,9 @@
     extra = commitopts.get('extra')
 
     parents = (first.p1().node(), first.p2().node())
+    editor = None
+    if not commitopts.get('rollup'):
+        editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
     new = context.memctx(repo,
                          parents=parents,
                          text=message,
@@ -301,7 +305,7 @@
                          user=user,
                          date=date,
                          extra=extra,
-                         editor=cmdutil.getcommiteditor(edit=True))
+                         editor=editor)
     return repo.commitctx(new)
 
 def pick(ui, repo, ctx, ha, opts):
@@ -334,6 +338,11 @@
         _('Make changes as needed, you may commit or record as needed now.\n'
           'When you are finished, run hg histedit --continue to resume.'))
 
+def rollup(ui, repo, ctx, ha, opts):
+    rollupopts = opts.copy()
+    rollupopts['rollup'] = True
+    return fold(ui, repo, ctx, ha, rollupopts)
+
 def fold(ui, repo, ctx, ha, opts):
     oldctx = repo[ha]
     hg.update(repo, ctx.node())
@@ -356,10 +365,13 @@
     commitopts = opts.copy()
     commitopts['user'] = ctx.user()
     # commit message
-    newmessage = '\n***\n'.join(
-        [ctx.description()] +
-        [repo[r].description() for r in internalchanges] +
-        [oldctx.description()]) + '\n'
+    if opts.get('rollup'):
+        newmessage = ctx.description()
+    else:
+        newmessage = '\n***\n'.join(
+            [ctx.description()] +
+            [repo[r].description() for r in internalchanges] +
+            [oldctx.description()]) + '\n'
     commitopts['message'] = newmessage
     # date
     commitopts['date'] = max(ctx.date(), oldctx.date())
@@ -400,9 +412,10 @@
             _('Fix up the change and run hg histedit --continue'))
     message = oldctx.description()
     commit = commitfuncfor(repo, oldctx)
+    editor = cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
     new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
                  extra=oldctx.extra(),
-                 editor=cmdutil.getcommiteditor(edit=True))
+                 editor=editor)
     newctx = repo[new]
     if oldctx.node() != newctx.node():
         return newctx, [(oldctx.node(), (new,))]
@@ -439,6 +452,8 @@
                'edit': edit,
                'f': fold,
                'fold': fold,
+               'r': rollup,
+               'roll': rollup,
                'd': drop,
                'drop': drop,
                'm': message,
@@ -674,12 +689,14 @@
     m, a, r, d = repo.status()[:4]
     if m or a or r or d:
         # prepare the message for the commit to comes
-        if action in ('f', 'fold'):
+        if action in ('f', 'fold', 'r', 'roll'):
             message = 'fold-temp-revision %s' % currentnode
         else:
             message = ctx.description()
         editopt = action in ('e', 'edit', 'm', 'mess')
-        editor = cmdutil.getcommiteditor(edit=editopt)
+        canonaction = {'e': 'edit', 'm': 'mess', 'p': 'pick'}
+        editform = 'histedit.%s' % canonaction.get(action, action)
+        editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
         commit = commitfuncfor(repo, ctx)
         new = commit(text=message, user=ctx.user(),
                      date=ctx.date(), extra=ctx.extra(),
@@ -695,15 +712,19 @@
         # to parent.
         replacements.append((ctx.node(), tuple(newchildren)))
 
-    if action in ('f', 'fold'):
+    if action in ('f', 'fold', 'r', 'roll'):
         if newchildren:
             # finalize fold operation if applicable
             if new is None:
                 new = newchildren[-1]
             else:
                 newchildren.pop()  # remove new from internal changes
-            parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
-                                         newchildren)
+            foldopts = opts
+            if action in ('r', 'roll'):
+                foldopts = foldopts.copy()
+                foldopts['rollup'] = True
+            parentctx, repl = finishfold(ui, repo, parentctx, ctx, new,
+                                         foldopts, newchildren)
             replacements.extend(repl)
         else:
             # newchildren is empty if the fold did not result in any commit
--- a/hgext/keyword.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/keyword.py	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,6 @@
 # keyword.py - $Keyword$ expansion for Mercurial
 #
-# Copyright 2007-2012 Christian Ebert <blacktrash@gmx.net>
+# Copyright 2007-2014 Christian Ebert <blacktrash@gmx.net>
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
@@ -87,7 +87,7 @@
 from mercurial import scmutil, pathutil
 from mercurial.hgweb import webcommands
 from mercurial.i18n import _
-import os, re, shutil, tempfile
+import os, re, tempfile
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
@@ -450,7 +450,12 @@
     repo.commit(text=msg)
     ui.status(_('\n\tkeywords expanded\n'))
     ui.write(repo.wread(fn))
-    shutil.rmtree(tmpdir, ignore_errors=True)
+    for root, dirs, files in os.walk(tmpdir, topdown=False):
+        for f in files:
+            util.unlink(os.path.join(root, f))
+        for d in dirs:
+            os.rmdir(os.path.join(root, d))
+    os.rmdir(tmpdir)
 
 @command('kwexpand',
     commands.walkopts,
--- a/hgext/largefiles/lfcommands.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/largefiles/lfcommands.py	Thu Aug 14 16:25:47 2014 -0500
@@ -510,26 +510,7 @@
 
             updated += update1
 
-            standin = lfutil.standin(lfile)
-            if standin in repo.dirstate:
-                stat = repo.dirstate._map[standin]
-                state, mtime = stat[0], stat[3]
-            else:
-                state, mtime = '?', -1
-            if state == 'n':
-                if normallookup or mtime < 0:
-                    # state 'n' doesn't ensure 'clean' in this case
-                    lfdirstate.normallookup(lfile)
-                else:
-                    lfdirstate.normal(lfile)
-            elif state == 'm':
-                lfdirstate.normallookup(lfile)
-            elif state == 'r':
-                lfdirstate.remove(lfile)
-            elif state == 'a':
-                lfdirstate.add(lfile)
-            elif state == '?':
-                lfdirstate.drop(lfile)
+            lfutil.synclfdirstate(repo, lfdirstate, lfile, normallookup)
 
         lfdirstate.write()
         if printmessage and lfiles:
--- a/hgext/largefiles/lfutil.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/largefiles/lfutil.py	Thu Aug 14 16:25:47 2014 -0500
@@ -363,6 +363,28 @@
         standins.append((lfile, hash))
     return standins
 
+def synclfdirstate(repo, lfdirstate, lfile, normallookup):
+    lfstandin = standin(lfile)
+    if lfstandin in repo.dirstate:
+        stat = repo.dirstate._map[lfstandin]
+        state, mtime = stat[0], stat[3]
+    else:
+        state, mtime = '?', -1
+    if state == 'n':
+        if normallookup or mtime < 0:
+            # state 'n' doesn't ensure 'clean' in this case
+            lfdirstate.normallookup(lfile)
+        else:
+            lfdirstate.normal(lfile)
+    elif state == 'm':
+        lfdirstate.normallookup(lfile)
+    elif state == 'r':
+        lfdirstate.remove(lfile)
+    elif state == 'a':
+        lfdirstate.add(lfile)
+    elif state == '?':
+        lfdirstate.drop(lfile)
+
 def getlfilestoupdate(oldstandins, newstandins):
     changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
     filelist = []
--- a/hgext/largefiles/overrides.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/largefiles/overrides.py	Thu Aug 14 16:25:47 2014 -0500
@@ -1157,19 +1157,20 @@
     repo.status = oldstatus
 
 def overriderollback(orig, ui, repo, **opts):
-    result = orig(ui, repo, **opts)
-    merge.update(repo, node=None, branchmerge=False, force=True,
-        partial=lfutil.isstandin)
     wlock = repo.wlock()
     try:
+        result = orig(ui, repo, **opts)
+        merge.update(repo, node=None, branchmerge=False, force=True,
+                     partial=lfutil.isstandin)
+
         lfdirstate = lfutil.openlfdirstate(ui, repo)
+        orphans = set(lfdirstate)
         lfiles = lfutil.listlfiles(repo)
-        oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
         for file in lfiles:
-            if file in oldlfiles:
-                lfdirstate.normallookup(file)
-            else:
-                lfdirstate.add(file)
+            lfutil.synclfdirstate(repo, lfdirstate, file, True)
+            orphans.discard(file)
+        for lfile in orphans:
+            lfdirstate.drop(lfile)
         lfdirstate.write()
     finally:
         wlock.release()
--- a/hgext/largefiles/reposetup.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/largefiles/reposetup.py	Thu Aug 14 16:25:47 2014 -0500
@@ -285,6 +285,16 @@
                                             printmessage=False)
                     result = orig(text=text, user=user, date=date, match=match,
                                     force=force, editor=editor, extra=extra)
+
+                    if result:
+                        lfdirstate = lfutil.openlfdirstate(ui, self)
+                        for f in self[result].files():
+                            if lfutil.isstandin(f):
+                                lfile = lfutil.splitstandin(f)
+                                lfutil.synclfdirstate(self, lfdirstate, lfile,
+                                                      False)
+                        lfdirstate.write()
+
                     return result
                 # Case 1: user calls commit with no specific files or
                 # include/exclude patterns: refresh and commit all files that
--- a/hgext/mq.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/mq.py	Thu Aug 14 16:25:47 2014 -0500
@@ -621,7 +621,7 @@
 
         # apply failed, strip away that rev and merge.
         hg.clean(repo, head)
-        strip(self.ui, repo, [n], update=False, backup='strip')
+        strip(self.ui, repo, [n], update=False, backup=False)
 
         ctx = repo[rev]
         ret = hg.merge(repo, rev)
@@ -930,7 +930,12 @@
             oldqbase = repo[qfinished[0]]
             tphase = repo.ui.config('phases', 'new-commit', phases.draft)
             if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
-                phases.advanceboundary(repo, tphase, qfinished)
+                tr = repo.transaction('qfinish')
+                try:
+                    phases.advanceboundary(repo, tr, tphase, qfinished)
+                    tr.close()
+                finally:
+                    tr.release()
 
     def delete(self, repo, patches, opts):
         if not patches and not opts.get('rev'):
@@ -1025,6 +1030,7 @@
         """
         msg = opts.get('msg')
         edit = opts.get('edit')
+        editform = opts.get('editform', 'mq.qnew')
         user = opts.get('user')
         date = opts.get('date')
         if date:
@@ -1079,7 +1085,7 @@
                         p.write("# Date %s %s\n\n" % date)
 
                 defaultmsg = "[mq]: %s" % patchfn
-                editor = cmdutil.getcommiteditor()
+                editor = cmdutil.getcommiteditor(editform=editform)
                 if edit:
                     def finishdesc(desc):
                         if desc.rstrip():
@@ -1089,7 +1095,8 @@
                     # i18n: this message is shown in editor with "HG: " prefix
                     extramsg = _('Leave message empty to use default message.')
                     editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
-                                                     extramsg=extramsg)
+                                                     extramsg=extramsg,
+                                                     editform=editform)
                     commitmsg = msg
                 else:
                     commitmsg = msg or defaultmsg
@@ -1456,7 +1463,7 @@
             for patch in reversed(self.applied[start:end]):
                 self.ui.status(_("popping %s\n") % patch.name)
             del self.applied[start:end]
-            strip(self.ui, repo, [rev], update=False, backup='strip')
+            strip(self.ui, repo, [rev], update=False, backup=False)
             for s, state in repo['.'].substate.items():
                 repo['.'].sub(s).get(state)
             if self.applied:
@@ -1485,6 +1492,7 @@
             return 1
         msg = opts.get('msg', '').rstrip()
         edit = opts.get('edit')
+        editform = opts.get('editform', 'mq.qrefresh')
         newuser = opts.get('user')
         newdate = opts.get('date')
         if newdate:
@@ -1645,7 +1653,7 @@
                 repo.setparents(*cparents)
                 self.applied.pop()
                 self.applieddirty = True
-                strip(self.ui, repo, [top], update=False, backup='strip')
+                strip(self.ui, repo, [top], update=False, backup=False)
             except: # re-raises
                 repo.dirstate.invalidate()
                 raise
@@ -1654,7 +1662,7 @@
                 # might be nice to attempt to roll back strip after this
 
                 defaultmsg = "[mq]: %s" % patchfn
-                editor = cmdutil.getcommiteditor()
+                editor = cmdutil.getcommiteditor(editform=editform)
                 if edit:
                     def finishdesc(desc):
                         if desc.rstrip():
@@ -1664,7 +1672,8 @@
                     # i18n: this message is shown in editor with "HG: " prefix
                     extramsg = _('Leave message empty to use default message.')
                     editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
-                                                     extramsg=extramsg)
+                                                     extramsg=extramsg,
+                                                     editform=editform)
                     message = msg or "\n".join(ph.message)
                 elif not msg:
                     if not ph.message:
@@ -1842,7 +1851,7 @@
                     update = True
                 else:
                     update = False
-                strip(self.ui, repo, [rev], update=update, backup='strip')
+                strip(self.ui, repo, [rev], update=update, backup=False)
         if qpp:
             self.ui.warn(_("saved queue repository parents: %s %s\n") %
                          (short(qpp[0]), short(qpp[1])))
@@ -1966,41 +1975,49 @@
                 lastparent = None
 
             diffopts = self.diffopts({'git': git})
-            for r in rev:
-                if not repo[r].mutable():
-                    raise util.Abort(_('revision %d is not mutable') % r,
-                                     hint=_('see "hg help phases" for details'))
-                p1, p2 = repo.changelog.parentrevs(r)
-                n = repo.changelog.node(r)
-                if p2 != nullrev:
-                    raise util.Abort(_('cannot import merge revision %d') % r)
-                if lastparent and lastparent != r:
-                    raise util.Abort(_('revision %d is not the parent of %d')
-                                     % (r, lastparent))
-                lastparent = p1
-
-                if not patchname:
-                    patchname = normname('%d.diff' % r)
-                checkseries(patchname)
-                self.checkpatchname(patchname, force)
-                self.fullseries.insert(0, patchname)
-
-                patchf = self.opener(patchname, "w")
-                cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
-                patchf.close()
-
-                se = statusentry(n, patchname)
-                self.applied.insert(0, se)
-
-                self.added.append(patchname)
-                imported.append(patchname)
-                patchname = None
-                if rev and repo.ui.configbool('mq', 'secret', False):
-                    # if we added anything with --rev, move the secret root
-                    phases.retractboundary(repo, phases.secret, [n])
-                self.parseseries()
-                self.applieddirty = True
-                self.seriesdirty = True
+            tr = repo.transaction('qimport')
+            try:
+                for r in rev:
+                    if not repo[r].mutable():
+                        raise util.Abort(_('revision %d is not mutable') % r,
+                                         hint=_('see "hg help phases" '
+                                                'for details'))
+                    p1, p2 = repo.changelog.parentrevs(r)
+                    n = repo.changelog.node(r)
+                    if p2 != nullrev:
+                        raise util.Abort(_('cannot import merge revision %d')
+                                         % r)
+                    if lastparent and lastparent != r:
+                        raise util.Abort(_('revision %d is not the parent of '
+                                           '%d')
+                                         % (r, lastparent))
+                    lastparent = p1
+
+                    if not patchname:
+                        patchname = normname('%d.diff' % r)
+                    checkseries(patchname)
+                    self.checkpatchname(patchname, force)
+                    self.fullseries.insert(0, patchname)
+
+                    patchf = self.opener(patchname, "w")
+                    cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
+                    patchf.close()
+
+                    se = statusentry(n, patchname)
+                    self.applied.insert(0, se)
+
+                    self.added.append(patchname)
+                    imported.append(patchname)
+                    patchname = None
+                    if rev and repo.ui.configbool('mq', 'secret', False):
+                        # if we added anything with --rev, move the secret root
+                        phases.retractboundary(repo, tr, phases.secret, [n])
+                    self.parseseries()
+                    self.applieddirty = True
+                    self.seriesdirty = True
+                tr.close()
+            finally:
+                tr.release()
 
         for i, filename in enumerate(files):
             if existing:
@@ -2585,7 +2602,8 @@
     diffopts = q.patchopts(q.diffopts(), *patches)
     wlock = repo.wlock()
     try:
-        q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'))
+        q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'),
+                  editform='mq.qfold')
         q.delete(repo, patches, opts)
         q.savedirty()
     finally:
--- a/hgext/purge.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/purge.py	Thu Aug 14 16:25:47 2014 -0500
@@ -26,7 +26,7 @@
 
 from mercurial import util, commands, cmdutil, scmutil
 from mercurial.i18n import _
-import os, stat
+import os
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
@@ -95,17 +95,6 @@
         else:
             ui.write('%s%s' % (name, eol))
 
-    def removefile(path):
-        try:
-            os.remove(path)
-        except OSError:
-            # read-only files cannot be unlinked under Windows
-            s = os.stat(path)
-            if (s.st_mode & stat.S_IWRITE) != 0:
-                raise
-            os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
-            os.remove(path)
-
     directories = []
     match = scmutil.match(repo[None], dirs, opts)
     match.explicitdir = match.traversedir = directories.append
@@ -115,7 +104,7 @@
         for f in sorted(status[4] + status[5]):
             if act:
                 ui.note(_('removing file %s\n') % f)
-            remove(removefile, f)
+            remove(util.unlink, f)
 
     if removedirs:
         for f in sorted(directories, reverse=True):
--- a/hgext/rebase.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/rebase.py	Thu Aug 14 16:25:47 2014 -0500
@@ -138,7 +138,7 @@
     skipped = set()
     targetancestors = set()
 
-    editor = cmdutil.getcommiteditor(**opts)
+    editor = cmdutil.getcommiteditor(editform='rebase.normal', **opts)
 
     lock = wlock = None
     try:
@@ -383,7 +383,8 @@
                 for rebased in state:
                     if rebased not in skipped and state[rebased] > nullmerge:
                         commitmsg += '\n* %s' % repo[rebased].description()
-                editor = cmdutil.getcommiteditor(edit=True)
+                editform = 'rebase.collapse'
+                editor = cmdutil.getcommiteditor(edit=True, editform=editform)
             newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
                                   extrafn=extrafn, editor=editor)
             for oldrev in state.iterkeys():
@@ -468,15 +469,18 @@
         extra = {'rebase_source': ctx.hex()}
         if extrafn:
             extrafn(ctx, extra)
-        # Commit might fail if unresolved files exist
-        newrev = repo.commit(text=commitmsg, user=ctx.user(),
-                             date=ctx.date(), extra=extra, editor=editor)
+
+        backup = repo.ui.backupconfig('phases', 'new-commit')
+        try:
+            targetphase = max(ctx.phase(), phases.draft)
+            repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
+            # Commit might fail if unresolved files exist
+            newrev = repo.commit(text=commitmsg, user=ctx.user(),
+                                 date=ctx.date(), extra=extra, editor=editor)
+        finally:
+            repo.ui.restoreconfig(backup)
+
         repo.dirstate.setbranch(repo[newrev].branch())
-        targetphase = max(ctx.phase(), phases.draft)
-        # retractboundary doesn't overwrite upper phase inherited from parent
-        newnode = repo[newrev].node()
-        if newnode:
-            phases.retractboundary(repo, targetphase, [newnode])
         return newrev
     except util.Abort:
         # Invalidate the previous setparents
--- a/hgext/shelve.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/shelve.py	Thu Aug 14 16:25:47 2014 -0500
@@ -73,7 +73,8 @@
         try:
             gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
             changegroup.addchangegroup(self.repo, gen, 'unshelve',
-                                       'bundle:' + self.vfs.join(self.fname))
+                                       'bundle:' + self.vfs.join(self.fname),
+                                       targetphase=phases.secret)
         finally:
             fp.close()
 
@@ -177,10 +178,14 @@
         hasmq = util.safehasattr(repo, 'mq')
         if hasmq:
             saved, repo.mq.checkapplied = repo.mq.checkapplied, False
+        backup = repo.ui.backupconfig('phases', 'new-commit')
         try:
+            repo.ui. setconfig('phases', 'new-commit', phases.secret)
+            editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts)
             return repo.commit(message, user, opts.get('date'), match,
-                               editor=cmdutil.getcommiteditor(**opts))
+                               editor=editor)
         finally:
+            repo.ui.restoreconfig(backup)
             if hasmq:
                 repo.mq.checkapplied = saved
 
@@ -234,8 +239,6 @@
                 ui.status(_("nothing changed\n"))
             return 1
 
-        phases.retractboundary(repo, phases.secret, [node])
-
         fp = shelvedfile(repo, name, 'files').opener('wb')
         fp.write('\0'.join(shelvedfiles))
 
@@ -388,7 +391,7 @@
 
         mergefiles(ui, repo, state.wctx, state.pendingctx)
 
-        repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
+        repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
         shelvedstate.clear(repo)
         ui.warn(_("unshelve of '%s' aborted\n") % state.name)
     finally:
@@ -457,7 +460,7 @@
         mergefiles(ui, repo, state.wctx, shelvectx)
 
         state.stripnodes.append(shelvectx.node())
-        repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
+        repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
         shelvedstate.clear(repo)
         unshelvecleanup(ui, repo, state.name, opts)
         ui.status(_("unshelve of '%s' complete\n") % state.name)
@@ -558,10 +561,13 @@
                 if hasmq:
                     saved, repo.mq.checkapplied = repo.mq.checkapplied, False
 
+                backup = repo.ui.backupconfig('phases', 'new-commit')
                 try:
+                    repo.ui. setconfig('phases', 'new-commit', phases.secret)
                     return repo.commit(message, 'shelve@localhost',
                                        opts.get('date'), match)
                 finally:
+                    repo.ui.restoreconfig(backup)
                     if hasmq:
                         repo.mq.checkapplied = saved
 
@@ -574,8 +580,6 @@
 
         ui.quiet = True
         shelvedfile(repo, basename, 'hg').applybundle()
-        nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
-        phases.retractboundary(repo, phases.secret, nodes)
 
         ui.quiet = oldquiet
 
--- a/hgext/strip.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/strip.py	Thu Aug 14 16:25:47 2014 -0500
@@ -42,7 +42,7 @@
             raise util.Abort(_("local changed subrepos found" + excsuffix))
     return m, a, r, d
 
-def strip(ui, repo, revs, update=True, backup="all", force=None, bookmark=None):
+def strip(ui, repo, revs, update=True, backup=True, force=None, bookmark=None):
     wlock = lock = None
     try:
         wlock = repo.wlock()
@@ -114,11 +114,9 @@
 
     Return 0 on success.
     """
-    backup = 'all'
-    if opts.get('backup'):
-        backup = 'strip'
-    elif opts.get('no_backup') or opts.get('nobackup'):
-        backup = 'none'
+    backup = True
+    if opts.get('no_backup') or opts.get('nobackup'):
+        backup = False
 
     cl = repo.changelog
     revs = list(revs) + opts.get('rev')
--- a/hgext/transplant.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/hgext/transplant.py	Thu Aug 14 16:25:47 2014 -0500
@@ -86,7 +86,7 @@
         self.opener = scmutil.opener(self.path)
         self.transplants = transplants(self.path, 'transplants',
                                        opener=self.opener)
-        self.editor = cmdutil.getcommiteditor(**opts)
+        self.editor = cmdutil.getcommiteditor(editform='transplant', **opts)
 
     def applied(self, repo, node, parent):
         '''returns True if a node is already an ancestor of parent
--- a/mercurial/branchmap.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/branchmap.py	Thu Aug 14 16:25:47 2014 -0500
@@ -62,8 +62,6 @@
         partial = None
     return partial
 
-
-
 ### Nearest subset relation
 # Nearest subset of filter X is a filter Y so that:
 # * Y is included in X,
--- a/mercurial/changegroup.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/changegroup.py	Thu Aug 14 16:25:47 2014 -0500
@@ -569,7 +569,8 @@
 
     return revisions, files
 
-def addchangegroup(repo, source, srctype, url, emptyok=False):
+def addchangegroup(repo, source, srctype, url, emptyok=False,
+                   targetphase=phases.draft):
     """Add the changegroup returned by source.read() to this repo.
     srctype is a string like 'push', 'pull', or 'unbundle'.  url is
     the URL of the repo where this changegroup is coming from.
@@ -699,15 +700,18 @@
             # We should not use added here but the list of all change in
             # the bundle
             if publishing:
-                phases.advanceboundary(repo, phases.public, srccontent)
+                phases.advanceboundary(repo, tr, phases.public, srccontent)
             else:
-                phases.advanceboundary(repo, phases.draft, srccontent)
-                phases.retractboundary(repo, phases.draft, added)
+                # Those changesets have been pushed from the outside, their
+                # phases are going to be pushed alongside. Therefor
+                # `targetphase` is ignored.
+                phases.advanceboundary(repo, tr, phases.draft, srccontent)
+                phases.retractboundary(repo, tr, phases.draft, added)
         elif srctype != 'strip':
             # publishing only alter behavior during push
             #
             # strip should not touch boundary at all
-            phases.retractboundary(repo, phases.draft, added)
+            phases.retractboundary(repo, tr, targetphase, added)
 
         # make changelog see real files again
         cl.finalize(trp)
--- a/mercurial/cmdutil.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/cmdutil.py	Thu Aug 14 16:25:47 2014 -0500
@@ -109,7 +109,8 @@
                              (logfile, inst.strerror))
     return message
 
-def getcommiteditor(edit=False, finishdesc=None, extramsg=None, **opts):
+def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
+                    editform='', **opts):
     """get appropriate commit message editor according to '--edit' option
 
     'finishdesc' is a function to be called with edited commit message
@@ -122,6 +123,9 @@
     'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
     is automatically added.
 
+    'editform' is a dot-separated list of names, to distinguish
+    the purpose of commit text editing.
+
     'getcommiteditor' returns 'commitforceeditor' regardless of
     'edit', if one of 'finishdesc' or 'extramsg' is specified, because
     they are specific for usage in MQ.
@@ -129,7 +133,10 @@
     if edit or finishdesc or extramsg:
         return lambda r, c, s: commitforceeditor(r, c, s,
                                                  finishdesc=finishdesc,
-                                                 extramsg=extramsg)
+                                                 extramsg=extramsg,
+                                                 editform=editform)
+    elif editform:
+        return lambda r, c, s: commiteditor(r, c, s, editform=editform)
     else:
         return commiteditor
 
@@ -586,7 +593,7 @@
     tmpname, message, user, date, branch, nodeid, p1, p2 = \
         patch.extract(ui, hunk)
 
-    editor = getcommiteditor(**opts)
+    editor = getcommiteditor(editform='import.normal', **opts)
     update = not opts.get('bypass')
     strip = opts["strip"]
     sim = float(opts.get('similarity') or 0)
@@ -680,12 +687,13 @@
                                     files, eolmode=None)
                 except patch.PatchError, e:
                     raise util.Abort(str(e))
+                editor = getcommiteditor(editform='import.bypass')
                 memctx = context.makememctx(repo, (p1.node(), p2.node()),
                                             message,
                                             opts.get('user') or user,
                                             opts.get('date') or date,
                                             branch, files, store,
-                                            editor=getcommiteditor())
+                                            editor=editor)
                 n = memctx.commit()
             finally:
                 store.close()
@@ -1574,8 +1582,14 @@
     if not slowpath:
         for f in match.files():
             if follow and f not in pctx:
-                raise util.Abort(_('cannot follow file not in parent '
-                                   'revision: "%s"') % f)
+                # If the file exists, it may be a directory, so let it
+                # take the slow path.
+                if os.path.exists(repo.wjoin(f)):
+                    slowpath = True
+                    continue
+                else:
+                    raise util.Abort(_('cannot follow file not in parent '
+                                       'revision: "%s"') % f)
             filelog = repo.file(f)
             if not filelog:
                 # A zero count may be a directory or deleted file, so
@@ -1599,9 +1613,6 @@
     if slowpath:
         # See walkchangerevs() slow path.
         #
-        if follow:
-            raise util.Abort(_('can only follow copies/renames for explicit '
-                               'filenames'))
         # pats/include/exclude cannot be represented as separate
         # revset expressions as their filtering logic applies at file
         # level. For instance "-I a -X a" matches a revision touching
@@ -1633,7 +1644,10 @@
 
     filematcher = None
     if opts.get('patch') or opts.get('stat'):
-        if follow and not match.always():
+        # When following files, track renames via a special matcher.
+        # If we're forced to take the slowpath it means we're following
+        # at least one pattern/directory, so don't bother with rename tracking.
+        if follow and not match.always() and not slowpath:
             # _makelogfilematcher expects its files argument to be relative to
             # the repo root, so use match.files(), not pats.
             filematcher = _makefollowlogfilematcher(repo, match.files(),
@@ -2100,9 +2114,10 @@
 
                 user = opts.get('user') or old.user()
                 date = opts.get('date') or old.date()
-            editor = getcommiteditor(**opts)
+            editform = 'commit.amend'
+            editor = getcommiteditor(editform=editform, **opts)
             if not message:
-                editor = getcommiteditor(edit=True)
+                editor = getcommiteditor(edit=True, editform=editform)
                 message = old.description()
 
             pureextra = extra.copy()
@@ -2176,17 +2191,24 @@
         lockmod.release(lock, wlock)
     return newid
 
-def commiteditor(repo, ctx, subs):
+def commiteditor(repo, ctx, subs, editform=''):
     if ctx.description():
         return ctx.description()
-    return commitforceeditor(repo, ctx, subs)
+    return commitforceeditor(repo, ctx, subs, editform=editform)
 
-def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None):
+def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
+                      editform=''):
     if not extramsg:
         extramsg = _("Leave message empty to abort commit.")
-    tmpl = repo.ui.config('committemplate', 'changeset', '').strip()
-    if tmpl:
-        committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
+
+    forms = [e for e in editform.split('.') if e]
+    forms.insert(0, 'changeset')
+    while forms:
+        tmpl = repo.ui.config('committemplate', '.'.join(forms))
+        if tmpl:
+            committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
+            break
+        forms.pop()
     else:
         committext = buildcommittext(repo, ctx, subs, extramsg)
 
@@ -2213,6 +2235,10 @@
     except SyntaxError, inst:
         raise util.Abort(inst.args[0])
 
+    for k, v in repo.ui.configitems('committemplate'):
+        if k != 'changeset':
+            t.t.cache[k] = v
+
     if not extramsg:
         extramsg = '' # ensure that extramsg is string
 
@@ -2347,17 +2373,37 @@
         # get the list of subrepos that must be reverted
         targetsubs = sorted(s for s in ctx.substate if m(s))
 
-        # Find status of all file in `names`. (Against working directory parent)
+        # Find status of all file in `names`.
         m = scmutil.matchfiles(repo, names)
-        changes = repo.status(node1=parent, match=m)[:4]
-        modified, added, removed, deleted = map(set, changes)
+
+        changes = repo.status(node1=node, match=m, clean=True)
+        modified = set(changes[0])
+        added    = set(changes[1])
+        removed  = set(changes[2])
+        deleted  = set(changes[3])
+
+        # We need to account for the state of file in the dirstate
+        #
+        # Even, when we revert agains something else than parent. this will
+        # slightly alter the behavior of revert (doing back up or not, delete
+        # or just forget etc)
+        if parent == node:
+            dsmodified = modified
+            dsadded = added
+            dsremoved = removed
+            modified, added, removed = set(), set(), set()
+        else:
+            changes = repo.status(node1=parent, match=m)
+            dsmodified = set(changes[0])
+            dsadded    = set(changes[1])
+            dsremoved  = set(changes[2])
 
         # if f is a rename, update `names` to also revert the source
         cwd = repo.getcwd()
-        for f in added:
+        for f in dsadded:
             src = repo.dirstate.copied(f)
             if src and src not in names and repo.dirstate[src] == 'r':
-                removed.add(src)
+                dsremoved.add(src)
                 names[src] = (repo.pathto(src, cwd), True)
 
         ## computation of the action to performs on `names` content.
@@ -2367,6 +2413,18 @@
                 return _('forgetting %s\n')
             return _('removing %s\n')
 
+        # split between files known in target manifest and the others
+        smf = set(mf)
+
+        missingmodified = dsmodified - smf
+        dsmodified -= missingmodified
+        missingadded = dsadded - smf
+        dsadded -= missingadded
+        missingremoved = dsremoved - smf
+        dsremoved -= missingremoved
+        missingdeleted = deleted - smf
+        deleted -= missingdeleted
+
         # action to be actually performed by revert
         # (<list of file>, message>) tuple
         actions = {'revert': ([], _('reverting %s\n')),
@@ -2377,18 +2435,16 @@
         disptable = (
             # dispatch table:
             #   file state
-            #   action if in target manifest
-            #   action if not in target manifest
-            #   make backup if in target manifest
-            #   make backup if not in target manifest
-            (modified, (actions['revert'],   True),
-                       (actions['remove'],   True)),
-            (added,    (actions['revert'],   True),
-                       (actions['remove'],   False)),
-            (removed,  (actions['undelete'], True),
-                       (None,                False)),
-            (deleted,  (actions['revert'], False),
-                       (actions['remove'], False)),
+            #   action
+            #   make backup
+            (dsmodified,       (actions['revert'],   True)),
+            (missingmodified,  (actions['remove'],   True)),
+            (dsadded,          (actions['revert'],   True)),
+            (missingadded,     (actions['remove'],   False)),
+            (dsremoved,        (actions['undelete'], True)),
+            (missingremoved,   (None,                False)),
+            (deleted,          (actions['revert'],   False)),
+            (missingdeleted,   (actions['remove'],   False)),
             )
 
         for abs, (rel, exact) in sorted(names.items()):
@@ -2414,14 +2470,15 @@
             # search the entry in the dispatch table.
             # if the file is in any of this sets, it was touched in the working
             # directory parent and we are sure it needs to be reverted.
-            for table, hit, miss in disptable:
+            for table, (action, backup) in disptable:
                 if abs not in table:
                     continue
-                # file has changed in dirstate
-                if mfentry:
-                    handle(*hit)
-                elif miss[0] is not None:
-                    handle(*miss)
+                if action is None:
+                    if exact:
+                        ui.warn(_('no changes needed to %s\n') % rel)
+
+                else:
+                    handle(action, backup)
                 break
             else:
                 # Not touched in current dirstate.
--- a/mercurial/commands.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/commands.py	Thu Aug 14 16:25:47 2014 -0500
@@ -505,11 +505,12 @@
 
 
         def commitfunc(ui, repo, message, match, opts):
-            e = cmdutil.getcommiteditor(**opts)
+            editform = 'backout'
+            e = cmdutil.getcommiteditor(editform=editform, **opts)
             if not message:
                 # we don't translate commit messages
                 message = "Backed out changeset %s" % short(node)
-                e = cmdutil.getcommiteditor(edit=True)
+                e = cmdutil.getcommiteditor(edit=True, editform=editform)
             return repo.commit(message, opts.get('user'), opts.get('date'),
                                match, editor=e)
         newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
@@ -1385,9 +1386,6 @@
         # Let --subrepos on the command line override config setting.
         ui.setconfig('ui', 'commitsubrepos', True, 'commit')
 
-    # Save this for restoring it later
-    oldcommitphase = ui.config('phases', 'new-commit')
-
     cmdutil.checkunfinished(repo, commit=True)
 
     branch = repo[None].branch()
@@ -1441,21 +1439,24 @@
             newmarks.write()
     else:
         def commitfunc(ui, repo, message, match, opts):
+            backup = ui.backupconfig('phases', 'new-commit')
+            baseui = repo.baseui
+            basebackup = baseui.backupconfig('phases', 'new-commit')
             try:
                 if opts.get('secret'):
                     ui.setconfig('phases', 'new-commit', 'secret', 'commit')
                     # Propagate to subrepos
-                    repo.baseui.setconfig('phases', 'new-commit', 'secret',
-                                          'commit')
-
+                    baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
+
+                editform = 'commit.normal'
+                editor = cmdutil.getcommiteditor(editform=editform, **opts)
                 return repo.commit(message, opts.get('user'), opts.get('date'),
                                    match,
-                                   editor=cmdutil.getcommiteditor(**opts),
+                                   editor=editor,
                                    extra=extra)
             finally:
-                ui.setconfig('phases', 'new-commit', oldcommitphase, 'commit')
-                repo.baseui.setconfig('phases', 'new-commit', oldcommitphase,
-                                      'commit')
+                ui.restoreconfig(backup)
+                repo.baseui.restoreconfig(basebackup)
 
 
         node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
@@ -3054,6 +3055,7 @@
      ('c', 'continue', False, _('resume interrupted graft')),
      ('e', 'edit', False, _('invoke editor on commit messages')),
      ('', 'log', None, _('append graft info to log message')),
+     ('f', 'force', False, _('force graft')),
      ('D', 'currentdate', False,
       _('record the current date as commit date')),
      ('U', 'currentuser', False,
@@ -3077,6 +3079,10 @@
 
       (grafted from CHANGESETHASH)
 
+    If --force is specified, revisions will be grafted even if they
+    are already ancestors of or have been grafted to the destination.
+    This is useful when the revisions have since been backed out.
+
     If a graft merge results in conflicts, the graft process is
     interrupted so that the current merge can be manually resolved.
     Once all conflicts are addressed, the graft process can be
@@ -3084,7 +3090,8 @@
 
     .. note::
 
-      The -c/--continue option does not reapply earlier options.
+      The -c/--continue option does not reapply earlier options, except
+      for --force.
 
     .. container:: verbose
 
@@ -3121,7 +3128,7 @@
     if not opts.get('date') and opts.get('currentdate'):
         opts['date'] = "%d %d" % util.makedate()
 
-    editor = cmdutil.getcommiteditor(**opts)
+    editor = cmdutil.getcommiteditor(editform='graft', **opts)
 
     cont = False
     if opts['continue']:
@@ -3150,52 +3157,59 @@
     if not revs:
         return -1
 
-    # check for ancestors of dest branch
-    crev = repo['.'].rev()
-    ancestors = repo.changelog.ancestors([crev], inclusive=True)
-    # Cannot use x.remove(y) on smart set, this has to be a list.
-    # XXX make this lazy in the future
-    revs = list(revs)
-    # don't mutate while iterating, create a copy
-    for rev in list(revs):
-        if rev in ancestors:
-            ui.warn(_('skipping ancestor revision %s\n') % rev)
-            # XXX remove on list is slow
-            revs.remove(rev)
-    if not revs:
-        return -1
-
-    # analyze revs for earlier grafts
-    ids = {}
-    for ctx in repo.set("%ld", revs):
-        ids[ctx.hex()] = ctx.rev()
-        n = ctx.extra().get('source')
-        if n:
-            ids[n] = ctx.rev()
-
-    # check ancestors for earlier grafts
-    ui.debug('scanning for duplicate grafts\n')
-
-    for rev in repo.changelog.findmissingrevs(revs, [crev]):
-        ctx = repo[rev]
-        n = ctx.extra().get('source')
-        if n in ids:
-            r = repo[n].rev()
-            if r in revs:
-                ui.warn(_('skipping revision %s (already grafted to %s)\n')
-                        % (r, rev))
+    # Don't check in the --continue case, in effect retaining --force across
+    # --continues. That's because without --force, any revisions we decided to
+    # skip would have been filtered out here, so they wouldn't have made their
+    # way to the graftstate. With --force, any revisions we would have otherwise
+    # skipped would not have been filtered out, and if they hadn't been applied
+    # already, they'd have been in the graftstate.
+    if not (cont or opts.get('force')):
+        # check for ancestors of dest branch
+        crev = repo['.'].rev()
+        ancestors = repo.changelog.ancestors([crev], inclusive=True)
+        # Cannot use x.remove(y) on smart set, this has to be a list.
+        # XXX make this lazy in the future
+        revs = list(revs)
+        # don't mutate while iterating, create a copy
+        for rev in list(revs):
+            if rev in ancestors:
+                ui.warn(_('skipping ancestor revision %s\n') % rev)
+                # XXX remove on list is slow
+                revs.remove(rev)
+        if not revs:
+            return -1
+
+        # analyze revs for earlier grafts
+        ids = {}
+        for ctx in repo.set("%ld", revs):
+            ids[ctx.hex()] = ctx.rev()
+            n = ctx.extra().get('source')
+            if n:
+                ids[n] = ctx.rev()
+
+        # check ancestors for earlier grafts
+        ui.debug('scanning for duplicate grafts\n')
+
+        for rev in repo.changelog.findmissingrevs(revs, [crev]):
+            ctx = repo[rev]
+            n = ctx.extra().get('source')
+            if n in ids:
+                r = repo[n].rev()
+                if r in revs:
+                    ui.warn(_('skipping revision %s (already grafted to %s)\n')
+                            % (r, rev))
+                    revs.remove(r)
+                elif ids[n] in revs:
+                    ui.warn(_('skipping already grafted revision %s '
+                                '(%s also has origin %d)\n') % (ids[n], rev, r))
+                    revs.remove(ids[n])
+            elif ctx.hex() in ids:
+                r = ids[ctx.hex()]
+                ui.warn(_('skipping already grafted revision %s '
+                                '(was grafted from %d)\n') % (r, rev))
                 revs.remove(r)
-            elif ids[n] in revs:
-                ui.warn(_('skipping already grafted revision %s '
-                            '(%s also has origin %d)\n') % (ids[n], rev, r))
-                revs.remove(ids[n])
-        elif ctx.hex() in ids:
-            r = ids[ctx.hex()]
-            ui.warn(_('skipping already grafted revision %s '
-                            '(was grafted from %d)\n') % (r, rev))
-            revs.remove(r)
-    if not revs:
-        return -1
+        if not revs:
+            return -1
 
     wlock = repo.wlock()
     try:
@@ -4027,11 +4041,11 @@
     rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
 
     ret = 1
-    m = scmutil.match(repo[rev], pats, opts, default='relglob')
+    ctx = repo[rev]
+    m = scmutil.match(ctx, pats, opts, default='relglob')
     m.bad = lambda x, y: False
-    for abs in repo[rev].walk(m):
-        if not rev and abs not in repo.dirstate:
-            continue
+
+    for abs in ctx.matches(m):
         if opts.get('fullpath'):
             ui.write(repo.wjoin(abs), end)
         else:
@@ -4560,17 +4574,22 @@
             ctx = repo[r]
             ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
     else:
+        tr = None
         lock = repo.lock()
         try:
+            tr = repo.transaction("phase")
             # set phase
             if not revs:
                 raise util.Abort(_('empty revision set'))
             nodes = [repo[r].node() for r in revs]
             olddata = repo._phasecache.getphaserevs(repo)[:]
-            phases.advanceboundary(repo, targetphase, nodes)
+            phases.advanceboundary(repo, tr, targetphase, nodes)
             if opts['force']:
-                phases.retractboundary(repo, targetphase, nodes)
+                phases.retractboundary(repo, tr, targetphase, nodes)
+            tr.close()
         finally:
+            if tr is not None:
+                tr.release()
             lock.release()
         # moving revision from public to draft may hide them
         # We have to check result on an unfiltered repository
@@ -5785,7 +5804,11 @@
         if date:
             date = util.parsedate(date)
 
-        editor = cmdutil.getcommiteditor(**opts)
+        if opts.get('remove'):
+            editform = 'tag.remove'
+        else:
+            editform = 'tag.add'
+        editor = cmdutil.getcommiteditor(editform=editform, **opts)
 
         # don't allow tagging the null rev
         if (not opts.get('remove') and
--- a/mercurial/config.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/config.py	Thu Aug 14 16:25:47 2014 -0500
@@ -76,7 +76,7 @@
             # no data before, remove everything
             section, item = data
             if section in self._data:
-                del self._data[section][item]
+                self._data[section].pop(item, None)
             self._source.pop((section, item), None)
 
     def parse(self, src, data, sections=None, remap=None, include=None):
--- a/mercurial/context.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/context.py	Thu Aug 14 16:25:47 2014 -0500
@@ -276,7 +276,7 @@
     def dirs(self):
         return self._dirs
 
-    def dirty(self):
+    def dirty(self, missing=False, merge=True, branch=True):
         return False
 
     def status(self, other=None, match=None, listignored=False,
@@ -598,6 +598,9 @@
                 continue
             match.bad(fn, _('no such file in rev %s') % self)
 
+    def matches(self, match):
+        return self.walk(match)
+
 class basefilectx(object):
     """A filecontext object represents the common logic for its children:
     filectx: read-only access to a filerevision that is already present
@@ -711,6 +714,10 @@
             return util.binary(self.data())
         except IOError:
             return False
+    def isexec(self):
+        return 'x' in self.flags()
+    def islink(self):
+        return 'l' in self.flags()
 
     def cmp(self, fctx):
         """compare with other file context
@@ -1144,6 +1151,9 @@
         return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
                                                True, False))
 
+    def matches(self, match):
+        return sorted(self._repo.dirstate.matches(match))
+
     def ancestors(self):
         for a in self._repo.changelog.ancestors(
             [p.rev() for p in self._parents]):
@@ -1546,6 +1556,14 @@
         # invert comparison to reuse the same code path
         return fctx.cmp(self)
 
+    def remove(self, ignoremissing=False):
+        """wraps unlink for a repo's working directory"""
+        util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
+
+    def write(self, data, flags):
+        """wraps repo.wwrite"""
+        self._repo.wwrite(self._path, data, flags)
+
 class memctx(committablectx):
     """Use memctx to perform in-memory commits via localrepo.commitctx().
 
@@ -1586,6 +1604,20 @@
         self._filectxfn = filectxfn
         self.substate = {}
 
+        # if store is not callable, wrap it in a function
+        if not callable(filectxfn):
+            def getfilectx(repo, memctx, path):
+                fctx = filectxfn[path]
+                # this is weird but apparently we only keep track of one parent
+                # (why not only store that instead of a tuple?)
+                copied = fctx.renamed()
+                if copied:
+                    copied = copied[0]
+                return memfilectx(repo, path, fctx.data(),
+                                  islink=fctx.islink(), isexec=fctx.isexec(),
+                                  copied=copied, memctx=memctx)
+            self._filectxfn = getfilectx
+
         self._extra = extra and extra.copy() or {}
         if self._extra.get('branch', '') == '':
             self._extra['branch'] = 'default'
@@ -1613,7 +1645,7 @@
         for f, fnode in man.iteritems():
             p1node = nullid
             p2node = nullid
-            p = pctx[f].parents()
+            p = pctx[f].parents() # if file isn't in pctx, check p2?
             if len(p) > 0:
                 p1node = p[0].node()
                 if len(p) > 1:
@@ -1650,9 +1682,14 @@
         return len(self.data())
     def flags(self):
         return self._flags
-    def isexec(self):
-        return 'x' in self._flags
-    def islink(self):
-        return 'l' in self._flags
     def renamed(self):
         return self._copied
+
+    def remove(self, ignoremissing=False):
+        """wraps unlink for a repo's working directory"""
+        # need to figure out what to do here
+        del self._changectx[self._path]
+
+    def write(self, data, flags):
+        """wraps repo.wwrite"""
+        self._data = data
--- a/mercurial/dirstate.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/dirstate.py	Thu Aug 14 16:25:47 2014 -0500
@@ -873,3 +873,21 @@
 
         return (lookup, modified, added, removed, deleted, unknown, ignored,
                 clean)
+
+    def matches(self, match):
+        '''
+        return files in the dirstate (in whatever state) filtered by match
+        '''
+        dmap = self._map
+        if match.always():
+            return dmap.keys()
+        files = match.files()
+        if match.matchfn == match.exact:
+            # fast path -- filter the other way around, since typically files is
+            # much smaller than dmap
+            return [f for f in files if f in dmap]
+        if not match.anypats() and util.all(fn in dmap for fn in files):
+            # fast path -- all the values are known to be files, so just return
+            # that
+            return list(files)
+        return [f for f in dmap if match(f)]
--- a/mercurial/dispatch.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/dispatch.py	Thu Aug 14 16:25:47 2014 -0500
@@ -331,17 +331,40 @@
         args = shlex.split(cmd)
     return args + givenargs
 
+def aliasinterpolate(name, args, cmd):
+    '''interpolate args into cmd for shell aliases
+
+    This also handles $0, $@ and "$@".
+    '''
+    # util.interpolate can't deal with "$@" (with quotes) because it's only
+    # built to match prefix + patterns.
+    replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
+    replacemap['$0'] = name
+    replacemap['$$'] = '$'
+    replacemap['$@'] = ' '.join(args)
+    # Typical Unix shells interpolate "$@" (with quotes) as all the positional
+    # parameters, separated out into words. Emulate the same behavior here by
+    # quoting the arguments individually. POSIX shells will then typically
+    # tokenize each argument into exactly one word.
+    replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
+    # escape '\$' for regex
+    regex = '|'.join(replacemap.keys()).replace('$', r'\$')
+    r = re.compile(regex)
+    return r.sub(lambda x: replacemap[x.group()], cmd)
+
 class cmdalias(object):
     def __init__(self, name, definition, cmdtable):
         self.name = self.cmd = name
         self.cmdname = ''
         self.definition = definition
+        self.fn = None
         self.args = []
         self.opts = []
         self.help = ''
         self.norepo = True
         self.optionalrepo = False
-        self.badalias = False
+        self.badalias = None
+        self.unknowncmd = False
 
         try:
             aliases, entry = cmdutil.findcmd(self.name, cmdtable)
@@ -354,11 +377,7 @@
             self.shadows = False
 
         if not self.definition:
-            def fn(ui, *args):
-                ui.warn(_("no definition for alias '%s'\n") % self.name)
-                return -1
-            self.fn = fn
-            self.badalias = True
+            self.badalias = _("no definition for alias '%s'") % self.name
             return
 
         if self.definition.startswith('!'):
@@ -376,10 +395,7 @@
                                  % (int(m.groups()[0]), self.name))
                         return ''
                 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
-                replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
-                replace['0'] = self.name
-                replace['@'] = ' '.join(args)
-                cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True)
+                cmd = aliasinterpolate(self.name, args, cmd)
                 return util.system(cmd, environ=env, out=ui.fout)
             self.fn = fn
             return
@@ -387,26 +403,17 @@
         try:
             args = shlex.split(self.definition)
         except ValueError, inst:
-            def fn(ui, *args):
-                ui.warn(_("error in definition for alias '%s': %s\n")
-                        % (self.name, inst))
-                return -1
-            self.fn = fn
-            self.badalias = True
+            self.badalias = (_("error in definition for alias '%s': %s")
+                             % (self.name, inst))
             return
         self.cmdname = cmd = args.pop(0)
         args = map(util.expandpath, args)
 
         for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
             if _earlygetopt([invalidarg], args):
-                def fn(ui, *args):
-                    ui.warn(_("error in definition for alias '%s': %s may only "
-                              "be given on the command line\n")
-                            % (self.name, invalidarg))
-                    return -1
-
-                self.fn = fn
-                self.badalias = True
+                self.badalias = (_("error in definition for alias '%s': %s may "
+                                   "only be given on the command line")
+                                 % (self.name, invalidarg))
                 return
 
         try:
@@ -427,26 +434,24 @@
             self.__doc__ = self.fn.__doc__
 
         except error.UnknownCommand:
-            def fn(ui, *args):
-                ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
-                            % (self.name, cmd))
+            self.badalias = (_("alias '%s' resolves to unknown command '%s'")
+                             % (self.name, cmd))
+            self.unknowncmd = True
+        except error.AmbiguousCommand:
+            self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
+                             % (self.name, cmd))
+
+    def __call__(self, ui, *args, **opts):
+        if self.badalias:
+            hint = None
+            if self.unknowncmd:
                 try:
                     # check if the command is in a disabled extension
-                    commands.help_(ui, cmd, unknowncmd=True)
+                    cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
+                    hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
                 except error.UnknownCommand:
                     pass
-                return -1
-            self.fn = fn
-            self.badalias = True
-        except error.AmbiguousCommand:
-            def fn(ui, *args):
-                ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
-                            % (self.name, cmd))
-                return -1
-            self.fn = fn
-            self.badalias = True
-
-    def __call__(self, ui, *args, **opts):
+            raise util.Abort(self.badalias, hint=hint)
         if self.shadows:
             ui.debug("alias '%s' shadows command '%s'\n" %
                      (self.name, self.cmdname))
--- a/mercurial/exchange.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/exchange.py	Thu Aug 14 16:25:47 2014 -0500
@@ -77,8 +77,57 @@
         self.remoteheads = None
         # testable as a boolean indicating if any nodes are missing locally.
         self.incoming = None
-        # set of all heads common after changeset bundle push
-        self.commonheads = None
+        # phases changes that must be pushed along side the changesets
+        self.outdatedphases = None
+        # phases changes that must be pushed if changeset push fails
+        self.fallbackoutdatedphases = None
+        # outgoing obsmarkers
+        self.outobsmarkers = set()
+
+    @util.propertycache
+    def futureheads(self):
+        """future remote heads if the changeset push succeeds"""
+        return self.outgoing.missingheads
+
+    @util.propertycache
+    def fallbackheads(self):
+        """future remote heads if the changeset push fails"""
+        if self.revs is None:
+            # not target to push, all common are relevant
+            return self.outgoing.commonheads
+        unfi = self.repo.unfiltered()
+        # I want cheads = heads(::missingheads and ::commonheads)
+        # (missingheads is revs with secret changeset filtered out)
+        #
+        # This can be expressed as:
+        #     cheads = ( (missingheads and ::commonheads)
+        #              + (commonheads and ::missingheads))"
+        #              )
+        #
+        # while trying to push we already computed the following:
+        #     common = (::commonheads)
+        #     missing = ((commonheads::missingheads) - commonheads)
+        #
+        # We can pick:
+        # * missingheads part of common (::commonheads)
+        common = set(self.outgoing.common)
+        nm = self.repo.changelog.nodemap
+        cheads = [node for node in self.revs if nm[node] in common]
+        # and
+        # * commonheads parents on missing
+        revset = unfi.set('%ln and parents(roots(%ln))',
+                         self.outgoing.commonheads,
+                         self.outgoing.missing)
+        cheads.extend(c.node() for c in revset)
+        return cheads
+
+    @property
+    def commonheads(self):
+        """set of all common heads after changeset bundle push"""
+        if self.ret:
+            return self.futureheads
+        else:
+            return self.fallbackheads
 
 def push(repo, remote, force=False, revs=None, newbranch=False):
     '''Push outgoing changesets (limited by revs) from a local
@@ -136,7 +185,6 @@
                 and pushop.remote.capable('bundle2-exp')):
                 _pushbundle2(pushop)
             _pushchangeset(pushop)
-            _pushcomputecommonheads(pushop)
             _pushsyncphase(pushop)
             _pushobsolete(pushop)
         finally:
@@ -149,8 +197,39 @@
     _pushbookmark(pushop)
     return pushop.ret
 
+# list of steps to perform discovery before push
+pushdiscoveryorder = []
+
+# Mapping between step name and function
+#
+# This exists to help extensions wrap steps if necessary
+pushdiscoverymapping = {}
+
+def pushdiscovery(stepname):
+    """decorator for function performing discovery before push
+
+    The function is added to the step -> function mapping and appended to the
+    list of steps.  Beware that decorated function will be added in order (this
+    may matter).
+
+    You can only use this decorator for a new step, if you want to wrap a step
+    from an extension, change the pushdiscovery dictionary directly."""
+    def dec(func):
+        assert stepname not in pushdiscoverymapping
+        pushdiscoverymapping[stepname] = func
+        pushdiscoveryorder.append(stepname)
+        return func
+    return dec
+
 def _pushdiscovery(pushop):
-    # discovery
+    """Run all discovery steps"""
+    for stepname in pushdiscoveryorder:
+        step = pushdiscoverymapping[stepname]
+        step(pushop)
+
+@pushdiscovery('changeset')
+def _pushdiscoverychangeset(pushop):
+    """discover the changeset that need to be pushed"""
     unfi = pushop.repo.unfiltered()
     fci = discovery.findcommonincoming
     commoninc = fci(unfi, pushop.remote, force=pushop.force)
@@ -162,6 +241,45 @@
     pushop.remoteheads = remoteheads
     pushop.incoming = inc
 
+@pushdiscovery('phase')
+def _pushdiscoveryphase(pushop):
+    """discover the phase that needs to be pushed
+
+    (computed for both success and failure case for changesets push)"""
+    outgoing = pushop.outgoing
+    unfi = pushop.repo.unfiltered()
+    remotephases = pushop.remote.listkeys('phases')
+    publishing = remotephases.get('publishing', False)
+    ana = phases.analyzeremotephases(pushop.repo,
+                                     pushop.fallbackheads,
+                                     remotephases)
+    pheads, droots = ana
+    extracond = ''
+    if not publishing:
+        extracond = ' and public()'
+    revset = 'heads((%%ln::%%ln) %s)' % extracond
+    # Get the list of all revs draft on remote by public here.
+    # XXX Beware that revset break if droots is not strictly
+    # XXX root we may want to ensure it is but it is costly
+    fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
+    if not outgoing.missing:
+        future = fallback
+    else:
+        # adds changeset we are going to push as draft
+        #
+        # should not be necessary for pushblishing server, but because of an
+        # issue fixed in xxxxx we have to do it anyway.
+        fdroots = list(unfi.set('roots(%ln  + %ln::)',
+                       outgoing.missing, droots))
+        fdroots = [f.node() for f in fdroots]
+        future = list(unfi.set(revset, fdroots, pushop.futureheads))
+    pushop.outdatedphases = future
+    pushop.fallbackoutdatedphases = fallback
+
+@pushdiscovery('obsmarker')
+def _pushdiscoveryobsmarkers(pushop):
+    pushop.outobsmarkers = pushop.repo.obsstore
+
 def _pushcheckoutgoing(pushop):
     outgoing = pushop.outgoing
     unfi = pushop.repo.unfiltered()
@@ -201,6 +319,31 @@
                              newbm)
     return True
 
+# List of names of steps to perform for an outgoing bundle2, order matters.
+b2partsgenorder = []
+
+# Mapping between step name and function
+#
+# This exists to help extensions wrap steps if necessary
+b2partsgenmapping = {}
+
+def b2partsgenerator(stepname):
+    """decorator for function generating bundle2 part
+
+    The function is added to the step -> function mapping and appended to the
+    list of steps.  Beware that decorated functions will be added in order
+    (this may matter).
+
+    You can only use this decorator for new steps, if you want to wrap a step
+    from an extension, attack the b2partsgenmapping dictionary directly."""
+    def dec(func):
+        assert stepname not in b2partsgenmapping
+        b2partsgenmapping[stepname] = func
+        b2partsgenorder.append(stepname)
+        return func
+    return dec
+
+@b2partsgenerator('changeset')
 def _pushb2ctx(pushop, bundler):
     """handle changegroup push through bundle2
 
@@ -227,8 +370,37 @@
         pushop.ret = cgreplies['changegroup'][0]['return']
     return handlereply
 
-# list of function that may decide to add parts to an outgoing bundle2
-bundle2partsgenerators = [_pushb2ctx]
+@b2partsgenerator('phase')
+def _pushb2phases(pushop, bundler):
+    """handle phase push through bundle2"""
+    if 'phases' in pushop.stepsdone:
+        return
+    b2caps = bundle2.bundle2caps(pushop.remote)
+    if not 'b2x:pushkey' in b2caps:
+        return
+    pushop.stepsdone.add('phases')
+    part2node = []
+    enc = pushkey.encode
+    for newremotehead in pushop.outdatedphases:
+        part = bundler.newpart('b2x:pushkey')
+        part.addparam('namespace', enc('phases'))
+        part.addparam('key', enc(newremotehead.hex()))
+        part.addparam('old', enc(str(phases.draft)))
+        part.addparam('new', enc(str(phases.public)))
+        part2node.append((part.id, newremotehead))
+    def handlereply(op):
+        for partid, node in part2node:
+            partrep = op.records.getreplies(partid)
+            results = partrep['pushkey']
+            assert len(results) <= 1
+            msg = None
+            if not results:
+                msg = _('server ignored update of %s to public!\n') % node
+            elif not int(results[0]['return']):
+                msg = _('updating %s to public failed!\n') % node
+            if msg is not None:
+                pushop.ui.warn(msg)
+    return handlereply
 
 def _pushbundle2(pushop):
     """push data to the remote using bundle2
@@ -240,7 +412,8 @@
     capsblob = bundle2.encodecaps(pushop.repo.bundle2caps)
     bundler.newpart('b2x:replycaps', data=capsblob)
     replyhandlers = []
-    for partgen in bundle2partsgenerators:
+    for partgenname in b2partsgenorder:
+        partgen = b2partsgenmapping[partgenname]
         ret = partgen(pushop, bundler)
         if callable(ret):
             replyhandlers.append(ret)
@@ -307,43 +480,8 @@
         # change
         pushop.ret = pushop.remote.addchangegroup(cg, 'push', pushop.repo.url())
 
-def _pushcomputecommonheads(pushop):
-    unfi = pushop.repo.unfiltered()
-    if pushop.ret:
-        # push succeed, synchronize target of the push
-        cheads = pushop.outgoing.missingheads
-    elif pushop.revs is None:
-        # All out push fails. synchronize all common
-        cheads = pushop.outgoing.commonheads
-    else:
-        # I want cheads = heads(::missingheads and ::commonheads)
-        # (missingheads is revs with secret changeset filtered out)
-        #
-        # This can be expressed as:
-        #     cheads = ( (missingheads and ::commonheads)
-        #              + (commonheads and ::missingheads))"
-        #              )
-        #
-        # while trying to push we already computed the following:
-        #     common = (::commonheads)
-        #     missing = ((commonheads::missingheads) - commonheads)
-        #
-        # We can pick:
-        # * missingheads part of common (::commonheads)
-        common = set(pushop.outgoing.common)
-        nm = pushop.repo.changelog.nodemap
-        cheads = [node for node in pushop.revs if nm[node] in common]
-        # and
-        # * commonheads parents on missing
-        revset = unfi.set('%ln and parents(roots(%ln))',
-                         pushop.outgoing.commonheads,
-                         pushop.outgoing.missing)
-        cheads.extend(c.node() for c in revset)
-    pushop.commonheads = cheads
-
 def _pushsyncphase(pushop):
     """synchronise phase information locally and remotely"""
-    unfi = pushop.repo.unfiltered()
     cheads = pushop.commonheads
     # even when we don't push, exchanging phase data is useful
     remotephases = pushop.remote.listkeys('phases')
@@ -376,12 +514,18 @@
             _localphasemove(pushop, cheads, phases.draft)
         ### Apply local phase on remote
 
-        # Get the list of all revs draft on remote by public here.
-        # XXX Beware that revset break if droots is not strictly
-        # XXX root we may want to ensure it is but it is costly
-        outdated = unfi.set('heads((%ln::%ln) and public())',
-                            droots, cheads)
+        if pushop.ret:
+            if 'phases' in pushop.stepsdone:
+                # phases already pushed though bundle2
+                return
+            outdated = pushop.outdatedphases
+        else:
+            outdated = pushop.fallbackoutdatedphases
 
+        pushop.stepsdone.add('phases')
+
+        # filter heads already turned public by the push
+        outdated = [c for c in outdated if c.node() not in pheads]
         b2caps = bundle2.bundle2caps(pushop.remote)
         if 'b2x:pushkey' in b2caps:
             # server supports bundle2, let's do a batched push through it
@@ -431,7 +575,12 @@
 def _localphasemove(pushop, nodes, phase=phases.public):
     """move <nodes> to <phase> in the local source repo"""
     if pushop.locallocked:
-        phases.advanceboundary(pushop.repo, phase, nodes)
+        tr = pushop.repo.transaction('push-phase-sync')
+        try:
+            phases.advanceboundary(pushop.repo, tr, phase, nodes)
+            tr.close()
+        finally:
+            tr.release()
     else:
         # repo is not locked, do not change any phases!
         # Informs the user that phases should have been moved when
@@ -444,13 +593,16 @@
 
 def _pushobsolete(pushop):
     """utility function to push obsolete markers to a remote"""
+    if 'obsmarkers' in pushop.stepsdone:
+        return
     pushop.ui.debug('try to push obsolete markers to remote\n')
     repo = pushop.repo
     remote = pushop.remote
+    pushop.stepsdone.add('obsmarkers')
     if (obsolete._enabled and repo.obsstore and
         'obsolete' in remote.listkeys('namespaces')):
         rslts = []
-        remotedata = repo.listkeys('obsolete')
+        remotedata = obsolete._pushkeyescape(pushop.outobsmarkers)
         for key in sorted(remotedata, reverse=True):
             # reverse sort to ensure we end with dump0
             data = remotedata[key]
@@ -673,14 +825,29 @@
         pheads, _dr = phases.analyzeremotephases(pullop.repo,
                                                  pullop.pulledsubset,
                                                  remotephases)
-        phases.advanceboundary(pullop.repo, phases.public, pheads)
-        phases.advanceboundary(pullop.repo, phases.draft,
-                               pullop.pulledsubset)
+        dheads = pullop.pulledsubset
     else:
         # Remote is old or publishing all common changesets
         # should be seen as public
-        phases.advanceboundary(pullop.repo, phases.public,
-                               pullop.pulledsubset)
+        pheads = pullop.pulledsubset
+        dheads = []
+    unfi = pullop.repo.unfiltered()
+    phase = unfi._phasecache.phase
+    rev = unfi.changelog.nodemap.get
+    public = phases.public
+    draft = phases.draft
+
+    # exclude changesets already public locally and update the others
+    pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
+    if pheads:
+        tr = pullop.gettransaction()
+        phases.advanceboundary(pullop.repo, tr, public, pheads)
+
+    # exclude changesets already draft locally and update the others
+    dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
+    if dheads:
+        tr = pullop.gettransaction()
+        phases.advanceboundary(pullop.repo, tr, draft, dheads)
 
 def _pullobsolete(pullop):
     """utility function to pull obsolete markers from a remote
@@ -726,9 +893,13 @@
     The implementation is at a very early stage and will get massive rework
     when the API of bundle is refined.
     """
-    # build changegroup bundle here.
-    cg = changegroup.getbundle(repo, source, heads=heads,
-                               common=common, bundlecaps=bundlecaps)
+    cg = None
+    if kwargs.get('cg', True):
+        # build changegroup bundle here.
+        cg = changegroup.getbundle(repo, source, heads=heads,
+                                   common=common, bundlecaps=bundlecaps)
+    elif 'HG2X' not in bundlecaps:
+        raise ValueError(_('request for bundle10 must include changegroup'))
     if bundlecaps is None or 'HG2X' not in bundlecaps:
         if kwargs:
             raise ValueError(_('unsupported getbundle arguments: %s')
--- a/mercurial/filemerge.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/filemerge.py	Thu Aug 14 16:25:47 2014 -0500
@@ -178,24 +178,30 @@
 
     ui = repo.ui
 
+    validkeep = ['keep', 'keep-merge3']
+
     # do we attempt to simplemerge first?
     try:
         premerge = _toolbool(ui, tool, "premerge", not binary)
     except error.ConfigError:
         premerge = _toolstr(ui, tool, "premerge").lower()
-        valid = 'keep'.split()
-        if premerge not in valid:
-            _valid = ', '.join(["'" + v + "'" for v in valid])
+        if premerge not in validkeep:
+            _valid = ', '.join(["'" + v + "'" for v in validkeep])
             raise error.ConfigError(_("%s.premerge not valid "
                                       "('%s' is neither boolean nor %s)") %
                                     (tool, premerge, _valid))
 
     if premerge:
+        if premerge == 'keep-merge3':
+            if not labels:
+                labels = _defaultconflictlabels
+            if len(labels) < 3:
+                labels.append('base')
         r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
         if not r:
             ui.debug(" premerge successful\n")
             return 0
-        if premerge != 'keep':
+        if premerge not in validkeep:
             util.copyfile(back, a) # restore from backup and try again
     return 1 # continue merging
 
@@ -206,7 +212,8 @@
     """
     Uses the internal non-interactive simple merge algorithm for merging
     files. It will fail if there are any conflicts and leave markers in
-    the partially merged file."""
+    the partially merged file. Markers will have two sections, one for each side
+    of merge."""
     tool, toolpath, binary, symlink = toolconf
     if symlink:
         repo.ui.warn(_('warning: internal:merge cannot merge symlinks '
@@ -218,10 +225,25 @@
 
         ui = repo.ui
 
-        r = simplemerge.simplemerge(ui, a, b, c, label=labels, no_minimal=True)
+        r = simplemerge.simplemerge(ui, a, b, c, label=labels)
         return True, r
     return False, 0
 
+@internaltool('merge3', True,
+              _("merging %s incomplete! "
+                "(edit conflicts, then use 'hg resolve --mark')\n"))
+def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
+    """
+    Uses the internal non-interactive simple merge algorithm for merging
+    files. It will fail if there are any conflicts and leave markers in
+    the partially merged file. Marker will have three sections, one from each
+    side of the merge and one for the base content."""
+    if not labels:
+        labels = _defaultconflictlabels
+    if len(labels) < 3:
+        labels.append('base')
+    return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
+
 @internaltool('tagmerge', True,
               _("automatic tag merging of %s failed! "
                 "(use 'hg resolve --tool internal:merge' or another merge "
@@ -312,23 +334,27 @@
 
 _defaultconflictlabels = ['local', 'other']
 
-def _formatlabels(repo, fcd, fco, labels):
+def _formatlabels(repo, fcd, fco, fca, labels):
     """Formats the given labels using the conflict marker template.
 
     Returns a list of formatted labels.
     """
     cd = fcd.changectx()
     co = fco.changectx()
+    ca = fca.changectx()
 
     ui = repo.ui
     template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
     template = templater.parsestring(template, quoted=False)
-    tmpl = templater.templater(None, cache={ 'conflictmarker' : template })
+    tmpl = templater.templater(None, cache={'conflictmarker': template})
+
+    pad = max(len(l) for l in labels)
 
-    pad = max(len(labels[0]), len(labels[1]))
-
-    return [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
-            _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
+    newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
+                 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
+    if len(labels) > 2:
+        newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
+    return newlabels
 
 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
     """perform a 3-way merge in the working directory
@@ -388,16 +414,13 @@
     ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
 
     markerstyle = ui.config('ui', 'mergemarkers', 'basic')
-    if markerstyle == 'basic':
-        formattedlabels = _defaultconflictlabels
-    else:
-        if not labels:
-            labels = _defaultconflictlabels
-
-        formattedlabels = _formatlabels(repo, fcd, fco, labels)
+    if not labels:
+        labels = _defaultconflictlabels
+    if markerstyle != 'basic':
+        labels = _formatlabels(repo, fcd, fco, fca, labels)
 
     needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf,
-                        (a, b, c, back), labels=formattedlabels)
+                        (a, b, c, back), labels=labels)
     if not needcheck:
         if r:
             if onfailure:
--- a/mercurial/help.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/help.py	Thu Aug 14 16:25:47 2014 -0500
@@ -31,7 +31,7 @@
     doc = ''.join(rst)
     return doc
 
-def optrst(options, verbose):
+def optrst(header, options, verbose):
     data = []
     multioccur = False
     for option in options:
@@ -59,10 +59,11 @@
 
         data.append((so, lo, desc))
 
-    rst = minirst.maketable(data, 1)
+    if multioccur:
+        header += (_(" ([+] can be repeated)"))
 
-    if multioccur:
-        rst.append(_("\n[+] marked option can be specified multiple times\n"))
+    rst = ['\n%s:\n\n' % header]
+    rst.extend(minirst.maketable(data, 1))
 
     return ''.join(rst)
 
@@ -234,11 +235,13 @@
         rst = []
 
         # check if it's an invalid alias and display its error if it is
-        if getattr(entry[0], 'badalias', False):
-            if not unknowncmd:
-                ui.pushbuffer()
-                entry[0](ui)
-                rst.append(ui.popbuffer())
+        if getattr(entry[0], 'badalias', None):
+            rst.append(entry[0].badalias + '\n')
+            if entry[0].unknowncmd:
+                try:
+                    rst.extend(helpextcmd(entry[0].cmdname))
+                except error.UnknownCommand:
+                    pass
             return rst
 
         # synopsis
@@ -276,31 +279,27 @@
             mod = extensions.find(name)
             doc = gettext(mod.__doc__) or ''
             if '\n' in doc.strip():
-                msg = _('use "hg help -e %s" to show help for '
-                        'the %s extension') % (name, name)
+                msg = _('(use "hg help -e %s" to show help for '
+                        'the %s extension)') % (name, name)
                 rst.append('\n%s\n' % msg)
         except KeyError:
             pass
 
         # options
         if not ui.quiet and entry[1]:
-            rst.append('\n%s\n\n' % _("options:"))
-            rst.append(optrst(entry[1], ui.verbose))
+            rst.append(optrst(_("options"), entry[1], ui.verbose))
 
         if ui.verbose:
-            rst.append('\n%s\n\n' % _("global options:"))
-            rst.append(optrst(commands.globalopts, ui.verbose))
+            rst.append(optrst(_("global options"),
+                              commands.globalopts, ui.verbose))
 
         if not ui.verbose:
             if not full:
-                rst.append(_('\nuse "hg help %s" to show the full help text\n')
+                rst.append(_('\n(use "hg %s -h" to show more help)\n')
                            % name)
             elif not ui.quiet:
-                omitted = _('use "hg -v help %s" to show more complete'
-                            ' help and the global options') % name
-                notomitted = _('use "hg -v help %s" to show'
-                               ' the global options') % name
-                indicateomitted(rst, omitted, notomitted)
+                rst.append(_('\n(some details hidden, use --verbose '
+                               'to show complete help)'))
 
         return rst
 
@@ -366,30 +365,25 @@
             for t, desc in topics:
                 rst.append(" :%s: %s\n" % (t, desc))
 
-        optlist = []
-        if not ui.quiet:
-            if ui.verbose:
-                optlist.append((_("global options:"), commands.globalopts))
-                if name == 'shortlist':
-                    optlist.append((_('use "hg help" for the full list '
-                                           'of commands'), ()))
+        if ui.quiet:
+            pass
+        elif ui.verbose:
+            rst.append('\n%s\n' % optrst(_("global options"),
+                                         commands.globalopts, ui.verbose))
+            if name == 'shortlist':
+                rst.append(_('\n(use "hg help" for the full list '
+                             'of commands)\n'))
+        else:
+            if name == 'shortlist':
+                rst.append(_('\n(use "hg help" for the full list of commands '
+                             'or "hg -v" for details)\n'))
+            elif name and not full:
+                rst.append(_('\n(use "hg help %s" to show the full help '
+                             'text)\n') % name)
             else:
-                if name == 'shortlist':
-                    msg = _('use "hg help" for the full list of commands '
-                            'or "hg -v" for details')
-                elif name and not full:
-                    msg = _('use "hg help %s" to show the full help '
-                            'text') % name
-                else:
-                    msg = _('use "hg -v help%s" to show builtin aliases and '
-                            'global options') % (name and " " + name or "")
-                optlist.append((msg, ()))
-
-        if optlist:
-            for title, options in optlist:
-                rst.append('\n%s\n' % title)
-                if options:
-                    rst.append('\n%s\n' % optrst(options, ui.verbose))
+                rst.append(_('\n(use "hg help -v%s" to show built-in aliases '
+                             'and global options)\n')
+                           % (name and " " + name or ""))
         return rst
 
     def helptopic(name):
@@ -408,8 +402,8 @@
             rst += ["    %s\n" % l for l in doc().splitlines()]
 
         if not ui.verbose:
-            omitted = (_('use "hg help -v %s" to show more complete help') %
-                       name)
+            omitted = _('(some details hidden, use --verbose'
+                         ' to show complete help)')
             indicateomitted(rst, omitted)
 
         try:
@@ -440,8 +434,8 @@
             rst.append('\n')
 
         if not ui.verbose:
-            omitted = (_('use "hg help -v %s" to show more complete help') %
-                       name)
+            omitted = _('(some details hidden, use --verbose'
+                         ' to show complete help)')
             indicateomitted(rst, omitted)
 
         if mod:
@@ -452,8 +446,8 @@
             modcmds = set([c.split('|', 1)[0] for c in ct])
             rst.extend(helplist(modcmds.__contains__))
         else:
-            rst.append(_('use "hg help extensions" for information on enabling '
-                       'extensions\n'))
+            rst.append(_('(use "hg help extensions" for information on enabling'
+                       ' extensions)\n'))
         return rst
 
     def helpextcmd(name):
@@ -464,8 +458,8 @@
         rst = listexts(_("'%s' is provided by the following "
                               "extension:") % cmd, {ext: doc}, indent=4)
         rst.append('\n')
-        rst.append(_('use "hg help extensions" for information on enabling '
-                   'extensions\n'))
+        rst.append(_('(use "hg help extensions" for information on enabling '
+                   'extensions)\n'))
         return rst
 
 
--- a/mercurial/help/config.txt	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/help/config.txt	Thu Aug 14 16:25:47 2014 -0500
@@ -229,8 +229,9 @@
 Positional arguments like ``$1``, ``$2``, etc. in the alias definition
 expand to the command arguments. Unmatched arguments are
 removed. ``$0`` expands to the alias name and ``$@`` expands to all
-arguments separated by a space. These expansions happen before the
-command is passed to the shell.
+arguments separated by a space. ``"$@"`` (with quotes) expands to all
+arguments quoted individually and separated by a space. These expansions
+happen before the command is passed to the shell.
 
 Shell aliases are executed in an environment where ``$HG`` expands to
 the path of the Mercurial that was used to execute the alias. This is
@@ -388,6 +389,48 @@
 - :hg:`tag`
 - :hg:`transplant`
 
+Configuring items below instead of ``changeset`` allows showing
+customized message only for specific actions, or showing different
+messages for each actions.
+
+- ``changeset.backout`` for :hg:`backout`
+- ``changeset.commit.amend`` for :hg:`commit --amend`
+- ``changeset.commit.normal`` for :hg:`commit` without ``--amend``
+- ``changeset.fetch`` for :hg:`fetch` (impling merge commit)
+- ``changeset.gpg.sign`` for :hg:`sign`
+- ``changeset.graft`` for :hg:`graft`
+- ``changeset.histedit.edit`` for ``edit`` of :hg:`histedit`
+- ``changeset.histedit.fold`` for ``fold`` of :hg:`histedit`
+- ``changeset.histedit.mess`` for ``mess`` of :hg:`histedit`
+- ``changeset.histedit.pick`` for ``pick`` of :hg:`histedit`
+- ``changeset.import.bypass`` for :hg:`import --bypass`
+- ``changeset.import.normal`` for :hg:`import` without ``--bypass``
+- ``changeset.mq.qnew`` for :hg:`qnew`
+- ``changeset.mq.qfold`` for :hg:`qfold`
+- ``changeset.mq.qrefresh`` for :hg:`qrefresh`
+- ``changeset.rebase.collapse`` for :hg:`rebase --collapse`
+- ``changeset.rebase.normal`` for :hg:`rebase` without ``--collapse``
+- ``changeset.shelve.shelve`` for :hg:`shelve`
+- ``changeset.tag.add`` for :hg:`tag` without ``--remove``
+- ``changeset.tag.remove`` for :hg:`tag --remove`
+- ``changeset.transplant`` for :hg:`transplant`
+
+These dot-separated lists of names are treated as hierarchical ones.
+For example, ``changeset.tag.remove`` customizes the commit message
+only for :hg:`tag --remove`, but ``changeset.tag`` customizes the
+commit message for :hg:`tag` regardless of ``--remove`` option.
+
+In this section, items other than ``changeset`` can be referred from
+others. For example, the configuration to list committed files up
+below can be referred as ``{listupfiles}``::
+
+    [committemplate]
+    listupfiles = {file_adds %
+       "HG: added {file}\n"     }{file_mods %
+       "HG: changed {file}\n"   }{file_dels %
+       "HG: removed {file}\n"   }{if(files, "",
+       "HG: no files changed\n")}
+
 ``decode/encode``
 -----------------
 
@@ -912,8 +955,10 @@
 
 ``premerge``
   Attempt to run internal non-interactive 3-way merge tool before
-  launching external tool.  Options are ``true``, ``false``, or ``keep``
-  to leave markers in the file if the premerge fails.
+  launching external tool.  Options are ``true``, ``false``, ``keep`` or
+  ``keep-merge3``. The ``keep`` option will leave markers in the file if the
+  premerge fails. The ``keep-merge3`` will do the same but include information
+  about the base of the merge in the marker (see internal:merge3).
   Default: True
 
 ``binary``
--- a/mercurial/i18n.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/i18n.py	Thu Aug 14 16:25:47 2014 -0500
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 import encoding
-import gettext, sys, os
+import gettext, sys, os, locale
 
 # modelled after templater.templatepath:
 if getattr(sys, 'frozen', None) is not None:
@@ -20,7 +20,25 @@
     if os.path.isdir(localedir):
         break
 
-t = gettext.translation('hg', localedir, fallback=True)
+_languages = None
+if (os.name == 'nt'
+    and 'LANGUAGE' not in os.environ
+    and 'LC_ALL' not in os.environ
+    and 'LC_MESSAGES' not in os.environ
+    and 'LANG' not in os.environ):
+    # Try to detect UI language by "User Interface Language Management" API
+    # if no locale variables are set. Note that locale.getdefaultlocale()
+    # uses GetLocaleInfo(), which may be different from UI language.
+    # (See http://msdn.microsoft.com/en-us/library/dd374098(v=VS.85).aspx )
+    try:
+        import ctypes
+        langid = ctypes.windll.kernel32.GetUserDefaultUILanguage()
+        _languages = [locale.windows_locale[langid]]
+    except (ImportError, AttributeError, KeyError):
+        # ctypes not found or unknown langid
+        pass
+
+t = gettext.translation('hg', localedir, _languages, fallback=True)
 
 def gettext(message):
     """Translate message.
--- a/mercurial/localrepo.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/localrepo.py	Thu Aug 14 16:25:47 2014 -0500
@@ -182,7 +182,9 @@
 
     bundle2caps = {'HG2X': (),
                    'b2x:listkeys': (),
-                   'b2x:pushkey': ()}
+                   'b2x:pushkey': (),
+                   'b2x:changegroup': (),
+                  }
 
     # a list of (ui, featureset) functions.
     # only functions defined in module of enabled extensions are invoked
@@ -1087,8 +1089,6 @@
             return l
 
         def unlock():
-            if hasunfilteredcache(self, '_phasecache'):
-                self._phasecache.write()
             for k, ce in self._filecache.items():
                 if k == 'dirstate' or k not in self.__dict__:
                     continue
@@ -1440,7 +1440,7 @@
                 # be compliant anyway
                 #
                 # if minimal phase was 0 we don't need to retract anything
-                phases.retractboundary(self, targetphase, [n])
+                phases.retractboundary(self, tr, targetphase, [n])
             tr.close()
             branchmap.updatecache(self.filtered('served'))
             return n
--- a/mercurial/phases.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/phases.py	Thu Aug 14 16:25:47 2014 -0500
@@ -196,19 +196,24 @@
             return
         f = self.opener('phaseroots', 'w', atomictemp=True)
         try:
-            for phase, roots in enumerate(self.phaseroots):
-                for h in roots:
-                    f.write('%i %s\n' % (phase, hex(h)))
+            self._write(f)
         finally:
             f.close()
+
+    def _write(self, fp):
+        for phase, roots in enumerate(self.phaseroots):
+            for h in roots:
+                fp.write('%i %s\n' % (phase, hex(h)))
         self.dirty = False
 
-    def _updateroots(self, phase, newroots):
+    def _updateroots(self, phase, newroots, tr):
         self.phaseroots[phase] = newroots
         self._phaserevs = None
         self.dirty = True
 
-    def advanceboundary(self, repo, targetphase, nodes):
+        tr.addfilegenerator('phase', ('phaseroots',), self._write)
+
+    def advanceboundary(self, repo, tr, targetphase, nodes):
         # Be careful to preserve shallow-copied values: do not update
         # phaseroots values, replace them.
 
@@ -224,15 +229,15 @@
             roots = set(ctx.node() for ctx in repo.set(
                     'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
             if olds != roots:
-                self._updateroots(phase, roots)
+                self._updateroots(phase, roots, tr)
                 # some roots may need to be declared for lower phases
                 delroots.extend(olds - roots)
             # declare deleted root in the target phase
             if targetphase != 0:
-                self.retractboundary(repo, targetphase, delroots)
+                self.retractboundary(repo, tr, targetphase, delroots)
         repo.invalidatevolatilesets()
 
-    def retractboundary(self, repo, targetphase, nodes):
+    def retractboundary(self, repo, tr, targetphase, nodes):
         # Be careful to preserve shallow-copied values: do not update
         # phaseroots values, replace them.
 
@@ -247,7 +252,7 @@
             currentroots.update(newroots)
             ctxs = repo.set('roots(%ln::)', currentroots)
             currentroots.intersection_update(ctx.node() for ctx in ctxs)
-            self._updateroots(targetphase, currentroots)
+            self._updateroots(targetphase, currentroots, tr)
         repo.invalidatevolatilesets()
 
     def filterunknown(self, repo):
@@ -278,7 +283,7 @@
         # (see branchmap one)
         self._phaserevs = None
 
-def advanceboundary(repo, targetphase, nodes):
+def advanceboundary(repo, tr, targetphase, nodes):
     """Add nodes to a phase changing other nodes phases if necessary.
 
     This function move boundary *forward* this means that all nodes
@@ -286,10 +291,10 @@
 
     Simplify boundary to contains phase roots only."""
     phcache = repo._phasecache.copy()
-    phcache.advanceboundary(repo, targetphase, nodes)
+    phcache.advanceboundary(repo, tr, targetphase, nodes)
     repo._phasecache.replace(phcache)
 
-def retractboundary(repo, targetphase, nodes):
+def retractboundary(repo, tr, targetphase, nodes):
     """Set nodes back to a phase changing other nodes phases if
     necessary.
 
@@ -298,7 +303,7 @@
 
     Simplify boundary to contains phase roots only."""
     phcache = repo._phasecache.copy()
-    phcache.retractboundary(repo, targetphase, nodes)
+    phcache.retractboundary(repo, tr, targetphase, nodes)
     repo._phasecache.replace(phcache)
 
 def listphases(repo):
@@ -331,13 +336,16 @@
 def pushphase(repo, nhex, oldphasestr, newphasestr):
     """List phases root for serialization over pushkey"""
     repo = repo.unfiltered()
+    tr = None
     lock = repo.lock()
     try:
         currentphase = repo[nhex].phase()
         newphase = abs(int(newphasestr)) # let's avoid negative index surprise
         oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
         if currentphase == oldphase and newphase < oldphase:
-            advanceboundary(repo, newphase, [bin(nhex)])
+            tr = repo.transaction('pushkey-phase')
+            advanceboundary(repo, tr, newphase, [bin(nhex)])
+            tr.close()
             return 1
         elif currentphase == newphase:
             # raced, but got correct result
@@ -345,6 +353,8 @@
         else:
             return 0
     finally:
+        if tr:
+            tr.release()
         lock.release()
 
 def analyzeremotephases(repo, subset, roots):
--- a/mercurial/repair.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/repair.py	Thu Aug 14 16:25:47 2014 -0500
@@ -47,7 +47,13 @@
 
     return s
 
-def strip(ui, repo, nodelist, backup="all", topic='backup'):
+def strip(ui, repo, nodelist, backup=True, topic='backup'):
+
+    # Simple way to maintain backwards compatibility for this
+    # argument.
+    if backup in ['none', 'strip']:
+        backup = False
+
     repo = repo.unfiltered()
     repo.destroying()
 
@@ -58,8 +64,6 @@
     striplist = [cl.rev(node) for node in nodelist]
     striprev = min(striplist)
 
-    keeppartialbundle = backup == 'strip'
-
     # Some revisions with rev > striprev may not be descendants of striprev.
     # We have to find these revisions and put them in a bundle, so that
     # we can restore them after the truncations.
@@ -109,7 +113,7 @@
     # create a changegroup for all the branches we need to keep
     backupfile = None
     vfs = repo.vfs
-    if backup == "all":
+    if backup:
         backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
         repo.ui.status(_("saved backup bundle to %s\n") %
                        vfs.join(backupfile))
@@ -118,7 +122,7 @@
     if saveheads or savebases:
         # do not compress partial bundle if we remove it from disk later
         chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
-                            compress=keeppartialbundle)
+                            compress=False)
 
     mfst = repo.manifest
 
@@ -156,8 +160,6 @@
             if not repo.ui.verbose:
                 repo.ui.popbuffer()
             f.close()
-            if not keeppartialbundle:
-                vfs.unlink(chgrpfile)
 
         # remove undo files
         for undovfs, undofile in repo.undofiles():
@@ -179,5 +181,9 @@
             ui.warn(_("strip failed, partial bundle stored in '%s'\n")
                     % vfs.join(chgrpfile))
         raise
+    else:
+        if saveheads or savebases:
+            # Remove partial backup only if there were no exceptions
+            vfs.unlink(chgrpfile)
 
     repo.destroyed()
--- a/mercurial/repoview.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/repoview.py	Thu Aug 14 16:25:47 2014 -0500
@@ -7,11 +7,14 @@
 # GNU General Public License version 2 or any later version.
 
 import copy
+import error
+import hashlib
 import phases
 import util
 import obsolete
+import struct
 import tags as tagsmod
-
+from mercurial.i18n import _
 
 def hideablerevs(repo):
     """Revisions candidates to be hidden
@@ -19,13 +22,14 @@
     This is a standalone function to help extensions to wrap it."""
     return obsolete.getrevs(repo, 'obsolete')
 
-def _gethiddenblockers(repo):
-    """Get revisions that will block hidden changesets from being filtered
+def _getstaticblockers(repo):
+    """Cacheable revisions blocking hidden changesets from being filtered.
 
+    Additional non-cached hidden blockers are computed in _getdynamicblockers.
     This is a standalone function to help extensions to wrap it."""
     assert not repo.changelog.filteredrevs
     hideable = hideablerevs(repo)
-    blockers = []
+    blockers = set()
     if hideable:
         # We use cl to avoid recursive lookup from repo[xxx]
         cl = repo.changelog
@@ -33,29 +37,123 @@
         revs = cl.revs(start=firsthideable)
         tofilter = repo.revs(
             '(%ld) and children(%ld)', list(revs), list(hideable))
-        blockers = [r for r in tofilter if r not in hideable]
-        for par in repo[None].parents():
-            blockers.append(par.rev())
-        for bm in repo._bookmarks.values():
-            blockers.append(cl.rev(bm))
-        tags = {}
-        tagsmod.readlocaltags(repo.ui, repo, tags, {})
-        if tags:
-            rev, nodemap = cl.rev, cl.nodemap
-            blockers.extend(rev(t[0]) for t in tags.values() if t[0] in nodemap)
+        blockers.update([r for r in tofilter if r not in hideable])
+    return blockers
+
+def _getdynamicblockers(repo):
+    """Non-cacheable revisions blocking hidden changesets from being filtered.
+
+    Get revisions that will block hidden changesets and are likely to change,
+    but unlikely to create hidden blockers. They won't be cached, so be careful
+    with adding additional computation."""
+
+    cl = repo.changelog
+    blockers = set()
+    blockers.update([par.rev() for par in repo[None].parents()])
+    blockers.update([cl.rev(bm) for bm in repo._bookmarks.values()])
+
+    tags = {}
+    tagsmod.readlocaltags(repo.ui, repo, tags, {})
+    if tags:
+        rev, nodemap = cl.rev, cl.nodemap
+        blockers.update(rev(t[0]) for t in tags.values() if t[0] in nodemap)
     return blockers
 
+cacheversion = 1
+cachefile = 'cache/hidden'
+
+def cachehash(repo, hideable):
+    """return sha1 hash of repository data to identify a valid cache.
+
+    We calculate a sha1 of repo heads and the content of the obsstore and write
+    it to the cache. Upon reading we can easily validate by checking the hash
+    against the stored one and discard the cache in case the hashes don't match.
+    """
+    h = hashlib.sha1()
+    h.update(''.join(repo.heads()))
+    h.update(str(hash(frozenset(hideable))))
+    return h.digest()
+
+def trywritehiddencache(repo, hideable, hidden):
+    """write cache of hidden changesets to disk
+
+    Will not write the cache if a wlock cannot be obtained lazily.
+    The cache consists of a head of 22byte:
+       2 byte    version number of the cache
+      20 byte    sha1 to validate the cache
+     n*4 byte    hidden revs
+    """
+    wlock = fh = None
+    try:
+        wlock = repo.wlock(wait=False)
+        # write cache to file
+        newhash = cachehash(repo, hideable)
+        sortedset = sorted(hidden)
+        data = struct.pack('>%iI' % len(sortedset), *sortedset)
+        fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True)
+        fh.write(struct.pack(">H", cacheversion))
+        fh.write(newhash)
+        fh.write(data)
+    except (IOError, OSError):
+        repo.ui.debug('error writing hidden changesets cache')
+    except error.LockHeld:
+        repo.ui.debug('cannot obtain lock to write hidden changesets cache')
+    finally:
+        if fh:
+            fh.close()
+        if wlock:
+            wlock.release()
+
+def tryreadcache(repo, hideable):
+    """read a cache if the cache exists and is valid, otherwise returns None."""
+    hidden = fh = None
+    try:
+        if repo.vfs.exists(cachefile):
+            fh = repo.vfs.open(cachefile, 'rb')
+            version, = struct.unpack(">H", fh.read(2))
+            oldhash = fh.read(20)
+            newhash = cachehash(repo, hideable)
+            if (cacheversion, oldhash) == (version, newhash):
+                # cache is valid, so we can start reading the hidden revs
+                data = fh.read()
+                count = len(data) / 4
+                hidden = frozenset(struct.unpack('>%iI' % count, data))
+        return hidden
+    finally:
+        if fh:
+            fh.close()
+
 def computehidden(repo):
     """compute the set of hidden revision to filter
 
     During most operation hidden should be filtered."""
     assert not repo.changelog.filteredrevs
+
+    hidden = frozenset()
     hideable = hideablerevs(repo)
     if hideable:
         cl = repo.changelog
-        blocked = cl.ancestors(_gethiddenblockers(repo), inclusive=True)
-        return frozenset(r for r in hideable if r not in blocked)
-    return frozenset()
+        hidden = tryreadcache(repo, hideable)
+        if hidden is None:
+            blocked = cl.ancestors(_getstaticblockers(repo), inclusive=True)
+            hidden = frozenset(r for r in hideable if r not in blocked)
+            trywritehiddencache(repo, hideable, hidden)
+        elif repo.ui.configbool('experimental', 'verifyhiddencache', True):
+            blocked = cl.ancestors(_getstaticblockers(repo), inclusive=True)
+            computed = frozenset(r for r in hideable if r not in blocked)
+            if computed != hidden:
+                trywritehiddencache(repo, hideable, computed)
+                repo.ui.warn(_('Cache inconsistency detected. Please ' +
+                    'open an issue on http://bz.selenic.com.\n'))
+                hidden = computed
+
+        # check if we have wd parents, bookmarks or tags pointing to hidden
+        # changesets and remove those.
+        dynamic = hidden & _getdynamicblockers(repo)
+        if dynamic:
+            blocked = cl.ancestors(dynamic, inclusive=True)
+            hidden = frozenset(r for r in hidden if r not in blocked)
+    return hidden
 
 def computeunserved(repo):
     """compute the set of revision that should be filtered when used a server
--- a/mercurial/simplemerge.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/simplemerge.py	Thu Aug 14 16:25:47 2014 -0500
@@ -82,8 +82,7 @@
                     start_marker='<<<<<<<',
                     mid_marker='=======',
                     end_marker='>>>>>>>',
-                    base_marker=None,
-                    reprocess=False):
+                    base_marker=None):
         """Return merge in cvs-like form.
         """
         self.conflicts = False
@@ -93,8 +92,6 @@
                 newline = '\r\n'
             elif self.a[0].endswith('\r'):
                 newline = '\r'
-        if base_marker and reprocess:
-            raise CantReprocessAndShowBase
         if name_a:
             start_marker = start_marker + ' ' + name_a
         if name_b:
@@ -102,8 +99,6 @@
         if name_base and base_marker:
             base_marker = base_marker + ' ' + name_base
         merge_regions = self.merge_regions()
-        if reprocess is True:
-            merge_regions = self.reprocess_merge_regions(merge_regions)
         for t in merge_regions:
             what = t[0]
             if what == 'unchanged':
@@ -131,33 +126,6 @@
             else:
                 raise ValueError(what)
 
-    def merge_annotated(self):
-        """Return merge with conflicts, showing origin of lines.
-
-        Most useful for debugging merge.
-        """
-        for t in self.merge_regions():
-            what = t[0]
-            if what == 'unchanged':
-                for i in range(t[1], t[2]):
-                    yield 'u | ' + self.base[i]
-            elif what == 'a' or what == 'same':
-                for i in range(t[1], t[2]):
-                    yield what[0] + ' | ' + self.a[i]
-            elif what == 'b':
-                for i in range(t[1], t[2]):
-                    yield 'b | ' + self.b[i]
-            elif what == 'conflict':
-                yield '<<<<\n'
-                for i in range(t[3], t[4]):
-                    yield 'A | ' + self.a[i]
-                yield '----\n'
-                for i in range(t[5], t[6]):
-                    yield 'B | ' + self.b[i]
-                yield '>>>>\n'
-            else:
-                raise ValueError(what)
-
     def merge_groups(self):
         """Yield sequence of line groups.  Each one is a tuple:
 
@@ -278,42 +246,6 @@
                 ia = aend
                 ib = bend
 
-    def reprocess_merge_regions(self, merge_regions):
-        """Where there are conflict regions, remove the agreed lines.
-
-        Lines where both A and B have made the same changes are
-        eliminated.
-        """
-        for region in merge_regions:
-            if region[0] != "conflict":
-                yield region
-                continue
-            type, iz, zmatch, ia, amatch, ib, bmatch = region
-            a_region = self.a[ia:amatch]
-            b_region = self.b[ib:bmatch]
-            matches = mdiff.get_matching_blocks(''.join(a_region),
-                                                ''.join(b_region))
-            next_a = ia
-            next_b = ib
-            for region_ia, region_ib, region_len in matches[:-1]:
-                region_ia += ia
-                region_ib += ib
-                reg = self.mismatch_region(next_a, region_ia, next_b,
-                                           region_ib)
-                if reg is not None:
-                    yield reg
-                yield 'same', region_ia, region_len + region_ia
-                next_a = region_ia + region_len
-                next_b = region_ib + region_len
-            reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
-            if reg is not None:
-                yield reg
-
-    def mismatch_region(next_a, region_ia,  next_b, region_ib):
-        if next_a < region_ia or next_b < region_ib:
-            return 'conflict', None, None, next_a, region_ia, next_b, region_ib
-    mismatch_region = staticmethod(mismatch_region)
-
     def find_sync_regions(self):
         """Return a list of sync regions, where both descendants match the base.
 
@@ -415,13 +347,16 @@
 
     name_a = local
     name_b = other
+    name_base = None
     labels = opts.get('label', [])
     if len(labels) > 0:
         name_a = labels[0]
     if len(labels) > 1:
         name_b = labels[1]
     if len(labels) > 2:
-        raise util.Abort(_("can only specify two labels."))
+        name_base = labels[2]
+    if len(labels) > 3:
+        raise util.Abort(_("can only specify three labels."))
 
     try:
         localtext = readfile(local)
@@ -437,11 +372,12 @@
     else:
         out = sys.stdout
 
-    reprocess = not opts.get('no_minimal')
-
     m3 = Merge3Text(basetext, localtext, othertext)
-    for line in m3.merge_lines(name_a=name_a, name_b=name_b,
-                               reprocess=reprocess):
+    extrakwargs = {}
+    if name_base is not None:
+        extrakwargs['base_marker'] = '|||||||'
+        extrakwargs['name_base'] = name_base
+    for line in m3.merge_lines(name_a=name_a, name_b=name_b, **extrakwargs):
         out.write(line)
 
     if not opts.get('print'):
--- a/mercurial/transaction.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/transaction.py	Thu Aug 14 16:25:47 2014 -0500
@@ -96,6 +96,9 @@
             opener.chmod(self.journal, createmode & 0666)
             opener.chmod(self.backupjournal, createmode & 0666)
 
+        # hold file generations to be performed on commit
+        self._filegenerators = {}
+
     def __del__(self):
         if self.journal:
             self._abort()
@@ -154,7 +157,7 @@
 
         if file in self.map or file in self.backupmap:
             return
-        backupfile = "journal.%s" % file
+        backupfile = "%s.backup.%s" % (self.journal, file)
         if self.opener.exists(file):
             filepath = self.opener.join(file)
             backuppath = self.opener.join(backupfile)
@@ -173,6 +176,28 @@
         self.backupsfile.flush()
 
     @active
+    def addfilegenerator(self, genid, filenames, genfunc, order=0):
+        """add a function to generates some files at transaction commit
+
+        The `genfunc` argument is a function capable of generating proper
+        content of each entry in the `filename` tuple.
+
+        At transaction close time, `genfunc` will be called with one file
+        object argument per entries in `filenames`.
+
+        The transaction itself is responsible for the backup, creation and
+        final write of such file.
+
+        The `genid` argument is used to ensure the same set of file is only
+        generated once. Call to `addfilegenerator` for a `genid` already
+        present will overwrite the old entry.
+
+        The `order` argument may be used to control the order in which multiple
+        generator will be executed.
+        """
+        self._filegenerators[genid] = (order, filenames, genfunc)
+
+    @active
     def find(self, file):
         if file in self.map:
             return self.entries[self.map[file]]
@@ -213,6 +238,18 @@
     @active
     def close(self):
         '''commit the transaction'''
+        # write files registered for generation
+        for order, filenames, genfunc in sorted(self._filegenerators.values()):
+            files = []
+            try:
+                for name in filenames:
+                    self.addbackup(name)
+                    files.append(self.opener(name, 'w', atomictemp=True))
+                genfunc(*files)
+            finally:
+                for f in files:
+                    f.close()
+
         if self.count == 1 and self.onclose is not None:
             self.onclose()
 
--- a/mercurial/wireproto.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/mercurial/wireproto.py	Thu Aug 14 16:25:47 2014 -0500
@@ -203,7 +203,8 @@
 gboptsmap = {'heads':  'nodes',
              'common': 'nodes',
              'bundlecaps': 'csv',
-             'listkeys': 'csv'}
+             'listkeys': 'csv',
+             'cg': 'boolean'}
 
 # client side
 
@@ -349,6 +350,8 @@
                 value = encodelist(value)
             elif keytype == 'csv':
                 value = ','.join(value)
+            elif keytype == 'boolean':
+                value = bool(value)
             elif keytype != 'plain':
                 raise KeyError('unknown getbundle option type %s'
                                % keytype)
@@ -652,6 +655,8 @@
             opts[k] = decodelist(v)
         elif keytype == 'csv':
             opts[k] = set(v.split(','))
+        elif keytype == 'boolean':
+            opts[k] = '%i' % bool(v)
         elif keytype != 'plain':
             raise KeyError('unknown getbundle option type %s'
                            % keytype)
--- a/tests/hghave.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/hghave.py	Thu Aug 14 16:25:47 2014 -0500
@@ -5,6 +5,17 @@
 
 tempprefix = 'hg-hghave-'
 
+checks = {
+    "true": (lambda: True, "yak shaving"),
+    "false": (lambda: False, "nail clipper"),
+}
+
+def check(name, desc):
+    def decorator(func):
+        checks[name] = (func, desc)
+        return func
+    return decorator
+
 def matchoutput(cmd, regexp, ignorestatus=False):
     """Return True if cmd executes successfully and its output
     is matched by the supplied regular expression.
@@ -19,9 +30,11 @@
         ret = 1
     return (ignorestatus or ret is None) and r.search(s)
 
+@check("baz", "GNU Arch baz client")
 def has_baz():
     return matchoutput('baz --version 2>&1', r'baz Bazaar version')
 
+@check("bzr", "Canonical's Bazaar client")
 def has_bzr():
     try:
         import bzrlib
@@ -29,6 +42,7 @@
     except ImportError:
         return False
 
+@check("bzr114", "Canonical's Bazaar client >= 1.14")
 def has_bzr114():
     try:
         import bzrlib
@@ -37,21 +51,26 @@
     except ImportError:
         return False
 
+@check("cvs", "cvs client/server")
 def has_cvs():
     re = r'Concurrent Versions System.*?server'
     return matchoutput('cvs --version 2>&1', re) and not has_msys()
 
+@check("cvs112", "cvs client/server >= 1.12")
 def has_cvs112():
     re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
     return matchoutput('cvs --version 2>&1', re) and not has_msys()
 
+@check("darcs", "darcs client")
 def has_darcs():
     return matchoutput('darcs --version', r'2\.[2-9]', True)
 
+@check("mtn", "monotone client (>= 1.0)")
 def has_mtn():
     return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
         'mtn --version', r'monotone 0\.', True)
 
+@check("eol-in-paths", "end-of-lines in paths")
 def has_eol_in_paths():
     try:
         fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
@@ -61,6 +80,7 @@
     except (IOError, OSError):
         return False
 
+@check("execbit", "executable bit")
 def has_executablebit():
     try:
         EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
@@ -78,6 +98,7 @@
         return False
     return not (new_file_has_exec or exec_flags_cannot_flip)
 
+@check("icasefs", "case insensitive file system")
 def has_icasefs():
     # Stolen from mercurial.util
     fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
@@ -96,6 +117,7 @@
     finally:
         os.remove(path)
 
+@check("fifo", "named pipes")
 def has_fifo():
     if getattr(os, "mkfifo", None) is None:
         return False
@@ -107,9 +129,11 @@
     except OSError:
         return False
 
+@check("killdaemons", 'killdaemons.py support')
 def has_killdaemons():
     return True
 
+@check("cacheable", "cacheable filesystem")
 def has_cacheable_fs():
     from mercurial import util
 
@@ -120,6 +144,7 @@
     finally:
         os.remove(path)
 
+@check("lsprof", "python lsprof module")
 def has_lsprof():
     try:
         import _lsprof
@@ -127,12 +152,15 @@
     except ImportError:
         return False
 
+@check("gettext", "GNU Gettext (msgfmt)")
 def has_gettext():
     return matchoutput('msgfmt --version', 'GNU gettext-tools')
 
+@check("git", "git command line client")
 def has_git():
     return matchoutput('git --version 2>&1', r'^git version')
 
+@check("docutils", "Docutils text processing library")
 def has_docutils():
     try:
         from docutils.core import publish_cmdline
@@ -146,16 +174,20 @@
         return (0, 0)
     return (int(m.group(1)), int(m.group(2)))
 
+@check("svn15", "subversion client and admin tools >= 1.5")
 def has_svn15():
     return getsvnversion() >= (1, 5)
 
+@check("svn13", "subversion client and admin tools >= 1.3")
 def has_svn13():
     return getsvnversion() >= (1, 3)
 
+@check("svn", "subversion client and admin tools")
 def has_svn():
     return matchoutput('svn --version 2>&1', r'^svn, version') and \
         matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
 
+@check("svn-bindings", "subversion python bindings")
 def has_svn_bindings():
     try:
         import svn.core
@@ -166,10 +198,12 @@
     except ImportError:
         return False
 
+@check("p4", "Perforce server and client")
 def has_p4():
     return (matchoutput('p4 -V', r'Rev\. P4/') and
             matchoutput('p4d -V', r'Rev\. P4D/'))
 
+@check("symlink", "symbolic links")
 def has_symlink():
     if getattr(os, "symlink", None) is None:
         return False
@@ -181,6 +215,7 @@
     except (OSError, AttributeError):
         return False
 
+@check("hardlink", "hardlinks")
 def has_hardlink():
     from mercurial import util
     fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
@@ -196,12 +231,15 @@
     finally:
         os.unlink(fn)
 
+@check("tla", "GNU Arch tla client")
 def has_tla():
     return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
 
+@check("gpg", "gpg client")
 def has_gpg():
     return matchoutput('gpg --version 2>&1', r'GnuPG')
 
+@check("unix-permissions", "unix-style permissions")
 def has_unix_permissions():
     d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
     try:
@@ -218,14 +256,17 @@
     finally:
         os.rmdir(d)
 
+@check("root", "root permissions")
 def has_root():
     return getattr(os, 'geteuid', None) and os.geteuid() == 0
 
+@check("pyflakes", "Pyflakes python linter")
 def has_pyflakes():
     return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
                        r"<stdin>:1: 're' imported but unused",
                        True)
 
+@check("pygments", "Pygments source highlighting library")
 def has_pygments():
     try:
         import pygments
@@ -233,14 +274,17 @@
     except ImportError:
         return False
 
+@check("python243", "python >= 2.4.3")
 def has_python243():
     return sys.version_info >= (2, 4, 3)
 
+@check("outer-repo", "outer repo")
 def has_outer_repo():
     # failing for other reasons than 'no repo' imply that there is a repo
     return not matchoutput('hg root 2>&1',
                            r'abort: no repository found', True)
 
+@check("ssl", "python >= 2.6 ssl module and python OpenSSL")
 def has_ssl():
     try:
         import ssl
@@ -250,19 +294,24 @@
     except ImportError:
         return False
 
+@check("windows", "Windows")
 def has_windows():
     return os.name == 'nt'
 
+@check("system-sh", "system() uses sh")
 def has_system_sh():
     return os.name != 'nt'
 
+@check("serve", "platform and python can manage 'hg serve -d'")
 def has_serve():
     return os.name != 'nt' # gross approximation
 
+@check("test-repo", "running tests from repository")
 def has_test_repo():
     t = os.environ["TESTDIR"]
     return os.path.isdir(os.path.join(t, "..", ".hg"))
 
+@check("tic", "terminfo compiler and curses module")
 def has_tic():
     try:
         import curses
@@ -271,63 +320,20 @@
     except ImportError:
         return False
 
+@check("msys", "Windows with MSYS")
 def has_msys():
     return os.getenv('MSYSTEM')
 
+@check("aix", "AIX")
 def has_aix():
     return sys.platform.startswith("aix")
 
+@check("absimport", "absolute_import in __future__")
 def has_absimport():
     import __future__
     from mercurial import util
     return util.safehasattr(__future__, "absolute_import")
 
+@check("py3k", "running with Python 3.x")
 def has_py3k():
     return 3 == sys.version_info[0]
-
-checks = {
-    "true": (lambda: True, "yak shaving"),
-    "false": (lambda: False, "nail clipper"),
-    "baz": (has_baz, "GNU Arch baz client"),
-    "bzr": (has_bzr, "Canonical's Bazaar client"),
-    "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"),
-    "cacheable": (has_cacheable_fs, "cacheable filesystem"),
-    "cvs": (has_cvs, "cvs client/server"),
-    "cvs112": (has_cvs112, "cvs client/server >= 1.12"),
-    "darcs": (has_darcs, "darcs client"),
-    "docutils": (has_docutils, "Docutils text processing library"),
-    "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"),
-    "execbit": (has_executablebit, "executable bit"),
-    "fifo": (has_fifo, "named pipes"),
-    "gettext": (has_gettext, "GNU Gettext (msgfmt)"),
-    "git": (has_git, "git command line client"),
-    "gpg": (has_gpg, "gpg client"),
-    "hardlink": (has_hardlink, "hardlinks"),
-    "icasefs": (has_icasefs, "case insensitive file system"),
-    "killdaemons": (has_killdaemons, 'killdaemons.py support'),
-    "lsprof": (has_lsprof, "python lsprof module"),
-    "mtn": (has_mtn, "monotone client (>= 1.0)"),
-    "outer-repo": (has_outer_repo, "outer repo"),
-    "p4": (has_p4, "Perforce server and client"),
-    "pyflakes": (has_pyflakes, "Pyflakes python linter"),
-    "pygments": (has_pygments, "Pygments source highlighting library"),
-    "python243": (has_python243, "python >= 2.4.3"),
-    "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"),
-    "svn13": (has_svn13, "subversion client and admin tools >= 1.3"),
-    "svn15": (has_svn15, "subversion client and admin tools >= 1.5"),
-    "svn-bindings": (has_svn_bindings, "subversion python bindings"),
-    "symlink": (has_symlink, "symbolic links"),
-    "system-sh": (has_system_sh, "system() uses sh"),
-    "test-repo": (has_test_repo, "running tests from repository"),
-    "tic": (has_tic, "terminfo compiler and curses module"),
-    "tla": (has_tla, "GNU Arch tla client"),
-    "unix-permissions": (has_unix_permissions, "unix-style permissions"),
-    "windows": (has_windows, "Windows"),
-    "msys": (has_msys, "Windows with MSYS"),
-    "aix": (has_aix, "AIX"),
-    "absimport": (has_absimport, "absolute_import in __future__"),
-    "py3k": (has_py3k, "running with Python 3.x"),
-}
--- a/tests/run-tests.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/run-tests.py	Thu Aug 14 16:25:47 2014 -0500
@@ -57,6 +57,7 @@
 import threading
 import killdaemons as killmod
 import Queue as queue
+from xml.dom import minidom
 import unittest
 
 processlock = threading.Lock()
@@ -190,6 +191,8 @@
              " (implies --keep-tmpdir)")
     parser.add_option("-v", "--verbose", action="store_true",
         help="output verbose messages")
+    parser.add_option("--xunit", type="string",
+                      help="record xunit results at specified path")
     parser.add_option("--view", type="string",
         help="external diff viewer")
     parser.add_option("--with-hg", type="string",
@@ -304,6 +307,20 @@
 
     return log(*msg)
 
+# Bytes that break XML even in a CDATA block: control characters 0-31
+# sans \t, \n and \r
+CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]")
+
+def cdatasafe(data):
+    """Make a string safe to include in a CDATA block.
+
+    Certain control characters are illegal in a CDATA block, and
+    there's no way to include a ]]> in a CDATA either. This function
+    replaces illegal bytes with ? and adds a space between the ]] so
+    that it won't break the CDATA block.
+    """
+    return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>')
+
 def log(*msg):
     """Log something to stdout.
 
@@ -460,8 +477,15 @@
                 raise
             except SkipTest, e:
                 result.addSkip(self, str(e))
+                # The base class will have already counted this as a
+                # test we "ran", but we want to exclude skipped tests
+                # from those we count towards those run.
+                result.testsRun -= 1
             except IgnoreTest, e:
                 result.addIgnore(self, str(e))
+                # As with skips, ignores also should be excluded from
+                # the number of tests executed.
+                result.testsRun -= 1
             except WarnTest, e:
                 result.addWarn(self, str(e))
             except self.failureException, e:
@@ -786,7 +810,15 @@
         for n, l in enumerate(lines):
             if not l.endswith('\n'):
                 l += '\n'
-            if l.startswith('#if'):
+            if l.startswith('#require'):
+                lsplit = l.split()
+                if len(lsplit) < 2 or lsplit[0] != '#require':
+                    after.setdefault(pos, []).append('  !!! invalid #require\n')
+                if not self._hghave(lsplit[1:]):
+                    script = ["exit 80\n"]
+                    break
+                after.setdefault(pos, []).append(l)
+            elif l.startswith('#if'):
                 lsplit = l.split()
                 if len(lsplit) < 2 or lsplit[0] != '#if':
                     after.setdefault(pos, []).append('  !!! invalid #if\n')
@@ -1041,7 +1073,7 @@
         output = re.sub(s, r, output)
     return ret, output.splitlines(True)
 
-iolock = threading.Lock()
+iolock = threading.RLock()
 
 class SkipTest(Exception):
     """Raised to indicate that a test is to be skipped."""
@@ -1077,46 +1109,59 @@
 
         self.times = []
         self._started = {}
+        self._stopped = {}
+        # Data stored for the benefit of generating xunit reports.
+        self.successes = []
+        self.faildata = {}
 
     def addFailure(self, test, reason):
         self.failures.append((test, reason))
 
-        iolock.acquire()
         if self._options.first:
             self.stop()
         else:
+            iolock.acquire()
             if not self._options.nodiff:
                 self.stream.write('\nERROR: %s output changed\n' % test)
 
             self.stream.write('!')
             self.stream.flush()
-        iolock.release()
+            iolock.release()
 
-    def addError(self, *args, **kwargs):
-        super(TestResult, self).addError(*args, **kwargs)
+    def addSuccess(self, test):
+        iolock.acquire()
+        super(TestResult, self).addSuccess(test)
+        iolock.release()
+        self.successes.append(test)
 
+    def addError(self, test, err):
+        super(TestResult, self).addError(test, err)
         if self._options.first:
             self.stop()
 
     # Polyfill.
     def addSkip(self, test, reason):
         self.skipped.append((test, reason))
-
+        iolock.acquire()
         if self.showAll:
             self.stream.writeln('skipped %s' % reason)
         else:
             self.stream.write('s')
             self.stream.flush()
+        iolock.release()
 
     def addIgnore(self, test, reason):
         self.ignored.append((test, reason))
-
+        iolock.acquire()
         if self.showAll:
             self.stream.writeln('ignored %s' % reason)
         else:
-            if reason != 'not retesting':
+            if reason != 'not retesting' and reason != "doesn't match keyword":
                 self.stream.write('i')
+            else:
+                self.testsRun += 1
             self.stream.flush()
+        iolock.release()
 
     def addWarn(self, test, reason):
         self.warned.append((test, reason))
@@ -1124,16 +1169,20 @@
         if self._options.first:
             self.stop()
 
+        iolock.acquire()
         if self.showAll:
             self.stream.writeln('warned %s' % reason)
         else:
             self.stream.write('~')
             self.stream.flush()
+        iolock.release()
 
     def addOutputMismatch(self, test, ret, got, expected):
         """Record a mismatch in test output for a particular test."""
 
         accepted = False
+        failed = False
+        lines = []
 
         iolock.acquire()
         if self._options.nodiff:
@@ -1162,7 +1211,8 @@
                     else:
                         rename(test.errpath, '%s.out' % test.path)
                     accepted = True
-
+            if not accepted and not failed:
+                self.faildata[test.name] = ''.join(lines)
         iolock.release()
 
         return accepted
@@ -1170,17 +1220,30 @@
     def startTest(self, test):
         super(TestResult, self).startTest(test)
 
-        self._started[test.name] = time.time()
+        # os.times module computes the user time and system time spent by
+        # child's processes along with real elapsed time taken by a process.
+        # This module has one limitation. It can only work for Linux user
+        # and not for Windows.
+        self._started[test.name] = os.times()
 
     def stopTest(self, test, interrupted=False):
         super(TestResult, self).stopTest(test)
 
-        self.times.append((test.name, time.time() - self._started[test.name]))
+        self._stopped[test.name] = os.times()
+
+        starttime = self._started[test.name]
+        endtime = self._stopped[test.name]
+        self.times.append((test.name, endtime[2] - starttime[2],
+                    endtime[3] - starttime[3], endtime[4] - starttime[4]))
+
         del self._started[test.name]
+        del self._stopped[test.name]
 
         if interrupted:
+            iolock.acquire()
             self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
-                test.name, self.times[-1][1]))
+                test.name, self.times[-1][3]))
+            iolock.release()
 
 class TestSuite(unittest.TestSuite):
     """Custom unitest TestSuite that knows how to execute Mercurial tests."""
@@ -1314,6 +1377,7 @@
         skipped = len(result.skipped)
         ignored = len(result.ignored)
 
+        iolock.acquire()
         self.stream.writeln('')
 
         if not self._runner.options.noskips:
@@ -1326,20 +1390,39 @@
         for test, msg in result.errors:
             self.stream.writeln('Errored %s: %s' % (test.name, msg))
 
+        if self._runner.options.xunit:
+            xuf = open(self._runner.options.xunit, 'wb')
+            try:
+                timesd = dict(
+                    (test, real) for test, cuser, csys, real in result.times)
+                doc = minidom.Document()
+                s = doc.createElement('testsuite')
+                s.setAttribute('name', 'run-tests')
+                s.setAttribute('tests', str(result.testsRun))
+                s.setAttribute('errors', "0") # TODO
+                s.setAttribute('failures', str(failed))
+                s.setAttribute('skipped', str(skipped + ignored))
+                doc.appendChild(s)
+                for tc in result.successes:
+                    t = doc.createElement('testcase')
+                    t.setAttribute('name', tc.name)
+                    t.setAttribute('time', '%.3f' % timesd[tc.name])
+                    s.appendChild(t)
+                for tc, err in sorted(result.faildata.iteritems()):
+                    t = doc.createElement('testcase')
+                    t.setAttribute('name', tc)
+                    t.setAttribute('time', '%.3f' % timesd[tc])
+                    cd = doc.createCDATASection(cdatasafe(err))
+                    t.appendChild(cd)
+                    s.appendChild(t)
+                xuf.write(doc.toprettyxml(indent='  ', encoding='utf-8'))
+            finally:
+                xuf.close()
+
         self._runner._checkhglib('Tested')
 
-        # When '--retest' is enabled, only failure tests run. At this point
-        # "result.testsRun" holds the count of failure test that has run. But
-        # as while printing output, we have subtracted the skipped and ignored
-        # count from "result.testsRun". Therefore, to make the count remain
-        # the same, we need to add skipped and ignored count in here.
-        if self._runner.options.retest:
-            result.testsRun = result.testsRun + skipped + ignored
-
-        # This differs from unittest's default output in that we don't count
-        # skipped and ignored tests as part of the total test count.
         self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
-            % (result.testsRun - skipped - ignored,
+            % (result.testsRun,
                skipped + ignored, warned, failed))
         if failed:
             self.stream.writeln('python hash seed: %s' %
@@ -1347,15 +1430,19 @@
         if self._runner.options.time:
             self.printtimes(result.times)
 
+        iolock.release()
+
         return result
 
     def printtimes(self, times):
+        # iolock held by run
         self.stream.writeln('# Producing time report')
-        times.sort(key=lambda t: (t[1], t[0]), reverse=True)
-        cols = '%7.3f   %s'
-        self.stream.writeln('%-7s   %s' % ('Time', 'Test'))
-        for test, timetaken in times:
-            self.stream.writeln(cols % (timetaken, test))
+        times.sort(key=lambda t: (t[3]))
+        cols = '%7.3f %7.3f %7.3f   %s'
+        self.stream.writeln('%-7s %-7s %-7s   %s' % ('cuser', 'csys', 'real',
+                    'Test'))
+        for test, cuser, csys, real in times:
+            self.stream.writeln(cols % (cuser, csys, real, test))
 
 class TestRunner(object):
     """Holds context for executing tests.
--- a/tests/test-acl.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-acl.t	Thu Aug 14 16:25:47 2014 -0500
@@ -82,6 +82,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -140,6 +141,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -202,6 +204,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -274,6 +277,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -341,6 +345,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -413,6 +418,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -482,6 +488,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -556,6 +563,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -627,6 +635,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -700,6 +709,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -779,6 +789,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -859,6 +870,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -934,6 +946,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1020,6 +1033,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1100,6 +1114,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1176,6 +1191,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1252,6 +1268,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1329,6 +1346,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1444,6 +1462,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1527,6 +1546,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1606,6 +1626,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1681,6 +1702,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1750,6 +1772,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1838,6 +1861,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1925,6 +1949,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1999,6 +2024,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -2080,6 +2106,7 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
--- a/tests/test-alias.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-alias.t	Thu Aug 14 16:25:47 2014 -0500
@@ -10,6 +10,7 @@
   > unknown = bargle
   > ambiguous = s
   > recursive = recursive
+  > disabled = email
   > nodefinition =
   > noclosingquotation = '
   > no--cwd = status --cwd elsewhere
@@ -30,6 +31,7 @@
   > echo1 = !printf '\$1\n'
   > echo2 = !printf '\$2\n'
   > echo13 = !printf '\$1 \$3\n'
+  > echotokens = !printf "%s\n" "\$@"
   > count = !hg log -r "\$@" --template=. | wc -c | sed -e 's/ //g'
   > mcount = !hg log \$@ --template=. | wc -c | sed -e 's/ //g'
   > rt = root
@@ -60,7 +62,7 @@
 unknown
 
   $ hg unknown
-  alias 'unknown' resolves to unknown command 'bargle'
+  abort: alias 'unknown' resolves to unknown command 'bargle'
   [255]
   $ hg help unknown
   alias 'unknown' resolves to unknown command 'bargle'
@@ -69,7 +71,7 @@
 ambiguous
 
   $ hg ambiguous
-  alias 'ambiguous' resolves to ambiguous command 's'
+  abort: alias 'ambiguous' resolves to ambiguous command 's'
   [255]
   $ hg help ambiguous
   alias 'ambiguous' resolves to ambiguous command 's'
@@ -78,16 +80,32 @@
 recursive
 
   $ hg recursive
-  alias 'recursive' resolves to unknown command 'recursive'
+  abort: alias 'recursive' resolves to unknown command 'recursive'
   [255]
   $ hg help recursive
   alias 'recursive' resolves to unknown command 'recursive'
 
 
+disabled
+
+  $ hg disabled
+  abort: alias 'disabled' resolves to unknown command 'email'
+  ('email' is provided by 'patchbomb' extension)
+  [255]
+  $ hg help disabled
+  alias 'disabled' resolves to unknown command 'email'
+  
+  'email' is provided by the following extension:
+  
+      patchbomb     command to send changesets as (a series of) patch emails
+  
+  (use "hg help extensions" for information on enabling extensions)
+
+
 no definition
 
   $ hg nodef
-  no definition for alias 'nodefinition'
+  abort: no definition for alias 'nodefinition'
   [255]
   $ hg help nodef
   no definition for alias 'nodefinition'
@@ -96,7 +114,7 @@
 no closing quotation
 
   $ hg noclosing
-  error in definition for alias 'noclosingquotation': No closing quotation
+  abort: error in definition for alias 'noclosingquotation': No closing quotation
   [255]
   $ hg help noclosing
   error in definition for alias 'noclosingquotation': No closing quotation
@@ -105,27 +123,30 @@
 invalid options
 
   $ hg no--cwd
-  error in definition for alias 'no--cwd': --cwd may only be given on the command line
+  abort: error in definition for alias 'no--cwd': --cwd may only be given on the command line
   [255]
   $ hg help no--cwd
-  error in definition for alias 'no--cwd': --cwd may only be given on the command line
+  error in definition for alias 'no--cwd': --cwd may only be given on the
+  command line
   $ hg no-R
-  error in definition for alias 'no-R': -R may only be given on the command line
+  abort: error in definition for alias 'no-R': -R may only be given on the command line
   [255]
   $ hg help no-R
   error in definition for alias 'no-R': -R may only be given on the command line
   $ hg no--repo
-  error in definition for alias 'no--repo': --repo may only be given on the command line
+  abort: error in definition for alias 'no--repo': --repo may only be given on the command line
   [255]
   $ hg help no--repo
-  error in definition for alias 'no--repo': --repo may only be given on the command line
+  error in definition for alias 'no--repo': --repo may only be given on the
+  command line
   $ hg no--repository
-  error in definition for alias 'no--repository': --repository may only be given on the command line
+  abort: error in definition for alias 'no--repository': --repository may only be given on the command line
   [255]
   $ hg help no--repository
-  error in definition for alias 'no--repository': --repository may only be given on the command line
+  error in definition for alias 'no--repository': --repository may only be given
+  on the command line
   $ hg no--config
-  error in definition for alias 'no--config': --config may only be given on the command line
+  abort: error in definition for alias 'no--config': --config may only be given on the command line
   [255]
 
 optional repository
@@ -229,6 +250,10 @@
   foo
   $ hg echoall 'test $2' foo
   test $2 foo
+  $ hg echoall 'test $@' foo '$@'
+  test $@ foo $@
+  $ hg echoall 'test "$@"' foo '"$@"'
+  test "$@" foo "$@"
   $ hg echo1 foo bar baz
   foo
   $ hg echo2 foo bar baz
@@ -237,6 +262,22 @@
   foo baz
   $ hg echo2 foo
   
+  $ hg echotokens
+  
+  $ hg echotokens foo 'bar $1 baz'
+  foo
+  bar $1 baz
+  $ hg echotokens 'test $2' foo
+  test $2
+  foo
+  $ hg echotokens 'test $@' foo '$@'
+  test $@
+  foo
+  $@
+  $ hg echotokens 'test "$@"' foo '"$@"'
+  test "$@"
+  foo
+  "$@"
   $ echo bar > bar
   $ hg commit -qA -m bar
   $ hg count .
@@ -370,7 +411,7 @@
   
   alias for: hg root
   
-  use "hg help rt" to show the full help text
+  (use "hg rt -h" to show more help)
   [255]
 
 invalid global arguments for normal commands, aliases, and shell aliases
@@ -399,7 +440,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
   [255]
   $ hg --invalid mylog
   hg: option --invalid not recognized
@@ -425,7 +466,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
   [255]
   $ hg --invalid blank
   hg: option --invalid not recognized
@@ -451,7 +492,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
   [255]
 
 This should show id:
--- a/tests/test-archive-symlinks.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-archive-symlinks.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" symlink || exit 80
+#require symlink
 
   $ origdir=`pwd`
 
--- a/tests/test-archive.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-archive.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
--- a/tests/test-bad-pull.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-bad-pull.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 #if windows
   $ hg clone http://localhost:$HGPORT/ copy
--- a/tests/test-bookmarks-pushpull.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-bookmarks-pushpull.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ cat << EOF >> $HGRCPATH
   > [ui]
--- a/tests/test-bundle2.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-bundle2.t	Thu Aug 14 16:25:47 2014 -0500
@@ -964,7 +964,8 @@
   >     raise util.Abort('Abandon ship!', hint="don't panic")
   > 
   > def uisetup(ui):
-  >     exchange.bundle2partsgenerators.insert(0, _pushbundle2failpart)
+  >     exchange.b2partsgenmapping['failpart'] = _pushbundle2failpart
+  >     exchange.b2partsgenorder.insert(0, 'failpart')
   > 
   > EOF
 
--- a/tests/test-casecollision-merge.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-casecollision-merge.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,4 @@
-run only on case-insensitive filesystems
-
-  $ "$TESTDIR/hghave" icasefs || exit 80
+#require icasefs
 
 ################################
 test for branch merging
--- a/tests/test-casecollision.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-casecollision.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,4 @@
-run only on case-sensitive filesystems
-
-  $ "$TESTDIR/hghave" no-icasefs || exit 80
+#require no-icasefs
 
 test file addition with colliding case
 
--- a/tests/test-casefolding.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-casefolding.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" icasefs || exit 80
+#require icasefs
 
   $ hg debugfs | grep 'case-sensitive:'
   case-sensitive: no
--- a/tests/test-changelog-exec.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-changelog-exec.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,9 +1,9 @@
+#require execbit
+
 b51a8138292a introduced a regression where we would mention in the
 changelog executable files added by the second parent of a merge. Test
 that that doesn't happen anymore
 
-  $ "$TESTDIR/hghave" execbit || exit 80
-
   $ hg init repo
   $ cd repo
   $ echo foo > foo
--- a/tests/test-check-code-hg.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-check-code-hg.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-#if test-repo
+#require test-repo
 
   $ check_code="$TESTDIR"/../contrib/check-code.py
   $ cd "$TESTDIR"/..
@@ -13,5 +13,3 @@
   Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/socketutil.py it has no-che?k-code (glob)
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-check-commit-hg.t	Thu Aug 14 16:25:47 2014 -0500
@@ -0,0 +1,26 @@
+#require test-repo
+
+Enable obsolescence to avoid the warning issue when obsmarker are found
+
+  $ cat > obs.py << EOF
+  > import mercurial.obsolete
+  > mercurial.obsolete._enabled = True
+  > EOF
+  $ echo '[extensions]' >> $HGRCPATH
+  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
+
+Go back in the hg repo
+
+  $ cd $TESTDIR/..
+
+  $ for node in `hg log --rev 'draft() and ::.' --template '{node|short}\n'`; do
+  >    hg export $node | contrib/check-commit > ${TESTTMP}/check-commit.out
+  >    if [ $? -ne 0 ]; then
+  >        echo "Revision $node does not comply to commit message rules"
+  >        echo '------------------------------------------------------'
+  >        cat ${TESTTMP}/check-commit.out
+  >        echo
+  >   fi
+  > done
+
+
--- a/tests/test-check-pyflakes.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-check-pyflakes.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-#if test-repo pyflakes
+#require test-repo pyflakes
 
   $ cd "`dirname "$TESTDIR"`"
 
@@ -19,4 +19,4 @@
   contrib/win32/hgwebdir_wsgi.py:93: 'from isapi.install import *' used; unable to detect undefined names (glob)
   tests/filterpyflakes.py:58: undefined name 'undefinedname'
   
-#endif
+
--- a/tests/test-clone-cgi.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-clone-cgi.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate web paths as if they were file paths
+#require no-msys # MSYS will translate web paths as if they were file paths
 
 This is a test of the wire protocol over CGI-based hgweb.
 initialize repository
--- a/tests/test-commandserver.py.out	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-commandserver.py.out	Thu Aug 14 16:25:47 2014 -0500
@@ -34,7 +34,7 @@
  summary       summarize working directory state
  update        update working directory (or switch revisions)
 
-use "hg help" for the full list of commands or "hg -v" for details
+(use "hg help" for the full list of commands or "hg -v" for details)
  runcommand id --quiet
 000000000000
  runcommand id
--- a/tests/test-commit.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-commit.t	Thu Aug 14 16:25:47 2014 -0500
@@ -359,6 +359,20 @@
 
   $ cat >> .hg/hgrc <<EOF
   > [committemplate]
+  > changeset.commit.normal = HG: this is "commit.normal" template
+  >     HG: {extramsg}
+  >     {if(currentbookmark,
+  >    "HG: bookmark '{currentbookmark}' is activated\n",
+  >    "HG: no bookmark is activated\n")}{subrepos %
+  >    "HG: subrepo '{subrepo}' is changed\n"}
+  > 
+  > changeset.commit = HG: this is "commit" template
+  >     HG: {extramsg}
+  >     {if(currentbookmark,
+  >    "HG: bookmark '{currentbookmark}' is activated\n",
+  >    "HG: no bookmark is activated\n")}{subrepos %
+  >    "HG: subrepo '{subrepo}' is changed\n"}
+  > 
   > changeset = HG: this is customized commit template
   >     HG: {extramsg}
   >     {if(currentbookmark,
@@ -373,7 +387,7 @@
   $ echo 'sub2 = sub2' >> .hgsub
 
   $ HGEDITOR=cat hg commit -S -q
-  HG: this is customized commit template
+  HG: this is "commit.normal" template
   HG: Leave message empty to abort commit.
   HG: bookmark 'currentbookmark' is activated
   HG: subrepo 'sub' is changed
@@ -381,9 +395,28 @@
   abort: empty commit message
   [255]
 
+  $ cat >> .hg/hgrc <<EOF
+  > [committemplate]
+  > changeset.commit.normal =
+  > # now, "changeset.commit" should be chosen for "hg commit"
+  > EOF
+
   $ hg bookmark --inactive currentbookmark
   $ hg forget .hgsub
   $ HGEDITOR=cat hg commit -q
+  HG: this is "commit" template
+  HG: Leave message empty to abort commit.
+  HG: no bookmark is activated
+  abort: empty commit message
+  [255]
+
+  $ cat >> .hg/hgrc <<EOF
+  > [committemplate]
+  > changeset.commit =
+  > # now, "changeset" should be chosen for "hg commit"
+  > EOF
+
+  $ HGEDITOR=cat hg commit -q
   HG: this is customized commit template
   HG: Leave message empty to abort commit.
   HG: no bookmark is activated
--- a/tests/test-completion.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-completion.t	Thu Aug 14 16:25:47 2014 -0500
@@ -257,7 +257,7 @@
   debugsuccessorssets: 
   debugwalk: include, exclude
   debugwireargs: three, four, five, ssh, remotecmd, insecure
-  graft: rev, continue, edit, log, currentdate, currentuser, date, user, tool, dry-run
+  graft: rev, continue, edit, log, force, currentdate, currentuser, date, user, tool, dry-run
   grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
   heads: rev, topo, active, closed, style, template
   help: extension, command, keyword
--- a/tests/test-conflict.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-conflict.t	Thu Aug 14 16:25:47 2014 -0500
@@ -198,3 +198,37 @@
   5
   >>>>>>> other
   Hop we are done.
+
+internal:merge3
+
+  $ hg up -q --clean .
+
+  $ hg merge 1 --tool internal:merge3
+  merging a
+  warning: conflicts during merge.
+  merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ cat a
+  Small Mathematical Series.
+  <<<<<<< local
+  1
+  2
+  3
+  6
+  8
+  ||||||| base
+  One
+  Two
+  Three
+  Four
+  Five
+  =======
+  1
+  2
+  3
+  4
+  5
+  >>>>>>> other
+  Hop we are done.
--- a/tests/test-contrib.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-contrib.t	Thu Aug 14 16:25:47 2014 -0500
@@ -143,23 +143,11 @@
   $ echo not other >> conflict-local
   $ echo end >> conflict-local
   $ echo end >> conflict-other
+
   $ python simplemerge -p conflict-local base conflict-other
   base
   <<<<<<< conflict-local
   not other
-  =======
-  other
-  >>>>>>> conflict-other
-  end
-  warning: conflicts during merge.
-  [1]
-
---no-minimal
-
-  $ python simplemerge -p --no-minimal conflict-local base conflict-other
-  base
-  <<<<<<< conflict-local
-  not other
   end
   =======
   other
@@ -174,10 +162,11 @@
   base
   <<<<<<< foo
   not other
+  end
   =======
   other
+  end
   >>>>>>> conflict-other
-  end
   warning: conflicts during merge.
   [1]
 
@@ -187,17 +176,33 @@
   base
   <<<<<<< foo
   not other
+  end
   =======
   other
+  end
   >>>>>>> bar
+  warning: conflicts during merge.
+  [1]
+
+3 labels
+
+  $ python simplemerge -p -L foo -L bar -L base conflict-local base conflict-other
+  base
+  <<<<<<< foo
+  not other
   end
+  ||||||| base
+  =======
+  other
+  end
+  >>>>>>> bar
   warning: conflicts during merge.
   [1]
 
 too many labels
 
-  $ python simplemerge -p -L foo -L bar -L baz conflict-local base conflict-other
-  abort: can only specify two labels.
+  $ python simplemerge -p -L foo -L bar -L baz -L buz conflict-local base conflict-other
+  abort: can only specify three labels.
   [255]
 
 binary file
@@ -231,7 +236,7 @@
    -L --label       labels to use on conflict markers
    -a --text        treat all files as text
    -p --print       print results instead of overwriting LOCAL
-      --no-minimal  do not try to minimize conflict regions
+      --no-minimal  no effect (DEPRECATED)
    -h --help        display help and exit
    -q --quiet       suppress output
 
@@ -251,7 +256,7 @@
    -L --label       labels to use on conflict markers
    -a --text        treat all files as text
    -p --print       print results instead of overwriting LOCAL
-      --no-minimal  do not try to minimize conflict regions
+      --no-minimal  no effect (DEPRECATED)
    -h --help        display help and exit
    -q --quiet       suppress output
   [1]
@@ -272,7 +277,7 @@
    -L --label       labels to use on conflict markers
    -a --text        treat all files as text
    -p --print       print results instead of overwriting LOCAL
-      --no-minimal  do not try to minimize conflict regions
+      --no-minimal  no effect (DEPRECATED)
    -h --help        display help and exit
    -q --quiet       suppress output
   [1]
--- a/tests/test-convert-baz.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-baz.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" baz symlink || exit 80
+#require baz symlink
 
   $ baz my-id "mercurial <mercurial@selenic.com>"
 
--- a/tests/test-convert-bzr-114.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-bzr-114.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,5 @@
+#require bzr114
 
-  $ "$TESTDIR/hghave" bzr114 || exit 80
   $ . "$TESTDIR/bzr-definitions"
 
 The file/directory replacement can only be reproduced on
--- a/tests/test-convert-cvs-branch.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-cvs-branch.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,7 +1,8 @@
+#require cvs
+
 This is http://mercurial.selenic.com/bts/issue1148
 and http://mercurial.selenic.com/bts/issue1447
 
-  $ "$TESTDIR/hghave" cvs || exit 80
   $ cvscall()
   > {
   >     cvs -f "$@" > /dev/null
--- a/tests/test-convert-cvs-detectmerge.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-cvs-detectmerge.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,8 +1,9 @@
+#require cvs
+
 Test config convert.cvsps.mergefrom config setting.
 (Should test similar mergeto feature, but I don't understand it yet.)
 Requires builtin cvsps.
 
-  $ "$TESTDIR/hghave" cvs || exit 80
   $ CVSROOT=`pwd`/cvsrepo
   $ export CVSROOT
 
--- a/tests/test-convert-cvs-synthetic.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-cvs-synthetic.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,7 @@
+#require cvs112
+
 This feature requires use of builtin cvsps!
 
-  $ "$TESTDIR/hghave" cvs112 || exit 80
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert = " >> $HGRCPATH
 
--- a/tests/test-convert-cvs.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-cvs.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,5 @@
+#require cvs
 
-  $ "$TESTDIR/hghave" cvs || exit 80
   $ cvscall()
   > {
   >     cvs -f "$@"
--- a/tests/test-convert-cvsnt-mergepoints.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-cvsnt-mergepoints.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,5 @@
+#require cvs
 
-  $ "$TESTDIR/hghave" cvs || exit 80
   $ filterpath()
   > {
   >     eval "$@" | sed "s:$CVSROOT:*REPO*:g"
--- a/tests/test-convert-darcs.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-darcs.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,5 @@
+#require darcs
 
-  $ "$TESTDIR/hghave" darcs || exit 80
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert=" >> $HGRCPATH
   $ DARCS_EMAIL='test@example.org'; export DARCS_EMAIL
--- a/tests/test-convert-git.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-git.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,5 @@
+#require git
 
-  $ "$TESTDIR/hghave" git || exit 80
   $ echo "[core]" >> $HOME/.gitconfig
   $ echo "autocrlf = false" >> $HOME/.gitconfig
   $ echo "[core]" >> $HOME/.gitconfig
--- a/tests/test-convert-hg-svn.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-hg-svn.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,5 @@
+#require svn svn-bindings
 
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert = " >> $HGRCPATH
   $ echo "mq = " >> $HGRCPATH
--- a/tests/test-convert-mtn.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-mtn.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" mtn || exit 80
+#require mtn
 
 Monotone directory is called .monotone on *nix and monotone
 on Windows.
--- a/tests/test-convert-p4-filetypes.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-p4-filetypes.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" p4 execbit symlink || exit 80
+#require p4 execbit symlink
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert = " >> $HGRCPATH
--- a/tests/test-convert-p4.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-p4.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" p4 || exit 80
+#require p4
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert = " >> $HGRCPATH
--- a/tests/test-convert-svn-branches.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-svn-branches.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-convert-svn-encoding.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-svn-encoding.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-convert-svn-move.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-svn-move.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-convert-svn-sink.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-svn-sink.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" svn13 || exit 80
+#require svn13
 
   $ svnupanddisplay()
   > {
--- a/tests/test-convert-svn-source.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-svn-source.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-convert-svn-startrev.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-svn-startrev.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-convert-svn-tags.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-svn-tags.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-convert-tagsbranch-topology.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-tagsbranch-topology.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,5 @@
+#require git
 
-  $ "$TESTDIR/hghave" git || exit 80
   $ echo "[core]" >> $HOME/.gitconfig
   $ echo "autocrlf = false" >> $HOME/.gitconfig
   $ echo "[core]" >> $HOME/.gitconfig
--- a/tests/test-convert-tla.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert-tla.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,5 @@
+#require tla symlink
 
-  $ "$TESTDIR/hghave" tla symlink || exit 80
   $ tla my-id "mercurial <mercurial@selenic.com>"
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert=" >> $HGRCPATH
--- a/tests/test-convert.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-convert.t	Thu Aug 14 16:25:47 2014 -0500
@@ -272,7 +272,7 @@
       --sourcesort       preserve source changesets order
       --closesort        try to reorder closed revisions
   
-  use "hg -v help convert" to show the global options
+  (some details hidden, use --verbose to show complete help)
   $ hg init a
   $ cd a
   $ echo a > a
--- a/tests/test-diff-upgrade.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-diff-upgrade.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "autodiff=$TESTDIR/autodiff.py" >> $HGRCPATH
--- a/tests/test-dispatch.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-dispatch.t	Thu Aug 14 16:25:47 2014 -0500
@@ -19,7 +19,7 @@
   
   output the current or given revision of files
   
-  options:
+  options ([+] can be repeated):
   
    -o --output FORMAT       print output to file with formatted name
    -r --rev REV             print the given revision
@@ -27,9 +27,7 @@
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
   
-  [+] marked option can be specified multiple times
-  
-  use "hg help cat" to show the full help text
+  (use "hg cat -h" to show more help)
   [255]
 
 [defaults]
--- a/tests/test-encoding-align.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-encoding-align.t	Thu Aug 14 16:25:47 2014 -0500
@@ -58,7 +58,7 @@
                           \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
                           \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
   
-  use "hg -v help showoptlist" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
   $ rm -f s; touch s
--- a/tests/test-encoding-textwrap.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-encoding-textwrap.t	Thu Aug 14 16:25:47 2014 -0500
@@ -69,7 +69,7 @@
       \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc)
       \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc)
   
-  use "hg -v help show_full_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (1-2) display Japanese full-width characters in utf-8
 
@@ -84,7 +84,7 @@
       \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc)
       \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc)
   
-  use "hg -v help show_full_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
 (1-3) display Japanese half-width characters in cp932
@@ -100,7 +100,7 @@
       \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc)
       \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc)
   
-  use "hg -v help show_half_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (1-4) display Japanese half-width characters in utf-8
 
@@ -115,7 +115,7 @@
       \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc)
       \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc)
   
-  use "hg -v help show_half_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
 
@@ -136,7 +136,7 @@
       \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
       \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
   
-  use "hg -v help show_ambig_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-1-2) display Japanese ambiguous-width characters in utf-8
 
@@ -151,7 +151,7 @@
       \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
       \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
   
-  use "hg -v help show_ambig_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-1-3) display Russian ambiguous-width characters in cp1251
 
@@ -166,7 +166,7 @@
       \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
       \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
   
-  use "hg -v help show_ambig_ru" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-1-4) display Russian ambiguous-width characters in utf-8
 
@@ -181,7 +181,7 @@
       \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
       \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
   
-  use "hg -v help show_ambig_ru" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
 (2-2) treat width of ambiguous characters as wide
@@ -202,7 +202,7 @@
       \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
       \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
   
-  use "hg -v help show_ambig_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-2-2) display Japanese ambiguous-width characters in utf-8
 
@@ -220,7 +220,7 @@
       \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
       \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
   
-  use "hg -v help show_ambig_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-2-3) display Russian ambiguous-width characters in cp1251
 
@@ -238,7 +238,7 @@
       \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
       \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
   
-  use "hg -v help show_ambig_ru" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-2-4) display Russian ambiguous-width characters in utf-8
 
@@ -256,6 +256,6 @@
       \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
       \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
   
-  use "hg -v help show_ambig_ru" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ cd ..
--- a/tests/test-eolfilename.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-eolfilename.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,6 @@
-http://mercurial.selenic.com/bts/issue352
+#require eol-in-paths
 
-  $ "$TESTDIR/hghave" eol-in-paths || exit 80
+http://mercurial.selenic.com/bts/issue352
 
 test issue352
 
--- a/tests/test-execute-bit.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-execute-bit.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
   $ hg init
   $ echo a > a
--- a/tests/test-extdiff.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-extdiff.t	Thu Aug 14 16:25:47 2014 -0500
@@ -37,7 +37,7 @@
       compared to the working directory, and, when no revisions are specified,
       the working directory files are compared to its parent.
   
-  options:
+  options ([+] can be repeated):
   
    -o --option OPT [+]      pass option to comparison program
    -r --rev REV [+]         revision
@@ -45,9 +45,7 @@
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help falabala" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg ci -d '0 0' -mtest1
 
--- a/tests/test-extension.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-extension.t	Thu Aug 14 16:25:47 2014 -0500
@@ -301,7 +301,7 @@
   
    foo           yet another foo command
   
-  global options:
+  global options ([+] can be repeated):
   
    -R --repository REPO   repository root directory or name of overlay bundle
                           file
@@ -321,8 +321,6 @@
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
-  
-  [+] marked option can be specified multiple times
 
 
 
@@ -337,7 +335,7 @@
    debugfoobar   yet another debug command
    foo           yet another foo command
   
-  global options:
+  global options ([+] can be repeated):
   
    -R --repository REPO   repository root directory or name of overlay bundle
                           file
@@ -357,8 +355,6 @@
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
-  
-  [+] marked option can be specified multiple times
 
 
 
@@ -388,9 +384,9 @@
       compared to the working directory, and, when no revisions are specified,
       the working directory files are compared to its parent.
   
-  use "hg help -e extdiff" to show help for the extdiff extension
+  (use "hg help -e extdiff" to show help for the extdiff extension)
   
-  options:
+  options ([+] can be repeated):
   
    -p --program CMD         comparison program to run
    -o --option OPT [+]      pass option to comparison program
@@ -399,9 +395,7 @@
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help extdiff" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
 
@@ -469,7 +463,7 @@
   
    extdiff       use external program to diff repository (or selected files)
   
-  use "hg -v help extdiff" to show builtin aliases and global options
+  (use "hg help -v extdiff" to show built-in aliases and global options)
 
 
 
@@ -533,7 +527,7 @@
   
   multirevs command
   
-  use "hg -v help multirevs" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
 
@@ -543,7 +537,7 @@
   
   multirevs command
   
-  use "hg help multirevs" to show the full help text
+  (use "hg multirevs -h" to show more help)
   [255]
 
 
@@ -588,7 +582,7 @@
   
       patchbomb     command to send changesets as (a series of) patch emails
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
 
   $ hg qdel
@@ -597,7 +591,7 @@
   
       mq            manage a stack of patches
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
   [255]
 
 
@@ -607,7 +601,7 @@
   
       churn         command to display statistics about repository history
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
   [255]
 
 
@@ -617,12 +611,12 @@
   $ hg help churn
   churn extension - command to display statistics about repository history
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
   $ hg help patchbomb
   patchbomb extension - command to send changesets as (a series of) patch emails
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
 
 Broken disabled extension and command:
@@ -642,7 +636,7 @@
   $ hg --config extensions.path=./path.py help broken
   broken extension - (no help text available)
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
 
   $ cat > hgext/forest.py <<EOF
--- a/tests/test-fetch.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-fetch.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "fetch=" >> $HGRCPATH
--- a/tests/test-flags.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-flags.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
   $ umask 027
 
--- a/tests/test-gendoc.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-gendoc.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,7 @@
+#require docutils
+
 Test document extraction
 
-  $ "$TESTDIR/hghave" docutils || exit 80
   $ HGENCODING=UTF-8
   $ export HGENCODING
   $ { echo C; ls "$TESTDIR/../i18n"/*.po | sort; } | while read PO; do
--- a/tests/test-getbundle.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-getbundle.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 = Test the getbundle() protocol function =
 
--- a/tests/test-globalopts.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-globalopts.t	Thu Aug 14 16:25:47 2014 -0500
@@ -357,7 +357,7 @@
    templating    Template Usage
    urls          URL Paths
   
-  use "hg -v help" to show builtin aliases and global options
+  (use "hg help -v" to show built-in aliases and global options)
 
 
 
@@ -439,7 +439,7 @@
    templating    Template Usage
    urls          URL Paths
   
-  use "hg -v help" to show builtin aliases and global options
+  (use "hg help -v" to show built-in aliases and global options)
 
 Not tested: --debugger
 
--- a/tests/test-glog.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-glog.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1645,13 +1645,28 @@
         ('symbol', 'filelog')
         ('string', 'aa'))))
 
-Test --follow on a directory
+Test --follow on a non-existent directory
 
   $ testlog -f dir
   abort: cannot follow file not in parent revision: "dir"
   abort: cannot follow file not in parent revision: "dir"
   abort: cannot follow file not in parent revision: "dir"
 
+Test --follow on a directory
+
+  $ hg up -q '.^'
+  $ testlog -f dir
+  []
+  (group
+    (func
+      ('symbol', '_matchfiles')
+      (list
+        (list
+          ('string', 'r:')
+          ('string', 'd:relpath'))
+        ('string', 'p:dir'))))
+  $ hg up -q tip
+
 Test --follow on file not in parent revision
 
   $ testlog -f a
@@ -1662,9 +1677,15 @@
 Test --follow and patterns
 
   $ testlog -f 'glob:*'
-  abort: can only follow copies/renames for explicit filenames
-  abort: can only follow copies/renames for explicit filenames
-  abort: can only follow copies/renames for explicit filenames
+  []
+  (group
+    (func
+      ('symbol', '_matchfiles')
+      (list
+        (list
+          ('string', 'r:')
+          ('string', 'd:relpath'))
+        ('string', 'p:glob:*'))))
 
 Test --follow on a single rename
 
@@ -1829,9 +1850,15 @@
           ('string', 'd:relpath'))
         ('string', 'p:a'))))
   $ testlog --removed --follow a
-  abort: can only follow copies/renames for explicit filenames
-  abort: can only follow copies/renames for explicit filenames
-  abort: can only follow copies/renames for explicit filenames
+  []
+  (group
+    (func
+      ('symbol', '_matchfiles')
+      (list
+        (list
+          ('string', 'r:')
+          ('string', 'd:relpath'))
+        ('string', 'p:a'))))
 
 Test --patch and --stat with --follow and --follow-first
 
--- a/tests/test-gpg.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-gpg.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,7 @@
+#require gpg
+
 Test the GPG extension
 
-  $ "$TESTDIR/hghave" gpg || exit 80
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
   > gpg=
--- a/tests/test-graft.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-graft.t	Thu Aug 14 16:25:47 2014 -0500
@@ -631,3 +631,50 @@
   grafting revision 13
   grafting revision 19
   merging b
+
+graft with --force (still doesn't graft merges)
+
+  $ hg graft 19 0 6
+  skipping ungraftable merge revision 6
+  skipping ancestor revision 0
+  skipping already grafted revision 19 (22 also has origin 2)
+  [255]
+  $ hg graft 19 0 6 --force
+  skipping ungraftable merge revision 6
+  grafting revision 19
+  merging b
+  grafting revision 0
+
+graft --force after backout
+
+  $ echo abc > a
+  $ hg ci -m 28
+  $ hg backout 28
+  reverting a
+  changeset 29:484c03b8dfa4 backs out changeset 28:6c56f0f7f033
+  $ hg graft 28
+  skipping ancestor revision 28
+  [255]
+  $ hg graft 28 --force
+  grafting revision 28
+  merging a
+  $ cat a
+  abc
+
+graft --continue after --force
+
+  $ hg backout 30
+  reverting a
+  changeset 31:3b96c18b7a1b backs out changeset 30:8f539994be33
+  $ hg graft 28 --force --tool internal:fail
+  grafting revision 28
+  abort: unresolved conflicts, can't continue
+  (use hg resolve and hg graft --continue)
+  [255]
+  $ hg resolve --all
+  merging a
+  (no more unresolved files)
+  $ hg graft -c
+  grafting revision 28
+  $ cat a
+  abc
--- a/tests/test-hardlinks.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hardlinks.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" hardlink || exit 80
+#require hardlink
 
   $ cat > nlinks.py <<EOF
   > import sys
--- a/tests/test-help.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-help.t	Thu Aug 14 16:25:47 2014 -0500
@@ -23,7 +23,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
 
   $ hg -q
    add           add the specified files on the next commit
@@ -122,7 +122,7 @@
    templating    Template Usage
    urls          URL Paths
   
-  use "hg -v help" to show builtin aliases and global options
+  (use "hg help -v" to show built-in aliases and global options)
 
   $ hg -q help
    add           add the specified files on the next commit
@@ -305,7 +305,7 @@
    update, up, checkout, co
                  update working directory (or switch revisions)
   
-  global options:
+  global options ([+] can be repeated):
   
    -R --repository REPO   repository root directory or name of overlay bundle
                           file
@@ -326,9 +326,7 @@
    -h --help              display help and exit
       --hidden            consider hidden changesets
   
-  [+] marked option can be specified multiple times
-  
-  use "hg help" for the full list of commands
+  (use "hg help" for the full list of commands)
 
   $ hg add -h
   hg add [OPTION]... [FILE]...
@@ -344,16 +342,14 @@
   
       Returns 0 if all files are successfully added.
   
-  options:
+  options ([+] can be repeated):
   
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
    -n --dry-run             do not perform actions, just print output
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help add" to show more complete help and the global options
+  (some details hidden, use --verbose to show complete help)
 
 Verbose help for add
 
@@ -383,16 +379,14 @@
   
       Returns 0 if all files are successfully added.
   
-  options:
+  options ([+] can be repeated):
   
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
    -n --dry-run             do not perform actions, just print output
   
-  [+] marked option can be specified multiple times
-  
-  global options:
+  global options ([+] can be repeated):
   
    -R --repository REPO   repository root directory or name of overlay bundle
                           file
@@ -412,8 +406,6 @@
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
-  
-  [+] marked option can be specified multiple times
 
 Test help option with version option
 
@@ -431,16 +423,14 @@
   
   add the specified files on the next commit
   
-  options:
+  options ([+] can be repeated):
   
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
    -n --dry-run             do not perform actions, just print output
   
-  [+] marked option can be specified multiple times
-  
-  use "hg help add" to show the full help text
+  (use "hg add -h" to show more help)
   [255]
 
 Test ambiguous command help
@@ -451,7 +441,7 @@
    add           add the specified files on the next commit
    addremove     add all new files, delete all missing files
   
-  use "hg -v help ad" to show builtin aliases and global options
+  (use "hg help -v ad" to show built-in aliases and global options)
 
 Test command without options
 
@@ -472,7 +462,7 @@
   
       Returns 0 on success, 1 if errors are encountered.
   
-  use "hg -v help verify" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg help diff
   hg diff [OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...
@@ -505,7 +495,7 @@
   
       Returns 0 on success.
   
-  options:
+  options ([+] can be repeated):
   
    -r --rev REV [+]         revision
    -c --change REV          change made by revision
@@ -523,9 +513,7 @@
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help diff" to show more complete help and the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg help status
   hg status [OPTION]... [FILE]...
@@ -567,7 +555,7 @@
   
       Returns 0 on success.
   
-  options:
+  options ([+] can be repeated):
   
    -A --all                 show status of all files
    -m --modified            show only modified files
@@ -586,9 +574,7 @@
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help status" to show more complete help and the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg -q help status
   hg status [OPTION]... [FILE]...
@@ -624,7 +610,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
   [255]
 
 
@@ -663,7 +649,7 @@
    -n --               normal desc
       --newline VALUE  line1 line2
   
-  use "hg -v help nohelp" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg help -k nohelp
   Commands:
@@ -758,7 +744,7 @@
    templating    Template Usage
    urls          URL Paths
   
-  use "hg -v help" to show builtin aliases and global options
+  (use "hg help -v" to show built-in aliases and global options)
 
 
 Test list of internal help commands
@@ -820,7 +806,7 @@
    debugwireargs
                  (no help text available)
   
-  use "hg -v help debug" to show builtin aliases and global options
+  (use "hg help -v debug" to show built-in aliases and global options)
 
 
 Test list of commands with command with no help text
@@ -832,7 +818,7 @@
   
    nohelp        (no help text available)
   
-  use "hg -v help helpext" to show builtin aliases and global options
+  (use "hg help -v helpext" to show built-in aliases and global options)
 
 
 test deprecated option is hidden in command help
@@ -843,7 +829,7 @@
   
   options:
   
-  use "hg -v help debugoptDEP" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 test deprecated option is shown with -v
   $ hg help -v debugoptDEP | grep dopt
@@ -857,9 +843,9 @@
   
   (*) (glob)
   
-  flaggor:
+  options:
   
-  *"hg -v help debugoptDEP"* (glob)
+  (some details hidden, use --verbose to show complete help)
 #endif
 
 Test commands that collide with topics (issue4240)
@@ -1030,7 +1016,7 @@
   
   This paragraph is never omitted, too (for extension)
   
-  use "hg help -v addverboseitems" to show more complete help
+  (some details hidden, use --verbose to show complete help)
   
   no commands defined
   $ hg help -v addverboseitems
@@ -1051,7 +1037,7 @@
   
       This paragraph is never omitted, too (for topic)
   
-  use "hg help -v topic-containing-verbose" to show more complete help
+  (some details hidden, use --verbose to show complete help)
   $ hg help -v topic-containing-verbose
   This is the topic to test omit indicating.
   """"""""""""""""""""""""""""""""""""""""""
@@ -1715,7 +1701,7 @@
   Returns 0 if all files are successfully added.
   </p>
   <p>
-  options:
+  options ([+] can be repeated):
   </p>
   <table>
   <tr><td>-I</td>
@@ -1732,10 +1718,7 @@
   <td>do not perform actions, just print output</td></tr>
   </table>
   <p>
-  [+] marked option can be specified multiple times
-  </p>
-  <p>
-  global options:
+  global options ([+] can be repeated):
   </p>
   <table>
   <tr><td>-R</td>
@@ -1787,9 +1770,6 @@
   <td>--hidden</td>
   <td>consider hidden changesets</td></tr>
   </table>
-  <p>
-  [+] marked option can be specified multiple times
-  </p>
   
   </div>
   </div>
@@ -1911,7 +1891,7 @@
   Returns 0 on success, 1 if any warnings encountered.
   </p>
   <p>
-  options:
+  options ([+] can be repeated):
   </p>
   <table>
   <tr><td>-A</td>
@@ -1928,10 +1908,7 @@
   <td>exclude names matching the given patterns</td></tr>
   </table>
   <p>
-  [+] marked option can be specified multiple times
-  </p>
-  <p>
-  global options:
+  global options ([+] can be repeated):
   </p>
   <table>
   <tr><td>-R</td>
@@ -1983,9 +1960,6 @@
   <td>--hidden</td>
   <td>consider hidden changesets</td></tr>
   </table>
-  <p>
-  [+] marked option can be specified multiple times
-  </p>
   
   </div>
   </div>
--- a/tests/test-hgweb-commands.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hgweb-commands.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 An attempt at more fully testing the hgweb web interface.
 The following things are tested elsewhere and are therefore omitted:
--- a/tests/test-hgweb-descend-empties.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hgweb-descend-empties.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 Test chains of near empty directories, terminating 3 different ways:
 - a1: file at level 4 (deepest)
--- a/tests/test-hgweb-diffs.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hgweb-diffs.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 setting up repo
 
--- a/tests/test-hgweb-empty.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hgweb-empty.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 Some tests for hgweb in an empty repository
 
--- a/tests/test-hgweb-filelog.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hgweb-filelog.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
--- a/tests/test-hgweb-raw.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hgweb-raw.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 Test raw style of hgweb
 
--- a/tests/test-hgweb-removed.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hgweb-removed.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 setting up repo
 
--- a/tests/test-hgweb.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hgweb.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 Some tests for hgweb. Tests static files, plain files and different 404's.
 
--- a/tests/test-hgwebdir.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hgwebdir.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 hide outer repo and work in dir without '.hg'
   $ hg init
--- a/tests/test-hgwebdirsym.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hgwebdirsym.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,6 @@
-Tests whether or not hgwebdir properly handles various symlink topologies.
+#require serve symlink
 
-  $ "$TESTDIR/hghave" serve symlink || exit 80
+Tests whether or not hgwebdir properly handles various symlink topologies.
 
 hide outer repo
   $ hg init
--- a/tests/test-highlight.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-highlight.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,5 @@
+#require pygments serve
 
-  $ "$TESTDIR/hghave" pygments serve || exit 80
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
   > highlight =
--- a/tests/test-histedit-arguments.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-histedit-arguments.t	Thu Aug 14 16:25:47 2014 -0500
@@ -57,6 +57,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -255,6 +256,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
--- a/tests/test-histedit-bookmark-motion.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-histedit-bookmark-motion.t	Thu Aug 14 16:25:47 2014 -0500
@@ -73,6 +73,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -133,6 +134,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
--- a/tests/test-histedit-commute.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-histedit-commute.t	Thu Aug 14 16:25:47 2014 -0500
@@ -67,6 +67,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -344,6 +345,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
--- a/tests/test-histedit-fold-non-commute.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-histedit-fold-non-commute.t	Thu Aug 14 16:25:47 2014 -0500
@@ -183,3 +183,165 @@
   f
 
   $ cd ..
+
+Repeat test using "roll", not "fold". "roll" folds in changes but drops message
+
+  $ initrepo r2
+  $ cd r2
+
+Initial generation of the command files
+
+  $ EDITED="$TESTTMP/editedhistory.2"
+  $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
+  $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
+  $ hg log --template 'roll {node|short} {rev} {desc}\n' -r 7 >> $EDITED
+  $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
+  $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
+  $ cat $EDITED
+  pick 65a9a84f33fd 3 c
+  pick 00f1c5383965 4 d
+  roll 39522b764e3d 7 does not commute with e
+  pick 7b4e2f4b7bcd 5 e
+  pick 500cac37a696 6 f
+
+log before edit
+  $ hg log --graph
+  @  changeset:   7:39522b764e3d
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     does not commute with e
+  |
+  o  changeset:   6:500cac37a696
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     f
+  |
+  o  changeset:   5:7b4e2f4b7bcd
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     e
+  |
+  o  changeset:   4:00f1c5383965
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     d
+  |
+  o  changeset:   3:65a9a84f33fd
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     c
+  |
+  o  changeset:   2:da6535b52e45
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     b
+  |
+  o  changeset:   1:c1f09da44841
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     a
+  |
+  o  changeset:   0:1715188a53c7
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     Initial commit
+  
+
+edit the history
+  $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
+  Fix up the change and run hg histedit --continue
+
+fix up
+  $ echo 'I can haz no commute' > e
+  $ hg resolve --mark e
+  (no more unresolved files)
+  $ hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
+  Fix up the change and run hg histedit --continue
+
+just continue this time
+  $ hg revert -r 'p1()' e
+  $ hg resolve --mark e
+  (no more unresolved files)
+  $ hg histedit --continue 2>&1 | fixbundle
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+log after edit
+  $ hg log --graph
+  @  changeset:   5:e7c4f5d4eb75
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     f
+  |
+  o  changeset:   4:803d1bb561fc
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     d
+  |
+  o  changeset:   3:65a9a84f33fd
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     c
+  |
+  o  changeset:   2:da6535b52e45
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     b
+  |
+  o  changeset:   1:c1f09da44841
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     a
+  |
+  o  changeset:   0:1715188a53c7
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     Initial commit
+  
+
+contents of e
+  $ hg cat e
+  I can haz no commute
+
+manifest
+  $ hg manifest
+  a
+  b
+  c
+  d
+  e
+  f
+
+description is taken from rollup target commit
+
+  $ hg log --debug --rev 4
+  changeset:   4:803d1bb561fceac3129ec778db9da249a3106fc3
+  phase:       draft
+  parent:      3:65a9a84f33fdeb1ad5679b3941ec885d2b24027b
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    4:b068a323d969f22af1296ec6a5ea9384cef437ac
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  files:       d e
+  extra:       branch=default
+  extra:       histedit_source=00f1c53839651fa5c76d423606811ea5455a79d0,39522b764e3d26103f08bd1fa2ccd3e3d7dbcf4e
+  description:
+  d
+  
+  
+
+done with repo r2
+
+  $ cd ..
--- a/tests/test-histedit-fold.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-histedit-fold.t	Thu Aug 14 16:25:47 2014 -0500
@@ -105,6 +105,50 @@
   
   
 
+rollup will fold without preserving the folded commit's message
+
+  $ hg histedit d2ae7f538514 --commands - 2>&1 <<EOF | fixbundle
+  > pick d2ae7f538514 b
+  > roll ee283cb5f2d5 e
+  > pick 6de59d13424a f
+  > pick 9c277da72c9b d
+  > EOF
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+log after edit
+  $ hg logt --graph
+  @  3:c4a9eb7989fc d
+  |
+  o  2:8e03a72b6f83 f
+  |
+  o  1:391ee782c689 b
+  |
+  o  0:cb9a9f314b8b a
+  
+
+description is taken from rollup target commit
+
+  $ hg log --debug --rev 1
+  changeset:   1:391ee782c68930be438ccf4c6a403daedbfbffa5
+  phase:       draft
+  parent:      0:cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    1:b5e112a3a8354e269b1524729f0918662d847c38
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  files+:      b e
+  extra:       branch=default
+  extra:       histedit_source=d2ae7f538514cd87c17547b0de4cea71fe1af9fb,ee283cb5f2d5955443f23a27b697a04339e9a39a
+  description:
+  b
+  
+  
+
 check saving last-message.txt
 
   $ cat > $TESTTMP/abortfolding.py <<EOF
@@ -128,9 +172,9 @@
   > EOF
 
   $ rm -f .hg/last-message.txt
-  $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 6de59d13424a --commands - 2>&1 <<EOF | fixbundle
-  > pick 6de59d13424a f
-  > fold 9c277da72c9b d
+  $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 8e03a72b6f83 --commands - 2>&1 <<EOF | fixbundle
+  > pick 8e03a72b6f83 f
+  > fold c4a9eb7989fc d
   > EOF
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   allow non-folding commit
--- a/tests/test-histedit-obsolete.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-histedit-obsolete.t	Thu Aug 14 16:25:47 2014 -0500
@@ -57,6 +57,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
--- a/tests/test-histedit-outgoing.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-histedit-outgoing.t	Thu Aug 14 16:25:47 2014 -0500
@@ -49,6 +49,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -80,6 +81,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -103,6 +105,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
--- a/tests/test-hook.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hook.t	Thu Aug 14 16:25:47 2014 -0500
@@ -210,6 +210,7 @@
   $ hg push -B baz ../a
   pushing to ../a
   searching for changes
+  listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
   no changes found
   listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
   listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
--- a/tests/test-http-branchmap.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-http-branchmap.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ hgserve() {
   >     hg serve -a localhost -p $HGPORT1 -d --pid-file=hg.pid \
--- a/tests/test-http-clone-r.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-http-clone-r.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 creating 'remote
 
--- a/tests/test-http-proxy.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-http-proxy.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init a
   $ cd a
--- a/tests/test-http.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-http.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
@@ -261,9 +261,10 @@
   "GET /?cmd=listkeys HTTP/1.1" 403 - x-hgarg-1:namespace=namespaces
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=phases
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases
   "GET /?cmd=branchmap HTTP/1.1" 200 -
   "GET /?cmd=branchmap HTTP/1.1" 200 -
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
   "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+5eb5abfefeea63c80dd7553bcc3783f37e0c5524
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases
--- a/tests/test-https.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-https.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,6 @@
-Proper https client requires the built-in ssl from Python 2.6.
+#require serve ssl
 
-  $ "$TESTDIR/hghave" serve ssl || exit 80
+Proper https client requires the built-in ssl from Python 2.6.
 
 Certificates created with:
  printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
--- a/tests/test-hup.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-hup.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,7 @@
+#require serve fifo
+
 Test hangup signal in the middle of transaction
 
-  $ "$TESTDIR/hghave" serve fifo || exit 80
   $ hg init
   $ mkfifo p
   $ hg serve --stdio < p 1>out 2>&1 &
--- a/tests/test-i18n.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-i18n.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,6 @@
-Translations are optional:
+#require gettext
 
-  $ "$TESTDIR/hghave" gettext || exit 80
+(Translations are optional)
 
 #if no-outer-repo
 
--- a/tests/test-identify.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-identify.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 #if no-outer-repo
 
--- a/tests/test-incoming-outgoing.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-incoming-outgoing.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
--- a/tests/test-inherit-mode.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-inherit-mode.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,7 +1,6 @@
-test that new files created in .hg inherit the permissions from .hg/store
+#require unix-permissions
 
-
-  $ "$TESTDIR/hghave" unix-permissions || exit 80
+test that new files created in .hg inherit the permissions from .hg/store
 
   $ mkdir dir
 
@@ -121,7 +120,6 @@
   00660 ../push/.hg/store/data/dir/bar.i
   00660 ../push/.hg/store/data/foo.i
   00660 ../push/.hg/store/fncache
-  00660 ../push/.hg/store/phaseroots
   00660 ../push/.hg/store/undo
   00660 ../push/.hg/store/undo.phaseroots
   00660 ../push/.hg/undo.bookmarks
--- a/tests/test-issue1438.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-issue1438.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,6 +1,6 @@
-http://mercurial.selenic.com/bts/issue1438
+#require symlink
 
-  $ "$TESTDIR/hghave" symlink || exit 80
+http://mercurial.selenic.com/bts/issue1438
 
   $ hg init
 
--- a/tests/test-issue1802.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-issue1802.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
 Create extension that can disable exec checks:
 
--- a/tests/test-known.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-known.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 = Test the known() protocol function =
 
--- a/tests/test-largefiles-update.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-largefiles-update.t	Thu Aug 14 16:25:47 2014 -0500
@@ -99,4 +99,72 @@
   $ cat .hglf/large1
   58e24f733a964da346e2407a2bee99d9001184f5
 
+Test that "hg rollback" restores status of largefiles correctly
+
+  $ hg update -C -q
+  $ hg remove large1
+  $ hg forget large2
+  $ echo largeX > largeX
+  $ hg add --large largeX
+  $ hg commit -m 'will be rollback-ed soon'
+  $ echo largeY > largeY
+  $ hg add --large largeY
+  $ hg status -A large1
+  large1: No such file or directory
+  $ hg status -A large2
+  ? large2
+  $ hg status -A largeX
+  C largeX
+  $ hg status -A largeY
+  A largeY
+  $ hg rollback
+  repository tip rolled back to revision 3 (undo commit)
+  working directory now based on revision 3
+  $ hg status -A large1
+  R large1
+  $ hg status -A large2
+  R large2
+  $ hg status -A largeX
+  A largeX
+  $ hg status -A largeY
+  ? largeY
+
+Test that "hg status" shows status of largefiles correctly just after
+automated commit like rebase/transplant
+
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > rebase =
+  > strip =
+  > transplant =
+  > EOF
+  $ hg update -q -C 1
+  $ hg remove large1
+  $ echo largeX > largeX
+  $ hg add --large largeX
+  $ hg commit -m '#4'
+
+  $ hg rebase -s 1 -d 2 --keep
+  $ hg status -A large1
+  large1: No such file or directory
+  $ hg status -A largeX
+  C largeX
+  $ hg strip -q 5
+
+  $ hg update -q -C 2
+  $ hg transplant -q 1 4
+  $ hg status -A large1
+  large1: No such file or directory
+  $ hg status -A largeX
+  C largeX
+  $ hg strip -q 5
+
+  $ hg update -q -C 2
+  $ hg transplant -q --merge 1 --merge 4
+  $ hg status -A large1
+  large1: No such file or directory
+  $ hg status -A largeX
+  C largeX
+  $ hg strip -q 5
+
   $ cd ..
--- a/tests/test-lock-badness.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-lock-badness.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-#if unix-permissions no-root no-windows
+#require unix-permissions no-root no-windows
 
 Prepare
 
@@ -39,4 +39,3 @@
   [255]
 
   $ chmod 700 a/.hg/store
-#endif
--- a/tests/test-log.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-log.t	Thu Aug 14 16:25:47 2014 -0500
@@ -78,12 +78,52 @@
   summary:     c
   
 
--f, directory
+-f, non-existent directory
 
   $ hg log -f dir
   abort: cannot follow file not in parent revision: "dir"
   [255]
 
+-f, directory
+
+  $ hg up -q 3
+  $ hg log -f dir
+  changeset:   2:f8954cd4dc1f
+  user:        test
+  date:        Thu Jan 01 00:00:03 1970 +0000
+  summary:     c
+  
+-f, directory with --patch
+
+  $ hg log -f dir -p
+  changeset:   2:f8954cd4dc1f
+  user:        test
+  date:        Thu Jan 01 00:00:03 1970 +0000
+  summary:     c
+  
+  diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
+  --- /dev/null* (glob)
+  +++ b/dir/b* (glob)
+  @@ -0,0 +1,1 @@
+  +a
+  
+
+-f, pattern
+
+  $ hg log -f -I 'dir**' -p
+  changeset:   2:f8954cd4dc1f
+  user:        test
+  date:        Thu Jan 01 00:00:03 1970 +0000
+  summary:     c
+  
+  diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
+  --- /dev/null* (glob)
+  +++ b/dir/b* (glob)
+  @@ -0,0 +1,1 @@
+  +a
+  
+  $ hg up -q 4
+
 -f, a wrong style
 
   $ hg log -f -l1 --style something
--- a/tests/test-merge-tools.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-merge-tools.t	Thu Aug 14 16:25:47 2014 -0500
@@ -30,6 +30,14 @@
   $ echo "revision 3" >> f
   $ hg commit -Am "revision 3"
   created new head
+
+revision 4 - hard to merge
+
+  $ hg update 0 > /dev/null
+  $ echo "revision 4" > f
+  $ hg commit -Am "revision 4"
+  created new head
+
   $ echo "[merge-tools]" > .hg/hgrc
 
   $ beforemerge() {
@@ -701,6 +709,77 @@
   # hg stat
   M f
 
+premerge=keep keeps conflict markers in:
+
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 4 --config merge-tools.true.premerge=keep
+  merging f
+  <<<<<<< local: ef83787e2614  - test: revision 1
+  revision 1
+  space
+  =======
+  revision 4
+  >>>>>>> other: 81448d39c9a0 - test: revision 4
+  revision 0
+  space
+  revision 4
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ aftermerge
+  # cat f
+  <<<<<<< local: ef83787e2614  - test: revision 1
+  revision 1
+  space
+  =======
+  revision 4
+  >>>>>>> other: 81448d39c9a0 - test: revision 4
+  # hg stat
+  M f
+
+premerge=keep-merge3 keeps conflict markers with base content:
+
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 4 --config merge-tools.true.premerge=keep-merge3
+  merging f
+  <<<<<<< local: ef83787e2614  - test: revision 1
+  revision 1
+  space
+  ||||||| base
+  revision 0
+  space
+  =======
+  revision 4
+  >>>>>>> other: 81448d39c9a0 - test: revision 4
+  revision 0
+  space
+  revision 4
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ aftermerge
+  # cat f
+  <<<<<<< local: ef83787e2614  - test: revision 1
+  revision 1
+  space
+  ||||||| base
+  revision 0
+  space
+  =======
+  revision 4
+  >>>>>>> other: 81448d39c9a0 - test: revision 4
+  # hg stat
+  M f
+
+
 Tool execution
 
 set tools.args explicit to include $base $local $other $output:
@@ -832,17 +911,17 @@
   true.priority=1
   true.executable=cat
   # hg update -C 1
-  $ echo "revision 4" > '"; exit 1; echo "'
-  $ hg commit -Am "revision 4"
-  adding "; exit 1; echo "
-  warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
-  $ hg update -C 1 > /dev/null
   $ echo "revision 5" > '"; exit 1; echo "'
   $ hg commit -Am "revision 5"
   adding "; exit 1; echo "
   warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
+  $ hg update -C 1 > /dev/null
+  $ echo "revision 6" > '"; exit 1; echo "'
+  $ hg commit -Am "revision 6"
+  adding "; exit 1; echo "
+  warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
   created new head
-  $ hg merge --config merge-tools.true.executable="true" -r 4
+  $ hg merge --config merge-tools.true.executable="true" -r 5
   merging "; exit 1; echo "
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
--- a/tests/test-merge-types.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-merge-types.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" symlink execbit || exit 80
+#require symlink execbit
 
   $ tellmeabout() {
   > if [ -h $1 ]; then
--- a/tests/test-mq-qclone-http.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-mq-qclone-http.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 hide outer repo
   $ hg init
--- a/tests/test-mq-qimport.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-mq-qimport.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ cat > writelines.py <<EOF
   > import sys
--- a/tests/test-mq-qrefresh-interactive.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-mq-qrefresh-interactive.t	Thu Aug 14 16:25:47 2014 -0500
@@ -29,7 +29,7 @@
   
       Returns 0 on success.
   
-  options:
+  options ([+] can be repeated):
   
    -e --edit                invoke editor on commit messages
    -g --git                 use git extended diff format
@@ -44,9 +44,7 @@
    -m --message TEXT        use text as commit message
    -l --logfile FILE        read commit message from file
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help qrefresh" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 help qrefresh (record)
 
@@ -73,7 +71,7 @@
   
       Returns 0 on success.
   
-  options:
+  options ([+] can be repeated):
   
    -e --edit                invoke editor on commit messages
    -g --git                 use git extended diff format
@@ -89,9 +87,7 @@
    -l --logfile FILE        read commit message from file
    -i --interactive         interactively select changes to refresh
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help qrefresh" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg init a
   $ cd a
--- a/tests/test-mq-qrefresh-replace-log-message.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-mq-qrefresh-replace-log-message.t	Thu Aug 14 16:25:47 2014 -0500
@@ -32,17 +32,19 @@
 
   $ cat >> .hg/hgrc <<EOF
   > [committemplate]
+  > listupfiles = {file_adds %
+  >    "HG: added {file}\n"     }{file_mods %
+  >    "HG: changed {file}\n"   }{file_dels %
+  >    "HG: removed {file}\n"   }{if(files, "",
+  >    "HG: no files changed\n")}
+  > 
   > changeset = HG: this is customized commit template
   >     {desc}\n\n
   >     HG: Enter commit message.  Lines beginning with 'HG:' are removed.
   >     HG: {extramsg}
   >     HG: --
   >     HG: user: {author}
-  >     HG: branch '{branch}'\n{file_adds %
-  >    "HG: added {file}\n"     }{file_mods %
-  >    "HG: changed {file}\n"   }{file_dels %
-  >    "HG: removed {file}\n"   }{if(files, "",
-  >    "HG: no files changed\n")}
+  >     HG: branch '{branch}'\n{listupfiles}
   > EOF
 
   $ echo bbbb > file
--- a/tests/test-mq-subrepo-svn.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-mq-subrepo-svn.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" svn13 || exit 80
+#require svn13
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "mq=" >> $HGRCPATH
--- a/tests/test-mq-symlinks.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-mq-symlinks.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" symlink || exit 80
+#require symlink
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "mq=" >> $HGRCPATH
--- a/tests/test-mq.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-mq.t	Thu Aug 14 16:25:47 2014 -0500
@@ -95,7 +95,7 @@
    qtop          print the name of the current patch
    qunapplied    print the patches not yet applied
   
-  use "hg -v help mq" to show builtin aliases and global options
+  (use "hg help -v mq" to show built-in aliases and global options)
 
   $ hg init a
   $ cd a
--- a/tests/test-newcgi.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-newcgi.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate web paths as if they were file paths
+#require no-msys # MSYS will translate web paths as if they were file paths
 
 This tests if CGI files from after d0db3462d568 but
 before d74fc8dec2b4 still work.
--- a/tests/test-newercgi.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-newercgi.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate web paths as if they were file paths
+#require no-msys # MSYS will translate web paths as if they were file paths
 
 This is a rudimentary test of the CGI files as of d74fc8dec2b4.
 
--- a/tests/test-no-symlinks.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-no-symlinks.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-symlink || exit 80
+#require no-symlink
 
 # The following script was used to create the bundle:
 #
--- a/tests/test-obsolete.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-obsolete.t	Thu Aug 14 16:25:47 2014 -0500
@@ -2,6 +2,8 @@
   > [phases]
   > # public changeset are not obsolete
   > publish=false
+  > [ui]
+  > logtemplate="{rev}:{node|short} ({phase}) [{tags} {bookmarks}] {desc|firstline}\n"
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -58,11 +60,7 @@
 
   $ hg up null --quiet # having 0 as parent prevents it to be hidden
   $ hg tip
-  changeset:   -1:000000000000
-  tag:         tip
-  user:        
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  
+  -1:000000000000 (public) [tip ] 
   $ hg up --hidden tip --quiet
   $ cd ..
 
@@ -125,59 +123,22 @@
 Check that graphlog detect that a changeset is obsolete:
 
   $ hg log -G
-  @  changeset:   5:5601fb93a350
-  |  tag:         tip
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add new_3_c
+  @  5:5601fb93a350 (draft) [tip ] add new_3_c
   |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
+  o  1:7c3bad9141dc (draft) [ ] add b
   |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (draft) [ ] add a
   
 
 check that heads does not report them
 
   $ hg heads
-  changeset:   5:5601fb93a350
-  tag:         tip
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_3_c
-  
+  5:5601fb93a350 (draft) [tip ] add new_3_c
   $ hg heads --hidden
-  changeset:   5:5601fb93a350
-  tag:         tip
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_3_c
-  
-  changeset:   4:ca819180edb9
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_2_c
-  
-  changeset:   3:cdbce2fbb163
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_c
-  
-  changeset:   2:245bde4270cd
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_c
-  
+  5:5601fb93a350 (draft) [tip ] add new_3_c
+  4:ca819180edb9 (draft) [ ] add new_2_c
+  3:cdbce2fbb163 (draft) [ ] add new_c
+  2:245bde4270cd (draft) [ ] add original_c
 
 
 check that summary does not report them
@@ -204,13 +165,7 @@
 check that various commands work well with filtering
 
   $ hg tip
-  changeset:   5:5601fb93a350
-  tag:         tip
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_3_c
-  
+  5:5601fb93a350 (draft) [tip ] add new_3_c
   $ hg log -r 6
   abort: unknown revision '6'!
   [255]
@@ -222,27 +177,13 @@
 
   $ hg --hidden phase --public 2
   $ hg log -G
-  @  changeset:   5:5601fb93a350
-  |  tag:         tip
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add new_3_c
+  @  5:5601fb93a350 (draft) [tip ] add new_3_c
   |
-  | o  changeset:   2:245bde4270cd
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add original_c
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
 
 And that bumped changeset are detected
@@ -253,13 +194,7 @@
 the public changeset
 
   $ hg log --hidden -r 'bumped()'
-  changeset:   5:5601fb93a350
-  tag:         tip
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_3_c
-  
+  5:5601fb93a350 (draft) [tip ] add new_3_c
 
 And that we can't push bumped changeset
 
@@ -289,27 +224,13 @@
   $ hg debugobsolete -d '1338 0' --flags 1 `getid new_3_c` `getid n3w_3_c`
   $ hg log -r 'bumped()'
   $ hg log -G
-  @  changeset:   6:6f9641995072
-  |  tag:         tip
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add n3w_3_c
+  @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | o  changeset:   2:245bde4270cd
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add original_c
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
 
 
@@ -328,28 +249,10 @@
   $ cd tmpc
   $ hg incoming ../tmpb
   comparing with ../tmpb
-  changeset:   0:1f0dee641bb7
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add a
-  
-  changeset:   1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add b
-  
-  changeset:   2:245bde4270cd
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_c
-  
-  changeset:   6:6f9641995072
-  tag:         tip
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add n3w_3_c
-  
+  0:1f0dee641bb7 (public) [ ] add a
+  1:7c3bad9141dc (public) [ ] add b
+  2:245bde4270cd (public) [ ] add original_c
+  6:6f9641995072 (draft) [tip ] add n3w_3_c
 
 Try to pull markers
 (extinct changeset are excluded but marker are pushed)
@@ -414,6 +317,7 @@
   $ hg init empty
   $ hg --config extensions.debugkeys=debugkeys.py -R empty push tmpd
   pushing to tmpd
+  listkeys phases
   no changes found
   listkeys phases
   listkeys bookmarks
@@ -426,45 +330,19 @@
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R clone-dest log -G --hidden
-  @  changeset:   6:6f9641995072
-  |  tag:         tip
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add n3w_3_c
-  |
-  | x  changeset:   5:5601fb93a350
-  |/   parent:      1:7c3bad9141dc
-  |    user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add new_3_c
-  |
-  | x  changeset:   4:ca819180edb9
-  |/   parent:      1:7c3bad9141dc
-  |    user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add new_2_c
+  @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  changeset:   3:cdbce2fbb163
-  |/   parent:      1:7c3bad9141dc
-  |    user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add new_c
+  | x  5:5601fb93a350 (draft) [ ] add new_3_c
+  |/
+  | x  4:ca819180edb9 (draft) [ ] add new_2_c
+  |/
+  | x  3:cdbce2fbb163 (draft) [ ] add new_c
+  |/
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  | o  changeset:   2:245bde4270cd
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add original_c
-  |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
   $ hg -R clone-dest debugobsolete
   245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
@@ -519,27 +397,13 @@
 
 
   $ hg log -G
-  o  changeset:   3:6f9641995072
-  |  tag:         tip
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add n3w_3_c
+  o  3:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | o  changeset:   2:245bde4270cd
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add original_c
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
   $ hg up 'desc("n3w_3_c")'
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -547,38 +411,17 @@
   $ mkcommit original_e
   $ hg debugobsolete `getid original_d` -d '0 0'
   $ hg log -r 'obsolete()'
-  changeset:   4:94b33453f93b
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_d
-  
+  4:94b33453f93b (draft) [ ] add original_d
   $ hg log -G -r '::unstable()'
-  @  changeset:   5:cda648ca50f5
-  |  tag:         tip
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add original_e
+  @  5:cda648ca50f5 (draft) [tip ] add original_e
+  |
+  x  4:94b33453f93b (draft) [ ] add original_d
   |
-  x  changeset:   4:94b33453f93b
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add original_d
+  o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
-  o  changeset:   3:6f9641995072
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add n3w_3_c
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
 
 refuse to push obsolete changeset
@@ -607,38 +450,12 @@
   $ hg out  ../tmpf
   comparing with ../tmpf
   searching for changes
-  changeset:   0:1f0dee641bb7
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add a
-  
-  changeset:   1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add b
-  
-  changeset:   2:245bde4270cd
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_c
-  
-  changeset:   3:6f9641995072
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add n3w_3_c
-  
-  changeset:   4:94b33453f93b
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_d
-  
-  changeset:   5:cda648ca50f5
-  tag:         tip
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_e
-  
+  0:1f0dee641bb7 (public) [ ] add a
+  1:7c3bad9141dc (public) [ ] add b
+  2:245bde4270cd (public) [ ] add original_c
+  3:6f9641995072 (draft) [ ] add n3w_3_c
+  4:94b33453f93b (draft) [ ] add original_d
+  5:cda648ca50f5 (draft) [tip ] add original_e
   $ hg push ../tmpf -f # -f because be push unstable too
   pushing to ../tmpf
   searching for changes
@@ -658,37 +475,17 @@
 Do not warn about new head when the new head is a successors of a remote one
 
   $ hg log -G
-  @  changeset:   5:cda648ca50f5
-  |  tag:         tip
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add original_e
+  @  5:cda648ca50f5 (draft) [tip ] add original_e
   |
-  x  changeset:   4:94b33453f93b
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add original_d
+  x  4:94b33453f93b (draft) [ ] add original_d
+  |
+  o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
-  o  changeset:   3:6f9641995072
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add n3w_3_c
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  | o  changeset:   2:245bde4270cd
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add original_c
-  |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
   $ hg up -q 'desc(n3w_3_c)'
   $ mkcommit obsolete_e
@@ -697,13 +494,7 @@
   $ hg outgoing ../tmpf # parasite hg outgoing testin
   comparing with ../tmpf
   searching for changes
-  changeset:   6:3de5eca88c00
-  tag:         tip
-  parent:      3:6f9641995072
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add obsolete_e
-  
+  6:3de5eca88c00 (draft) [tip ] add obsolete_e
   $ hg push ../tmpf
   pushing to ../tmpf
   searching for changes
@@ -773,13 +564,7 @@
   $ echo "obs=!" >> $HGRCPATH
   $ hg log -r tip
   obsolete feature not enabled but 68 markers found!
-  changeset:   68:c15e9edfca13
-  tag:         tip
-  parent:      7:50c51b361e60
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add celestine
-  
+  68:c15e9edfca13 (draft) [tip ] add celestine
 
 reenable for later test
 
@@ -805,40 +590,19 @@
   $ hg ci --amend
   $ cd ../other-issue3805
   $ hg log -G
-  @  changeset:   0:193e9254ce7e
-     tag:         tip
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     A
+  @  0:193e9254ce7e (draft) [tip ] A
   
   $ hg log -G -R ../repo-issue3805
-  @  changeset:   2:3816541e5485
-     tag:         tip
-     parent:      -1:000000000000
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     A
+  @  2:3816541e5485 (draft) [tip ] A
   
   $ hg incoming
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
-  changeset:   2:3816541e5485
-  tag:         tip
-  parent:      -1:000000000000
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     A
-  
+  2:3816541e5485 (draft) [tip ] A
   $ hg incoming --bundle ../issue3805.hg
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
-  changeset:   2:3816541e5485
-  tag:         tip
-  parent:      -1:000000000000
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     A
-  
+  2:3816541e5485 (draft) [tip ] A
   $ hg outgoing
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
@@ -853,13 +617,7 @@
   $ hg incoming http://localhost:$HGPORT
   comparing with http://localhost:$HGPORT/
   searching for changes
-  changeset:   1:3816541e5485
-  tag:         tip
-  parent:      -1:000000000000
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     A
-  
+  1:3816541e5485 (public) [tip ] A
   $ hg outgoing http://localhost:$HGPORT
   comparing with http://localhost:$HGPORT/
   searching for changes
@@ -894,18 +652,9 @@
 
   $ hg tag -l visible -r 0 --hidden
   $ hg log -G
-  @  changeset:   2:3816541e5485
-     tag:         tip
-     parent:      -1:000000000000
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     A
+  @  2:3816541e5485 (draft) [tip ] A
   
-  x  changeset:   0:193e9254ce7e
-     tag:         visible
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     A
+  x  0:193e9254ce7e (draft) [visible ] A
   
 Test that removing a local tag does not cause some commands to fail
 
--- a/tests/test-oldcgi.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-oldcgi.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate web paths as if they were file paths
+#require no-msys # MSYS will translate web paths as if they were file paths
 
 This tests if CGI files from before d0db3462d568 still work.
 
--- a/tests/test-patchbomb.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-patchbomb.t	Thu Aug 14 16:25:47 2014 -0500
@@ -8,6 +8,21 @@
 --===+[0-9]+=+--$ -> --===*=-- (glob)
 --===+[0-9]+=+$ -> --===*= (glob)
 
+  $ cat > prune-blank-after-boundary.py <<EOF
+  > import sys
+  > skipblank = False
+  > trim = lambda x: x.strip(' \r\n')
+  > for l in sys.stdin:
+  >     if trim(l).endswith('=--') or trim(l).endswith('=='):
+  >         skipblank = True
+  >         print l,
+  >         continue
+  >     if not trim(l) and skipblank:
+  >         continue
+  >     skipblank = False
+  >     print l,
+  > EOF
+  $ FILTERBOUNDARY="python `pwd`/prune-blank-after-boundary.py"
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "patchbomb=" >> $HGRCPATH
 
@@ -214,7 +229,7 @@
 
 test bundle and description:
   $ hg email --date '1970-1-1 0:3' -n -f quux -t foo \
-  >  -c bar -s test -r tip -b --desc description
+  >  -c bar -s test -r tip -b --desc description | $FILTERBOUNDARY
   searching for changes
   1 changesets found
   
@@ -689,7 +704,7 @@
   
 
 test inline for single patch:
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 2
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 2 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -732,7 +747,7 @@
 
 
 test inline for single patch (quoted-printable):
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 4
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 4 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -791,7 +806,7 @@
 
 test inline for multiple patches:
   $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i \
-  >  -r 0:1 -r 4
+  >  -r 0:1 -r 4 | $FILTERBOUNDARY
   this patch series consists of 3 patches.
   
   
@@ -943,7 +958,7 @@
   --===*=-- (glob)
 
 test attach for single patch:
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a -r 2
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a -r 2 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -994,7 +1009,7 @@
   --===*=-- (glob)
 
 test attach for single patch (quoted-printable):
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a -r 4
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a -r 4 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -1061,7 +1076,7 @@
   --===*=-- (glob)
 
 test attach and body for single patch:
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a --body -r 2
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a --body -r 2 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -1123,7 +1138,7 @@
 
 test attach for multiple patches:
   $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a \
-  >  -r 0:1 -r 4
+  >  -r 0:1 -r 4 | $FILTERBOUNDARY
   this patch series consists of 3 patches.
   
   
@@ -1579,7 +1594,8 @@
   $ hg tag -r2 two two.diff
 
 test inline for single named patch:
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 2
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i \
+  >   -r 2 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -1621,7 +1637,8 @@
   --===*=-- (glob)
 
 test inline for multiple named/unnamed patches:
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 0:1
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i \
+  >    -r 0:1 | $FILTERBOUNDARY
   this patch series consists of 2 patches.
   
   
@@ -1927,7 +1944,7 @@
   $ hg up -qr1
   $ echo dirt > a
   $ hg email --date '1970-1-1 0:1' -n --flag fooFlag -f quux -t foo -c bar -s test \
-  >  -r 2
+  >  -r 2 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
--- a/tests/test-permissions.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-permissions.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-#if unix-permissions no-root
+#require unix-permissions no-root
 
   $ hg init t
   $ cd t
@@ -70,5 +70,3 @@
   $ chmod +rx dir
 
   $ cd ..
-
-#endif
--- a/tests/test-phases-exchange.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-phases-exchange.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ hgph() { hg log -G --template "{rev} {phase} {desc} - {node|short}\n" $*; }
 
--- a/tests/test-pull-http.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-pull-http.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ hg init test
   $ cd test
--- a/tests/test-pull-permission.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-pull-permission.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-#if unix-permissions no-root
+#require unix-permissions no-root
 
   $ hg init a
   $ cd a
@@ -30,5 +30,3 @@
   1 files, 1 changesets, 1 total revisions
 
   $ cd ..
-
-#endif
--- a/tests/test-pull.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-pull.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
--- a/tests/test-push-cgi.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-push-cgi.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate web paths as if they were file paths
+#require no-msys # MSYS will translate web paths as if they were file paths
 
 This is a test of the push wire protocol over CGI-based hgweb.
 
--- a/tests/test-push-http.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-push-http.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ hg init test
   $ cd test
--- a/tests/test-push-warn.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-push-warn.t	Thu Aug 14 16:25:47 2014 -0500
@@ -35,6 +35,7 @@
   searching: 2 queries
   query 2; still undecided: 1, sample size is: 1
   2 total queries
+  listing keys for "phases"
   listing keys for "bookmarks"
   remote has heads on branch 'default' that are not known locally: 1c9246a22a0a
   new remote heads on branch 'default':
--- a/tests/test-qrecord.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-qrecord.t	Thu Aug 14 16:25:47 2014 -0500
@@ -9,7 +9,7 @@
   record extension - commands to interactively select changes for
   commit/qrefresh
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
 help qrecord (no record)
 
@@ -18,7 +18,7 @@
   
       record        commands to interactively select changes for commit/qrefresh
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
   $ echo "[extensions]"     >> $HGRCPATH
   $ echo "record="          >> $HGRCPATH
@@ -54,7 +54,7 @@
   
       This command is not available when committing a merge.
   
-  options:
+  options ([+] can be repeated):
   
    -A --addremove           mark new/missing files as added/removed before
                             committing
@@ -74,9 +74,7 @@
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help record" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 help (no mq, so no qrecord)
 
@@ -87,7 +85,7 @@
   
       See "hg help qnew" & "hg help record" for more information and usage.
   
-  use "hg -v help qrecord" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg init a
 
@@ -99,7 +97,7 @@
   
   interactively record a new patch
   
-  use "hg help qrecord" to show the full help text
+  (use "hg qrecord -h" to show more help)
   [255]
 
 qrecord patch (mq not present)
@@ -119,7 +117,7 @@
   
       See "hg help qnew" & "hg help record" for more information and usage.
   
-  use "hg -v help qrecord" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 help (mq present)
 
@@ -133,7 +131,7 @@
   
       See "hg help qnew" & "hg help record" for more information and usage.
   
-  options:
+  options ([+] can be repeated):
   
    -e --edit                invoke editor on commit messages
    -g --git                 use git extended diff format
@@ -150,9 +148,7 @@
    -B --ignore-blank-lines  ignore changes whose lines are all blank
       --mq                  operate on patch repository
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help qrecord" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ cd a
 
--- a/tests/test-relink.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-relink.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" hardlink || exit 80
+#require hardlink
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "relink=" >> $HGRCPATH
--- a/tests/test-repair-strip.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-repair-strip.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-#if unix-permissions no-root
+#require unix-permissions no-root
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "mq=">> $HGRCPATH
@@ -130,5 +130,3 @@
   2 files, 2 changesets, 2 total revisions
 
   $ cd ..
-
-#endif
--- a/tests/test-revert-flags.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-revert-flags.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
   $ hg init repo
   $ cd repo
--- a/tests/test-revert.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-revert.t	Thu Aug 14 16:25:47 2014 -0500
@@ -14,101 +14,94 @@
   [255]
   $ hg revert --all
 
-  $ echo 123 > b
+Introduce some changes and revert them
+--------------------------------------
 
-should show b unknown
+  $ echo 123 > b
 
   $ hg status
   ? b
   $ echo 12 > c
 
-should show b unknown and c modified
-
   $ hg status
   M c
   ? b
   $ hg add b
 
-should show b added and c modified
-
   $ hg status
   M c
   A b
   $ hg rm a
 
-should show a removed, b added and c modified
-
   $ hg status
   M c
   A b
   R a
-  $ hg revert a
 
-should show b added, copy saved, and c modified
+revert removal of a file
 
+  $ hg revert a
   $ hg status
   M c
   A b
-  $ hg revert b
 
-should show b unknown, and c modified
+revert addition of a file
 
+  $ hg revert b
   $ hg status
   M c
   ? b
-  $ hg revert --no-backup c
 
-should show unknown: b
+revert modification of a file (--no-backup)
 
+  $ hg revert --no-backup c
   $ hg status
   ? b
-  $ hg add b
 
-should show b added
+revert deletion (! status) of a added file
+------------------------------------------
+
+  $ hg add b
 
   $ hg status b
   A b
   $ rm b
-
-should show b deleted
-
   $ hg status b
   ! b
   $ hg revert -v b
   forgetting b
-
-should not find b
-
   $ hg status b
   b: * (glob)
 
-should show a c e
-
   $ ls
   a
   c
   e
 
-should verbosely save backup to e.orig
+Test creation of backup (.orig) files
+-------------------------------------
 
   $ echo z > e
   $ hg revert --all -v
   saving current version of e as e.orig
   reverting e
 
-should say no changes needed
+revert on clean file (no change)
+--------------------------------
 
   $ hg revert a
   no changes needed to a
 
-should say file not managed
+revert on an untracked file
+---------------------------
 
   $ echo q > q
   $ hg revert q
   file not managed: q
   $ rm q
 
-should say file not found
+revert on file that does not exists
+-----------------------------------
 
   $ hg revert notfound
   notfound: no such file in rev 334a9e57682c
@@ -122,21 +115,26 @@
   A z
   ? e.orig
 
-should add a, remove d, forget z
+revert to another revision (--rev)
+----------------------------------
 
   $ hg revert --all -r0
   adding a
   removing d
   forgetting z
 
-should forget a, undelete d
+revert explicitly to parent (--rev)
+-----------------------------------
 
   $ hg revert --all -rtip
   forgetting a
   undeleting d
   $ rm a *.orig
 
-should silently add a
+revert to another revision (--rev) and exact match
+--------------------------------------------------
+
+exact match are more silent
 
   $ hg revert -r0 a
   $ hg st a
@@ -145,21 +143,24 @@
   $ hg st d
   R d
 
-should silently keep d removed
+should keep d removed
 
   $ hg revert -r0 d
+  no changes needed to d
   $ hg st d
   R d
 
   $ hg update -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+revert of exec bit
+------------------
+
 #if execbit
   $ chmod +x c
   $ hg revert --all
   reverting c
 
-should print non-executable
-
   $ test -x c || echo non-executable
   non-executable
 
@@ -170,8 +171,6 @@
   $ hg revert --all
   reverting c
 
-should print executable
-
   $ test -x c && echo executable
   executable
 #endif
@@ -180,6 +179,7 @@
 
 
 Issue241: update and revert produces inconsistent repositories
+--------------------------------------------------------------
 
   $ hg init a
   $ cd a
@@ -193,20 +193,23 @@
   $ mkdir b
   $ echo b > b/b
 
-should fail - no arguments
+call `hg revert` with no file specified
+---------------------------------------
 
   $ hg revert -rtip
   abort: no files or directories specified
   (use --all to revert all files, or 'hg update 1' to update)
   [255]
 
-should succeed
+call `hg revert` with --all
+---------------------------
 
   $ hg revert --all -rtip
   reverting a
 
 
 Issue332: confusing message when reverting directory
+----------------------------------------------------
 
   $ hg ci -A -m b
   adding b/b
@@ -224,6 +227,7 @@
 
 
 reverting a rename target should revert the source
+--------------------------------------------------
 
   $ hg mv a newa
   $ hg revert newa
@@ -258,6 +262,7 @@
   $ hg rm removed ignoreddir/removed
 
 should revert ignored* and undelete *removed
+--------------------------------------------
 
   $ hg revert -a --no-backup
   reverting ignored
@@ -271,10 +276,14 @@
   $ hg rm removed
 
 should silently revert the named files
+--------------------------------------
 
   $ hg revert --no-backup ignored removed
   $ hg st -mardi
 
+Reverting copy (issue3920)
+--------------------------
+
 someone set up us the copies
 
   $ rm .hgignore
@@ -300,8 +309,9 @@
   R ignored
 
 Test revert of a file added by one side of the merge
+====================================================
 
-(remove any pending change)
+remove any pending change
 
   $ hg revert --all
   forgetting allyour
@@ -309,7 +319,7 @@
   undeleting ignored
   $ hg purge --all --config extensions.purge=
 
-(Adds a new commit)
+Adds a new commit
 
   $ echo foo > newadd
   $ hg add newadd
@@ -317,7 +327,7 @@
   created new head
 
 
-(merge it with the other head)
+merge it with the other head
 
   $ hg merge # merge 1 into 2
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -331,7 +341,7 @@
   commit: 2 modified, 1 removed (merge)
   update: (current)
 
-(clarifies who added what)
+clarifies who added what
 
   $ hg status
   M allyour
@@ -344,7 +354,8 @@
   A base
   R ignored
 
-(revert file added by p1() to p1() state)
+revert file added by p1() to p1() state
+-----------------------------------------
 
   $ hg revert -r 'p1()' 'glob:newad?'
   $ hg status
@@ -352,7 +363,8 @@
   M base
   R ignored
 
-(revert file added by p1() to p2() state)
+revert file added by p1() to p2() state
+------------------------------------------
 
   $ hg revert -r 'p2()' 'glob:newad?'
   removing newadd
@@ -362,7 +374,8 @@
   R ignored
   R newadd
 
-(revert file added by p2() to p2() state)
+revert file added by p2() to p2() state
+------------------------------------------
 
   $ hg revert -r 'p2()' 'glob:allyou?'
   $ hg status
@@ -371,7 +384,8 @@
   R ignored
   R newadd
 
-(revert file added by p2() to p1() state)
+revert file added by p2() to p1() state
+------------------------------------------
 
   $ hg revert -r 'p1()' 'glob:allyou?'
   removing allyour
@@ -381,4 +395,821 @@
   R ignored
   R newadd
 
+Systematic behavior validation of most possible cases
+=====================================================
 
+This section tests most of the possible combinations of working directory
+changes and inter-revision changes. The number of possible cases is significant
+but they all have a slighly different handling. So this section commits to
+generating and testing all of them to allow safe refactoring of the revert code.
+
+A python script is used to generate a file history for each combination of
+changes between, on one side the working directory and its parent and on
+the other side, changes between a revert target (--rev) and working directory
+parent. The three states generated are:
+
+- a "base" revision
+- a "parent" revision
+- the working directory (based on "parent")
+
+The file generated have names of the form:
+
+ <changeset-state>_<working-copy-state>
+
+Here, "changeset-state" conveys the state in "base" and "parent" (or the change
+that happen between them), "working-copy-state" is self explanatory.
+
+All known states are not tested yet. See inline documentation for details.
+Special cases from merge and rename are not tested by this section.
+
+There are also multiple cases where the current revert implementation is known to
+slightly misbehave.
+
+Write the python script to disk
+-------------------------------
+
+  $ cat << EOF > gen-revert-cases.py
+  > # generate proper file state to test revert behavior
+  > import sys
+  > import os
+  > 
+  > # content of the file in "base" and "parent"
+  > # None means no file at all
+  > ctxcontent = {
+  >     # clean: no change from base to parent
+  >     'clean': ['base', 'base'],
+  >     # modified: file content change from base to parent
+  >     'modified': ['base', 'parent'],
+  >     # added: file is missing from base and added in parent
+  >     'added': [None, 'parent'],
+  >     # removed: file exist in base but is removed from parent
+  >     'removed': ['base', None],
+  >     # file exist neither in base not in parent
+  >     'missing': [None, None],
+  > }
+  > 
+  > # content of file in working copy
+  > wccontent = {
+  >     # clean: wc content is the same as parent
+  >     'clean': lambda cc: cc[1],
+  >     # revert: wc content is the same as base
+  >     'revert': lambda cc: cc[0],
+  >     # wc: file exist with a content different from base and parent
+  >     'wc': lambda cc: 'wc',
+  >     # removed: file is missing and marked as untracked
+  >     'removed': lambda cc: None,
+  >     # deleted: file is recorded as tracked but missing
+  >     #          rely on file deletion outside of this script
+  >     'deleted': lambda cc:'TOBEDELETED',
+  > }
+  > # untracked-X is a version of X where the file is not tracked (? unknown)
+  > wccontent['untracked-clean'] = wccontent['clean']
+  > wccontent['untracked-revert'] = wccontent['revert']
+  > wccontent['untracked-wc'] = wccontent['wc']
+  > 
+  > # build the combination of possible states
+  > combination = []
+  > for ctxkey in ctxcontent:
+  >     for wckey in wccontent:
+  >         filename = "%s_%s" % (ctxkey, wckey)
+  >         combination.append((filename, ctxkey, wckey))
+  > 
+  > # make sure we have stable output
+  > combination.sort()
+  > 
+  > # retrieve the state we must generate
+  > target = sys.argv[1]
+  > 
+  > # compute file content
+  > content = []
+  > for filename, ctxkey, wckey in combination:
+  >     cc = ctxcontent[ctxkey]
+  >     if target == 'filelist':
+  >         print filename
+  >     elif target == 'base':
+  >         content.append((filename, cc[0]))
+  >     elif target == 'parent':
+  >         content.append((filename, cc[1]))
+  >     elif target == 'wc':
+  >         content.append((filename, wccontent[wckey](cc)))
+  >     else:
+  >         print >> sys.stderr, "unknown target:", target
+  >         sys.exit(1)
+  > 
+  > # write actual content
+  > for filename, data in content:
+  >     if data is not None:
+  >         f = open(filename, 'w')
+  >         f.write(data + '\n')
+  >         f.close()
+  >     elif os.path.exists(filename):
+  >        os.remove(filename)
+  > EOF
+
+check list of planned files
+
+  $ python gen-revert-cases.py filelist
+  added_clean
+  added_deleted
+  added_removed
+  added_revert
+  added_untracked-clean
+  added_untracked-revert
+  added_untracked-wc
+  added_wc
+  clean_clean
+  clean_deleted
+  clean_removed
+  clean_revert
+  clean_untracked-clean
+  clean_untracked-revert
+  clean_untracked-wc
+  clean_wc
+  missing_clean
+  missing_deleted
+  missing_removed
+  missing_revert
+  missing_untracked-clean
+  missing_untracked-revert
+  missing_untracked-wc
+  missing_wc
+  modified_clean
+  modified_deleted
+  modified_removed
+  modified_revert
+  modified_untracked-clean
+  modified_untracked-revert
+  modified_untracked-wc
+  modified_wc
+  removed_clean
+  removed_deleted
+  removed_removed
+  removed_revert
+  removed_untracked-clean
+  removed_untracked-revert
+  removed_untracked-wc
+  removed_wc
+
+Script to make a simple text version of the content
+---------------------------------------------------
+
+  $ cat << EOF >> dircontent.py
+  > # generate a simple text view of the directory for easy comparison
+  > import os
+  > files = os.listdir('.')
+  > files.sort()
+  > for filename in files:
+  >     if os.path.isdir(filename):
+  >         continue
+  >     content = open(filename).read()
+  >     print '%-6s %s' % (content.strip(), filename)
+  > EOF
+
+Generate appropriate repo state
+-------------------------------
+
+  $ hg init revert-ref
+  $ cd revert-ref
+
+Generate base changeset
+
+  $ python ../gen-revert-cases.py base
+  $ hg addremove --similarity 0
+  adding clean_clean
+  adding clean_deleted
+  adding clean_removed
+  adding clean_revert
+  adding clean_untracked-clean
+  adding clean_untracked-revert
+  adding clean_untracked-wc
+  adding clean_wc
+  adding modified_clean
+  adding modified_deleted
+  adding modified_removed
+  adding modified_revert
+  adding modified_untracked-clean
+  adding modified_untracked-revert
+  adding modified_untracked-wc
+  adding modified_wc
+  adding removed_clean
+  adding removed_deleted
+  adding removed_removed
+  adding removed_revert
+  adding removed_untracked-clean
+  adding removed_untracked-revert
+  adding removed_untracked-wc
+  adding removed_wc
+  $ hg status
+  A clean_clean
+  A clean_deleted
+  A clean_removed
+  A clean_revert
+  A clean_untracked-clean
+  A clean_untracked-revert
+  A clean_untracked-wc
+  A clean_wc
+  A modified_clean
+  A modified_deleted
+  A modified_removed
+  A modified_revert
+  A modified_untracked-clean
+  A modified_untracked-revert
+  A modified_untracked-wc
+  A modified_wc
+  A removed_clean
+  A removed_deleted
+  A removed_removed
+  A removed_revert
+  A removed_untracked-clean
+  A removed_untracked-revert
+  A removed_untracked-wc
+  A removed_wc
+  $ hg commit -m 'base'
+
+(create a simple text version of the content)
+
+  $ python ../dircontent.py > ../content-base.txt
+  $ cat ../content-base.txt
+  base   clean_clean
+  base   clean_deleted
+  base   clean_removed
+  base   clean_revert
+  base   clean_untracked-clean
+  base   clean_untracked-revert
+  base   clean_untracked-wc
+  base   clean_wc
+  base   modified_clean
+  base   modified_deleted
+  base   modified_removed
+  base   modified_revert
+  base   modified_untracked-clean
+  base   modified_untracked-revert
+  base   modified_untracked-wc
+  base   modified_wc
+  base   removed_clean
+  base   removed_deleted
+  base   removed_removed
+  base   removed_revert
+  base   removed_untracked-clean
+  base   removed_untracked-revert
+  base   removed_untracked-wc
+  base   removed_wc
+
+Create parent changeset
+
+  $ python ../gen-revert-cases.py parent
+  $ hg addremove --similarity 0
+  adding added_clean
+  adding added_deleted
+  adding added_removed
+  adding added_revert
+  adding added_untracked-clean
+  adding added_untracked-revert
+  adding added_untracked-wc
+  adding added_wc
+  removing removed_clean
+  removing removed_deleted
+  removing removed_removed
+  removing removed_revert
+  removing removed_untracked-clean
+  removing removed_untracked-revert
+  removing removed_untracked-wc
+  removing removed_wc
+  $ hg status
+  M modified_clean
+  M modified_deleted
+  M modified_removed
+  M modified_revert
+  M modified_untracked-clean
+  M modified_untracked-revert
+  M modified_untracked-wc
+  M modified_wc
+  A added_clean
+  A added_deleted
+  A added_removed
+  A added_revert
+  A added_untracked-clean
+  A added_untracked-revert
+  A added_untracked-wc
+  A added_wc
+  R removed_clean
+  R removed_deleted
+  R removed_removed
+  R removed_revert
+  R removed_untracked-clean
+  R removed_untracked-revert
+  R removed_untracked-wc
+  R removed_wc
+  $ hg commit -m 'parent'
+
+(create a simple text version of the content)
+
+  $ python ../dircontent.py > ../content-parent.txt
+  $ cat ../content-parent.txt
+  parent added_clean
+  parent added_deleted
+  parent added_removed
+  parent added_revert
+  parent added_untracked-clean
+  parent added_untracked-revert
+  parent added_untracked-wc
+  parent added_wc
+  base   clean_clean
+  base   clean_deleted
+  base   clean_removed
+  base   clean_revert
+  base   clean_untracked-clean
+  base   clean_untracked-revert
+  base   clean_untracked-wc
+  base   clean_wc
+  parent modified_clean
+  parent modified_deleted
+  parent modified_removed
+  parent modified_revert
+  parent modified_untracked-clean
+  parent modified_untracked-revert
+  parent modified_untracked-wc
+  parent modified_wc
+
+Setup working directory
+
+  $ python ../gen-revert-cases.py wc | cat
+  $ hg addremove --similarity 0
+  removing added_removed
+  removing added_revert
+  removing added_untracked-revert
+  removing clean_removed
+  adding missing_deleted
+  adding missing_untracked-wc
+  adding missing_wc
+  removing modified_removed
+  adding removed_deleted
+  adding removed_revert
+  adding removed_untracked-revert
+  adding removed_untracked-wc
+  adding removed_wc
+  $ hg forget *untracked*
+  $ rm *deleted*
+  $ hg status
+  M added_wc
+  M clean_wc
+  M modified_revert
+  M modified_wc
+  A missing_wc
+  A removed_revert
+  A removed_wc
+  R added_removed
+  R added_revert
+  R added_untracked-clean
+  R added_untracked-revert
+  R added_untracked-wc
+  R clean_removed
+  R clean_untracked-clean
+  R clean_untracked-revert
+  R clean_untracked-wc
+  R modified_removed
+  R modified_untracked-clean
+  R modified_untracked-revert
+  R modified_untracked-wc
+  ! added_deleted
+  ! clean_deleted
+  ! missing_deleted
+  ! modified_deleted
+  ! removed_deleted
+  ? missing_untracked-wc
+  ? removed_untracked-revert
+  ? removed_untracked-wc
+
+  $ hg status --rev 'desc("base")'
+  M clean_wc
+  M modified_clean
+  M modified_wc
+  M removed_wc
+  A added_clean
+  A added_wc
+  A missing_wc
+  R clean_removed
+  R clean_untracked-clean
+  R clean_untracked-revert
+  R clean_untracked-wc
+  R modified_removed
+  R modified_untracked-clean
+  R modified_untracked-revert
+  R modified_untracked-wc
+  R removed_clean
+  R removed_deleted
+  R removed_removed
+  R removed_untracked-clean
+  R removed_untracked-revert
+  R removed_untracked-wc
+  ! added_deleted
+  ! clean_deleted
+  ! missing_deleted
+  ! modified_deleted
+  ! removed_deleted
+  ? missing_untracked-wc
+
+(create a simple text version of the content)
+
+  $ python ../dircontent.py > ../content-wc.txt
+  $ cat ../content-wc.txt
+  parent added_clean
+  parent added_untracked-clean
+  wc     added_untracked-wc
+  wc     added_wc
+  base   clean_clean
+  base   clean_revert
+  base   clean_untracked-clean
+  base   clean_untracked-revert
+  wc     clean_untracked-wc
+  wc     clean_wc
+  wc     missing_untracked-wc
+  wc     missing_wc
+  parent modified_clean
+  base   modified_revert
+  parent modified_untracked-clean
+  base   modified_untracked-revert
+  wc     modified_untracked-wc
+  wc     modified_wc
+  base   removed_revert
+  base   removed_untracked-revert
+  wc     removed_untracked-wc
+  wc     removed_wc
+
+  $ cd ..
+
+Test revert --all to parent content
+-----------------------------------
+
+(setup from reference repo)
+
+  $ cp -r revert-ref revert-parent-all
+  $ cd revert-parent-all
+
+check revert output
+
+  $ hg revert --all
+  reverting added_deleted
+  undeleting added_removed
+  undeleting added_revert
+  undeleting added_untracked-clean
+  undeleting added_untracked-revert
+  undeleting added_untracked-wc
+  reverting added_wc
+  reverting clean_deleted
+  undeleting clean_removed
+  undeleting clean_untracked-clean
+  undeleting clean_untracked-revert
+  undeleting clean_untracked-wc
+  reverting clean_wc
+  forgetting missing_deleted
+  forgetting missing_wc
+  reverting modified_deleted
+  undeleting modified_removed
+  reverting modified_revert
+  undeleting modified_untracked-clean
+  undeleting modified_untracked-revert
+  undeleting modified_untracked-wc
+  reverting modified_wc
+  forgetting removed_deleted
+  forgetting removed_revert
+  forgetting removed_wc
+
+Compare resulting directory with revert target.
+
+The diff is filtered to include change only. The only difference should be
+additional `.orig` backup file when applicable.
+
+  $ python ../dircontent.py > ../content-parent-all.txt
+  $ cd ..
+  $ diff -U 0 -- content-parent.txt content-parent-all.txt | grep _
+  +wc     added_untracked-wc.orig
+  +wc     added_wc.orig
+  +wc     clean_untracked-wc.orig
+  +wc     clean_wc.orig
+  +wc     missing_untracked-wc
+  +wc     missing_wc
+  +base   modified_revert.orig
+  +base   modified_untracked-revert.orig
+  +wc     modified_untracked-wc.orig
+  +wc     modified_wc.orig
+  +base   removed_revert
+  +base   removed_untracked-revert
+  +wc     removed_untracked-wc
+  +wc     removed_wc
+
+Test revert --all to "base" content
+-----------------------------------
+
+(setup from reference repo)
+
+  $ cp -r revert-ref revert-base-all
+  $ cd revert-base-all
+
+check revert output
+
+Misbehavior:
+
+- report "reverting" when file needs no changes
+|
+| - reverting modified_revert
+| - reverting removed_revert
+
+  $ hg revert --all --rev 'desc(base)'
+  removing added_clean
+  removing added_deleted
+  removing added_wc
+  reverting clean_deleted
+  undeleting clean_removed
+  undeleting clean_untracked-clean
+  undeleting clean_untracked-revert
+  undeleting clean_untracked-wc
+  reverting clean_wc
+  forgetting missing_deleted
+  forgetting missing_wc
+  reverting modified_clean
+  reverting modified_deleted
+  undeleting modified_removed
+  reverting modified_revert
+  undeleting modified_untracked-clean
+  undeleting modified_untracked-revert
+  undeleting modified_untracked-wc
+  reverting modified_wc
+  adding removed_clean
+  reverting removed_deleted
+  adding removed_removed
+  reverting removed_revert
+  adding removed_untracked-clean
+  adding removed_untracked-revert
+  adding removed_untracked-wc
+  reverting removed_wc
+
+Compare resulting directory with revert target.
+
+The diff is filtered to include change only. The only difference should be
+additional `.orig` backup file when applicable.
+
+Misbehavior:
+
+- no backup for
+| - added_wc (DATA LOSS)
+
+  $ python ../dircontent.py > ../content-base-all.txt
+  $ cd ..
+  $ diff -U 0 -- content-base.txt content-base-all.txt | grep _
+  +parent added_untracked-clean
+  +wc     added_untracked-wc
+  +wc     clean_untracked-wc.orig
+  +wc     clean_wc.orig
+  +wc     missing_untracked-wc
+  +wc     missing_wc
+  +parent modified_untracked-clean.orig
+  +wc     modified_untracked-wc.orig
+  +wc     modified_wc.orig
+  +wc     removed_untracked-wc.orig
+  +wc     removed_wc.orig
+
+Test revert to parent content with explicit file name
+-----------------------------------------------------
+
+(setup from reference repo)
+
+  $ cp -r revert-ref revert-parent-explicit
+  $ cd revert-parent-explicit
+
+revert all files individually and check the output
+(output is expected to be different than in the --all case)
+
+  $ for file in `python ../gen-revert-cases.py filelist`; do
+  >   echo '### revert for:' $file;
+  >   hg revert $file;
+  >   echo
+  > done
+  ### revert for: added_clean
+  no changes needed to added_clean
+  
+  ### revert for: added_deleted
+  
+  ### revert for: added_removed
+  
+  ### revert for: added_revert
+  
+  ### revert for: added_untracked-clean
+  
+  ### revert for: added_untracked-revert
+  
+  ### revert for: added_untracked-wc
+  
+  ### revert for: added_wc
+  
+  ### revert for: clean_clean
+  no changes needed to clean_clean
+  
+  ### revert for: clean_deleted
+  
+  ### revert for: clean_removed
+  
+  ### revert for: clean_revert
+  no changes needed to clean_revert
+  
+  ### revert for: clean_untracked-clean
+  
+  ### revert for: clean_untracked-revert
+  
+  ### revert for: clean_untracked-wc
+  
+  ### revert for: clean_wc
+  
+  ### revert for: missing_clean
+  missing_clean: no such file in rev * (glob)
+  
+  ### revert for: missing_deleted
+  
+  ### revert for: missing_removed
+  missing_removed: no such file in rev * (glob)
+  
+  ### revert for: missing_revert
+  missing_revert: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-clean
+  missing_untracked-clean: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-revert
+  missing_untracked-revert: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-wc
+  file not managed: missing_untracked-wc
+  
+  ### revert for: missing_wc
+  
+  ### revert for: modified_clean
+  no changes needed to modified_clean
+  
+  ### revert for: modified_deleted
+  
+  ### revert for: modified_removed
+  
+  ### revert for: modified_revert
+  
+  ### revert for: modified_untracked-clean
+  
+  ### revert for: modified_untracked-revert
+  
+  ### revert for: modified_untracked-wc
+  
+  ### revert for: modified_wc
+  
+  ### revert for: removed_clean
+  removed_clean: no such file in rev * (glob)
+  
+  ### revert for: removed_deleted
+  
+  ### revert for: removed_removed
+  removed_removed: no such file in rev * (glob)
+  
+  ### revert for: removed_revert
+  
+  ### revert for: removed_untracked-clean
+  removed_untracked-clean: no such file in rev * (glob)
+  
+  ### revert for: removed_untracked-revert
+  file not managed: removed_untracked-revert
+  
+  ### revert for: removed_untracked-wc
+  file not managed: removed_untracked-wc
+  
+  ### revert for: removed_wc
+  
+
+check resulting directory againt the --all run
+(There should be no difference)
+
+  $ python ../dircontent.py > ../content-parent-explicit.txt
+  $ cd ..
+  $ diff -U 0 -- content-parent-all.txt content-parent-explicit.txt | grep _
+  [1]
+
+Test revert to "base" content with explicit file name
+-----------------------------------------------------
+
+(setup from reference repo)
+
+  $ cp -r revert-ref revert-base-explicit
+  $ cd revert-base-explicit
+
+revert all files individually and check the output
+(output is expected to be different than in the --all case)
+
+Misbehavior:
+
+- fails to report no change to revert for
+|
+| - clean_clean
+| - clean_revert
+| - modified_revert
+| - removed_revert
+
+  $ for file in `python ../gen-revert-cases.py filelist`; do
+  >   echo '### revert for:' $file;
+  >   hg revert $file --rev 'desc(base)';
+  >   echo
+  > done
+  ### revert for: added_clean
+  
+  ### revert for: added_deleted
+  
+  ### revert for: added_removed
+  no changes needed to added_removed
+  
+  ### revert for: added_revert
+  no changes needed to added_revert
+  
+  ### revert for: added_untracked-clean
+  no changes needed to added_untracked-clean
+  
+  ### revert for: added_untracked-revert
+  no changes needed to added_untracked-revert
+  
+  ### revert for: added_untracked-wc
+  no changes needed to added_untracked-wc
+  
+  ### revert for: added_wc
+  
+  ### revert for: clean_clean
+  
+  ### revert for: clean_deleted
+  
+  ### revert for: clean_removed
+  
+  ### revert for: clean_revert
+  
+  ### revert for: clean_untracked-clean
+  
+  ### revert for: clean_untracked-revert
+  
+  ### revert for: clean_untracked-wc
+  
+  ### revert for: clean_wc
+  
+  ### revert for: missing_clean
+  missing_clean: no such file in rev * (glob)
+  
+  ### revert for: missing_deleted
+  
+  ### revert for: missing_removed
+  missing_removed: no such file in rev * (glob)
+  
+  ### revert for: missing_revert
+  missing_revert: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-clean
+  missing_untracked-clean: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-revert
+  missing_untracked-revert: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-wc
+  file not managed: missing_untracked-wc
+  
+  ### revert for: missing_wc
+  
+  ### revert for: modified_clean
+  
+  ### revert for: modified_deleted
+  
+  ### revert for: modified_removed
+  
+  ### revert for: modified_revert
+  
+  ### revert for: modified_untracked-clean
+  
+  ### revert for: modified_untracked-revert
+  
+  ### revert for: modified_untracked-wc
+  
+  ### revert for: modified_wc
+  
+  ### revert for: removed_clean
+  
+  ### revert for: removed_deleted
+  
+  ### revert for: removed_removed
+  
+  ### revert for: removed_revert
+  
+  ### revert for: removed_untracked-clean
+  
+  ### revert for: removed_untracked-revert
+  
+  ### revert for: removed_untracked-wc
+  
+  ### revert for: removed_wc
+  
+
+check resulting directory againt the --all run
+(There should be no difference)
+
+  $ python ../dircontent.py > ../content-base-explicit.txt
+  $ cd ..
+  $ diff -U 0 -- content-base-all.txt content-base-explicit.txt | grep _
+  [1]
--- a/tests/test-run-tests.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-run-tests.t	Thu Aug 14 16:25:47 2014 -0500
@@ -13,6 +13,8 @@
   $ cat > test-success.t << EOF
   >   $ echo babar
   >   babar
+  >   $ echo xyzzy
+  >   xyzzy
   > EOF
 
   $ $TESTDIR/run-tests.py --with-hg=`which hg`
@@ -25,16 +27,20 @@
   $ cat > test-failure.t << EOF
   >   $ echo babar
   >   rataxes
+  > This is a noop statement so that
+  > this test is still more bytes than success.
   > EOF
 
   $ $TESTDIR/run-tests.py --with-hg=`which hg`
   
   --- $TESTTMP/test-failure.t (glob)
   +++ $TESTTMP/test-failure.t.err (glob)
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   
   ERROR: test-failure.t output changed
   !.
@@ -42,6 +48,39 @@
   # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
   python hash seed: * (glob)
   [1]
+test --xunit support
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` --xunit=xunit.xml
+  
+  --- $TESTTMP/test-failure.t
+  +++ $TESTTMP/test-failure.t.err
+  @@ -1,4 +1,4 @@
+     $ echo babar
+  -  rataxes
+  +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
+  
+  ERROR: test-failure.t output changed
+  !.
+  Failed test-failure.t: output changed
+  # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
+  $ cat xunit.xml
+  <?xml version="1.0" encoding="utf-8"?>
+  <testsuite errors="0" failures="1" name="run-tests" skipped="0" tests="2">
+    <testcase name="test-success.t" time="*"/> (glob)
+    <testcase name="test-failure.t" time="*"> (glob)
+  <![CDATA[--- $TESTTMP/test-failure.t
+  +++ $TESTTMP/test-failure.t.err
+  @@ -1,4 +1,4 @@
+     $ echo babar
+  -  rataxes
+  +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
+  ]]>  </testcase>
+  </testsuite>
 
 test for --retest
 ====================
@@ -50,15 +89,17 @@
   
   --- $TESTTMP/test-failure.t (glob)
   +++ $TESTTMP/test-failure.t.err (glob)
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   
   ERROR: test-failure.t output changed
   !
   Failed test-failure.t: output changed
-  # Ran 1 tests, 1 skipped, 0 warned, 1 failed.
+  # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
   python hash seed: * (glob)
   [1]
 
@@ -71,16 +112,23 @@
   .
   # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
 
+success w/ keyword
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` -k xyzzy
+  .
+  # Ran 2 tests, 1 skipped, 0 warned, 0 failed.
+
 failed
 
   $ $TESTDIR/run-tests.py --with-hg=`which hg` test-failure.t
   
   --- $TESTTMP/test-failure.t (glob)
   +++ $TESTTMP/test-failure.t.err (glob)
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   
   ERROR: test-failure.t output changed
   !
@@ -89,6 +137,25 @@
   python hash seed: * (glob)
   [1]
 
+failure w/ keyword
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` -k rataxes
+  
+  --- $TESTTMP/test-failure.t
+  +++ $TESTTMP/test-failure.t.err
+  @@ -1,4 +1,4 @@
+     $ echo babar
+  -  rataxes
+  +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
+  
+  ERROR: test-failure.t output changed
+  !
+  Failed test-failure.t: output changed
+  # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
 Running In Debug Mode
 ======================
 
@@ -97,14 +164,18 @@
   SALT* 0 0 (glob)
   + echo babar
   babar
-  + echo SALT* 2 0 (glob)
-  SALT* 2 0 (glob)
+  + echo SALT* 4 0 (glob)
+  SALT* 4 0 (glob)
   .+ echo SALT* 0 0 (glob)
   SALT* 0 0 (glob)
   + echo babar
   babar
   + echo SALT* 2 0 (glob)
   SALT* 2 0 (glob)
+  + echo xyzzy
+  xyzzy
+  + echo SALT* 4 0 (glob)
+  SALT* 4 0 (glob)
   .
   # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
 
@@ -118,19 +189,23 @@
   
   --- $TESTTMP/test-failure*.t (glob)
   +++ $TESTTMP/test-failure*.t.err (glob)
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   
   ERROR: test-failure*.t output changed (glob)
   !
   --- $TESTTMP/test-failure*.t (glob)
   +++ $TESTTMP/test-failure*.t.err (glob)
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   
   ERROR: test-failure*.t output changed (glob)
   !
@@ -156,10 +231,12 @@
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   Accept this change? [n] 
   ERROR: test-failure.t output changed
   !.
@@ -171,6 +248,20 @@
   $ cat test-failure.t
     $ echo babar
     rataxes
+  This is a noop statement so that
+  this test is still more bytes than success.
+
+View the fix
+
+  $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` --view echo
+  $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
+  
+  ERROR: test-failure.t output changed
+  !.
+  Failed test-failure.t: output changed
+  # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
 
 Accept the fix
 
@@ -178,16 +269,20 @@
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   Accept this change? [n] ..
   # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
 
   $ cat test-failure.t
     $ echo babar
     babar
+  This is a noop statement so that
+  this test is still more bytes than success.
 
 (reinstall)
   $ mv backup test-failure.t
@@ -201,3 +296,64 @@
   # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
   python hash seed: * (glob)
   [1]
+
+test for --time
+==================
+
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time
+  .
+  # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
+  # Producing time report
+  cuser   csys    real      Test
+  \s*[\d\.]{5}   \s*[\d\.]{5}   \s*[\d\.]{5}   test-success.t (re)
+
+test for --time with --job enabled
+====================================
+
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time --jobs 2
+  .
+  # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
+  # Producing time report
+  cuser   csys    real      Test
+  \s*[\d\.]{5}   \s*[\d\.]{5}   \s*[\d\.]{5}   test-success.t (re)
+
+Skips
+================
+  $ cat > test-skip.t <<EOF
+  >   $ echo xyzzy
+  > #require false
+  > EOF
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff
+  !.s
+  Skipped test-skip.t: irrelevant
+  Failed test-failure.t: output changed
+  # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy
+  .s
+  Skipped test-skip.t: irrelevant
+  # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
+
+Skips with xml
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy \
+  >  --xunit=xunit.xml
+  .s
+  Skipped test-skip.t: irrelevant
+  # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
+  $ cat xunit.xml
+  <?xml version="1.0" encoding="utf-8"?>
+  <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
+    <testcase name="test-success.t" time="*"/> (glob)
+  </testsuite>
+
+Missing skips or blacklisted skips don't count as executed:
+  $ echo test-failure.t > blacklist
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` --blacklist=blacklist \
+  >   test-failure.t test-bogus.t
+  ss
+  Skipped test-bogus.t: Doesn't exist
+  Skipped test-failure.t: blacklisted
+  # Ran 0 tests, 2 skipped, 0 warned, 0 failed.
+
--- a/tests/test-schemes.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-schemes.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
--- a/tests/test-serve.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-serve.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hgserve()
   > {
--- a/tests/test-share.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-share.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ echo "[extensions]"      >> $HGRCPATH
   $ echo "share = "          >> $HGRCPATH
--- a/tests/test-simplemerge.py	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-simplemerge.py	Thu Aug 14 16:25:47 2014 -0500
@@ -320,66 +320,6 @@
         self.log(''.join(ml))
         self.assertEquals(ml, MERGED_RESULT)
 
-    def test_minimal_conflicts_common(self):
-        """Reprocessing"""
-        base_text = ("a\n" * 20).splitlines(True)
-        this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
-        other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
-        m3 = Merge3(base_text, other_text, this_text)
-        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
-        merged_text = "".join(list(m_lines))
-        optimal_text = ("a\n" * 10 + "<<<<<<< OTHER\nc\n=======\n"
-            + ">>>>>>> THIS\n"
-            + 8* "b\n" + "<<<<<<< OTHER\nc\n=======\n"
-            + 2* "b\n" + ">>>>>>> THIS\n")
-        self.assertEquals(optimal_text, merged_text)
-
-    def test_minimal_conflicts_unique(self):
-        def add_newline(s):
-            """Add a newline to each entry in the string"""
-            return [(x+'\n') for x in s]
-
-        base_text = add_newline("abcdefghijklm")
-        this_text = add_newline("abcdefghijklmNOPQRSTUVWXYZ")
-        other_text = add_newline("abcdefghijklm1OPQRSTUVWXY2")
-        m3 = Merge3(base_text, other_text, this_text)
-        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
-        merged_text = "".join(list(m_lines))
-        optimal_text = ''.join(add_newline("abcdefghijklm")
-            + ["<<<<<<< OTHER\n1\n=======\nN\n>>>>>>> THIS\n"]
-            + add_newline('OPQRSTUVWXY')
-            + ["<<<<<<< OTHER\n2\n=======\nZ\n>>>>>>> THIS\n"]
-            )
-        self.assertEquals(optimal_text, merged_text)
-
-    def test_minimal_conflicts_nonunique(self):
-        def add_newline(s):
-            """Add a newline to each entry in the string"""
-            return [(x+'\n') for x in s]
-
-        base_text = add_newline("abacddefgghij")
-        this_text = add_newline("abacddefgghijkalmontfprz")
-        other_text = add_newline("abacddefgghijknlmontfprd")
-        m3 = Merge3(base_text, other_text, this_text)
-        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
-        merged_text = "".join(list(m_lines))
-        optimal_text = ''.join(add_newline("abacddefgghijk")
-            + ["<<<<<<< OTHER\nn\n=======\na\n>>>>>>> THIS\n"]
-            + add_newline('lmontfpr')
-            + ["<<<<<<< OTHER\nd\n=======\nz\n>>>>>>> THIS\n"]
-            )
-        self.assertEquals(optimal_text, merged_text)
-
-    def test_reprocess_and_base(self):
-        """Reprocessing and showing base breaks correctly"""
-        base_text = ("a\n" * 20).splitlines(True)
-        this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
-        other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
-        m3 = Merge3(base_text, other_text, this_text)
-        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True,
-                                 base_marker='|||||||')
-        self.assertRaises(CantReprocessAndShowBase, list, m_lines)
-
     def test_binary(self):
         self.assertRaises(util.Abort, Merge3, ['\x00'], ['a'], ['b'])
 
--- a/tests/test-simplemerge.py.out	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-simplemerge.py.out	Thu Aug 14 16:25:47 2014 -0500
@@ -1,5 +1,5 @@
-....................
+................
 ----------------------------------------------------------------------
-Ran 20 tests in 0.000s
+Ran 16 tests in 0.000s
 
 OK
--- a/tests/test-static-http.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-static-http.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 #if windows
   $ hg clone http://localhost:$HGPORT/ copy
--- a/tests/test-strict.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-strict.t	Thu Aug 14 16:25:47 2014 -0500
@@ -37,7 +37,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
   [255]
   $ hg annotate a
   0: a
--- a/tests/test-strip.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-strip.t	Thu Aug 14 16:25:47 2014 -0500
@@ -532,9 +532,9 @@
   
   strip changesets and all their descendants from the repository
   
-  use "hg help -e strip" to show help for the strip extension
+  (use "hg help -e strip" to show help for the strip extension)
   
-  options:
+  options ([+] can be repeated):
   
    -r --rev REV [+]    strip specified revision (optional, can specify revisions
                        without this option)
@@ -545,7 +545,5 @@
    -B --bookmark VALUE remove revs only reachable from given bookmark
       --mq             operate on patch repository
   
-  [+] marked option can be specified multiple times
-  
-  use "hg help strip" to show the full help text
+  (use "hg strip -h" to show more help)
   [255]
--- a/tests/test-subrepo-git.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-subrepo-git.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" git || exit 80
+#require git
 
 make git commits repeatable
 
--- a/tests/test-subrepo-relative-path.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-subrepo-relative-path.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 Preparing the subrepository 'sub'
 
--- a/tests/test-subrepo-svn.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-subrepo-svn.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" svn15 || exit 80
+#require svn15
 
   $ SVNREPOPATH=`pwd`/svn-repo
 #if windows
--- a/tests/test-symlink-placeholder.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-symlink-placeholder.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" symlink || exit 80
+#require symlink
 
 Create extension that can disable symlink support:
 
--- a/tests/test-symlinks.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-symlinks.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" symlink || exit 80
+#require symlink
 
 == tests added in 0.7 ==
 
--- a/tests/test-transplant.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-transplant.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
--- a/tests/test-treediscovery-legacy.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-treediscovery-legacy.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 Tests discovery against servers without getbundle support:
 
--- a/tests/test-treediscovery.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-treediscovery.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 Tests discovery against servers without getbundle support:
 
--- a/tests/test-unbundlehash.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-unbundlehash.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 Test wire protocol unbundle with hashed heads (capability: unbundlehash)
 
--- a/tests/test-update-issue1456.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-update-issue1456.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
   $ rm -rf a
   $ hg init a
--- a/tests/test-websub.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-websub.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
--- a/tests/test-wireproto.t	Thu Aug 14 16:18:45 2014 -0500
+++ b/tests/test-wireproto.t	Thu Aug 14 16:25:47 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 Test wire protocol argument passing