merge with stable
authorMatt Mackall <mpm@selenic.com>
Fri, 13 Mar 2015 17:55:04 -0500
changeset 24303 15afda349b11
parent 24302 6e092ea2eff1 (diff)
parent 24296 b73a22d1d9bf (current diff)
child 24305 867c3649be5d
merge with stable
mercurial/templater.py
tests/test-hgweb.t
--- a/Makefile	Fri Mar 13 21:18:59 2015 +0900
+++ b/Makefile	Fri Mar 13 17:55:04 2015 -0500
@@ -7,11 +7,14 @@
 PREFIX=/usr/local
 export PREFIX
 PYTHON=python
+$(eval HGROOT := $(shell pwd))
+HGPYTHONS ?= $(HGROOT)/build/pythons
 PURE=
 PYFILES:=$(shell find mercurial hgext doc -name '*.py')
 DOCFILES=mercurial/help/*.txt
 export LANGUAGE=C
 export LC_ALL=C
+TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
 
 # Set this to e.g. "mingw32" to use a non-default compiler.
 COMPILER=
@@ -98,6 +101,13 @@
 test-%:
 	cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
 
+testpy-%:
+	@echo Looking for Python $* in $(HGPYTHONS)
+	[ -e $(HGPYTHONS)/$*/bin/python ] || ( \
+	cd $$(mktemp --directory --tmpdir) && \
+        $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
+	cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
+
 check-code:
 	hg manifest | xargs python contrib/check-code.py
 
--- a/contrib/check-code.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/contrib/check-code.py	Fri Mar 13 17:55:04 2015 -0500
@@ -153,7 +153,7 @@
     (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
     (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
      "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
-     'hg pull -q file:../test'), # in test-pull.t which is skipped on windows
+     '# no-msys'), # in test-pull.t which is skipped on windows
     (r'^  saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
     (r'^  changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
      winglobmsg),
--- a/contrib/check-commit	Fri Mar 13 21:18:59 2015 +0900
+++ b/contrib/check-commit	Fri Mar 13 17:55:04 2015 -0500
@@ -25,6 +25,7 @@
     (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[^\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"),
--- a/contrib/win32/ReadMe.html	Fri Mar 13 21:18:59 2015 +0900
+++ b/contrib/win32/ReadMe.html	Fri Mar 13 17:55:04 2015 -0500
@@ -140,7 +140,7 @@
     </p>
 
     <p>
-      Mercurial is Copyright 2005-2014 Matt Mackall and others. See
+      Mercurial is Copyright 2005-2015 Matt Mackall and others. See
       the <tt>Contributors.txt</tt> file for a list of contributors.
     </p>
 
--- a/contrib/win32/mercurial.iss	Fri Mar 13 21:18:59 2015 +0900
+++ b/contrib/win32/mercurial.iss	Fri Mar 13 17:55:04 2015 -0500
@@ -21,7 +21,7 @@
 #endif
 
 [Setup]
-AppCopyright=Copyright 2005-2010 Matt Mackall and others
+AppCopyright=Copyright 2005-2015 Matt Mackall and others
 AppName=Mercurial
 #if ARCH == "x64"
 AppVerName=Mercurial {#VERSION} (64-bit)
@@ -44,7 +44,7 @@
 DefaultDirName={pf}\Mercurial
 SourceDir=..\..
 VersionInfoDescription=Mercurial distributed SCM (version {#VERSION})
-VersionInfoCopyright=Copyright 2005-2010 Matt Mackall and others
+VersionInfoCopyright=Copyright 2005-2015 Matt Mackall and others
 VersionInfoCompany=Matt Mackall and others
 InternalCompressLevel=max
 SolidCompression=true
Binary file contrib/wix/COPYING.rtf has changed
--- a/doc/hgmanpage.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/doc/hgmanpage.py	Fri Mar 13 17:55:04 2015 -0500
@@ -427,7 +427,7 @@
         pass
 
     def visit_block_quote(self, node):
-        # BUG/HACK: indent alway uses the _last_ indention,
+        # BUG/HACK: indent always uses the _last_ indention,
         # thus we need two of them.
         self.indent(BLOCKQOUTE_INDENT)
         self.indent(0)
--- a/hgext/churn.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/churn.py	Fri Mar 13 17:55:04 2015 -0500
@@ -46,7 +46,7 @@
             date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
             return date.strftime(opts['dateformat'])
     else:
-        tmpl = opts.get('template', '{author|email}')
+        tmpl = opts.get('oldtemplate') or opts.get('template')
         tmpl = maketemplater(ui, repo, tmpl)
         def getkey(ctx):
             ui.pushbuffer()
@@ -95,7 +95,9 @@
      _('count rate for the specified revision or revset'), _('REV')),
     ('d', 'date', '',
      _('count rate for revisions matching date spec'), _('DATE')),
-    ('t', 'template', '{author|email}',
+    ('t', 'oldtemplate', '',
+     _('template to group changesets (DEPRECATED)'), _('TEMPLATE')),
+    ('T', 'template', '{author|email}',
      _('template to group changesets'), _('TEMPLATE')),
     ('f', 'dateformat', '',
      _('strftime-compatible format for grouping by date'), _('FORMAT')),
--- a/hgext/color.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/color.py	Fri Mar 13 17:55:04 2015 -0500
@@ -140,6 +140,17 @@
 either using ansi mode (or auto mode), or by using less -r (which will
 pass through all terminal control codes, not just color control
 codes).
+
+On some systems (such as MSYS in Windows), the terminal may support
+a different color mode than the pager (activated via the "pager"
+extension). It is possible to define separate modes depending on whether
+the pager is active::
+
+  [color]
+  mode = auto
+  pagermode = ansi
+
+If ``pagermode`` is not defined, the ``mode`` will be used.
 '''
 
 import os
@@ -213,20 +224,43 @@
     formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
 
     mode = ui.config('color', 'mode', 'auto')
+
+    # If pager is active, color.pagermode overrides color.mode.
+    if getattr(ui, 'pageractive', False):
+        mode = ui.config('color', 'pagermode', mode)
+
     realmode = mode
     if mode == 'auto':
-        if os.name == 'nt' and 'TERM' not in os.environ:
-            # looks line a cmd.exe console, use win32 API or nothing
-            realmode = 'win32'
+        if os.name == 'nt':
+            term = os.environ.get('TERM')
+            # TERM won't be defined in a vanilla cmd.exe environment.
+            if not term:
+                realmode = 'win32'
+
+            # UNIX-like environments on Windows such as Cygwin and MSYS will
+            # set TERM. They appear to make a best effort attempt at setting it
+            # to something appropriate. However, not all environments with TERM
+            # defined support ANSI. Since "ansi" could result in terminal
+            # gibberish, we error on the side of selecting "win32". However, if
+            # w32effects is not defined, we almost certainly don't support
+            # "win32", so don't even try.
+            if 'xterm' in term or not w32effects:
+                realmode = 'ansi'
+            else:
+                realmode = 'win32'
         else:
             realmode = 'ansi'
 
+    def modewarn():
+        # only warn if color.mode was explicitly set and we're in
+        # an interactive terminal
+        if mode == realmode and ui.interactive():
+            ui.warn(_('warning: failed to set color mode to %s\n') % mode)
+
     if realmode == 'win32':
         _terminfo_params = {}
         if not w32effects:
-            if mode == 'win32':
-                # only warn if color.mode is explicitly set to win32
-                ui.warn(_('warning: failed to set color mode to %s\n') % mode)
+            modewarn()
             return None
         _effects.update(w32effects)
     elif realmode == 'ansi':
@@ -234,10 +268,8 @@
     elif realmode == 'terminfo':
         _terminfosetup(ui, mode)
         if not _terminfo_params:
-            if mode == 'terminfo':
-                ## FIXME Shouldn't we return None in this case too?
-                # only warn if color.mode is explicitly set to win32
-                ui.warn(_('warning: failed to set color mode to %s\n') % mode)
+            ## FIXME Shouldn't we return None in this case too?
+            modewarn()
             realmode = 'ansi'
     else:
         return None
--- a/hgext/convert/monotone.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/convert/monotone.py	Fri Mar 13 17:55:04 2015 -0500
@@ -297,7 +297,7 @@
         extra = {}
         certs = self.mtngetcerts(rev)
         if certs.get('suspend') == certs["branch"]:
-            extra['close'] = '1'
+            extra['close'] = 1
         return commit(
             author=certs["author"],
             date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
--- a/hgext/extdiff.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/extdiff.py	Fri Mar 13 17:55:04 2015 -0500
@@ -276,6 +276,7 @@
 
 def uisetup(ui):
     for cmd, path in ui.configitems('extdiff'):
+        path = util.expandpath(path)
         if cmd.startswith('cmd.'):
             cmd = cmd[4:]
             if not path:
--- a/hgext/graphlog.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/graphlog.py	Fri Mar 13 17:55:04 2015 -0500
@@ -54,4 +54,5 @@
     Nodes printed as an @ character are parents of the working
     directory.
     """
-    return cmdutil.graphlog(ui, repo, *pats, **opts)
+    opts['graph'] = True
+    return commands.log(ui, repo, *pats, **opts)
--- a/hgext/histedit.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/histedit.py	Fri Mar 13 17:55:04 2015 -0500
@@ -142,6 +142,13 @@
 as running ``hg histedit 836302820282``. If you need plan to push to a
 repository that Mercurial does not detect to be related to the source
 repo, you can add a ``--force`` option.
+
+Histedit rule lines are truncated to 80 characters by default. You
+can customise this behaviour by setting a different length in your
+configuration file:
+
+[histedit]
+linelen = 120      # truncate rule lines at 120 characters
 """
 
 try:
@@ -158,6 +165,7 @@
 from mercurial import error
 from mercurial import copies
 from mercurial import context
+from mercurial import extensions
 from mercurial import hg
 from mercurial import node
 from mercurial import repair
@@ -189,13 +197,13 @@
 """)
 
 class histeditstate(object):
-    def __init__(self, repo, parentctx=None, rules=None, keep=None,
+    def __init__(self, repo, parentctxnode=None, rules=None, keep=None,
             topmost=None, replacements=None, lock=None, wlock=None):
         self.repo = repo
         self.rules = rules
         self.keep = keep
         self.topmost = topmost
-        self.parentctx = parentctx
+        self.parentctxnode = parentctxnode
         self.lock = lock
         self.wlock = wlock
         if replacements is None:
@@ -214,7 +222,7 @@
 
         parentctxnode, rules, keep, topmost, replacements = pickle.load(fp)
 
-        self.parentctx = self.repo[parentctxnode]
+        self.parentctxnode = parentctxnode
         self.rules = rules
         self.keep = keep
         self.topmost = topmost
@@ -222,7 +230,7 @@
 
     def write(self):
         fp = self.repo.vfs('histedit-state', 'w')
-        pickle.dump((self.parentctx.node(), self.rules, self.keep,
+        pickle.dump((self.parentctxnode, self.rules, self.keep,
                      self.topmost, self.replacements), fp)
         fp.close()
 
@@ -346,10 +354,11 @@
     return repo.commitctx(new)
 
 def pick(ui, state, ha, opts):
-    repo, ctx = state.repo, state.parentctx
+    repo, ctxnode = state.repo, state.parentctxnode
+    ctx = repo[ctxnode]
     oldctx = repo[ha]
     if oldctx.parents()[0] == ctx:
-        ui.debug('node %s unchanged\n' % ha)
+        ui.debug('node %s unchanged\n' % ha[:12])
         return oldctx, []
     hg.update(repo, ctx.node())
     stats = applychanges(ui, repo, oldctx, opts)
@@ -361,14 +370,15 @@
     n = commit(text=oldctx.description(), user=oldctx.user(),
                date=oldctx.date(), extra=oldctx.extra())
     if n is None:
-        ui.warn(_('%s: empty changeset\n') % node.hex(ha))
+        ui.warn(_('%s: empty changeset\n') % ha[:12])
         return ctx, []
     new = repo[n]
     return new, [(oldctx.node(), (n,))]
 
 
 def edit(ui, state, ha, opts):
-    repo, ctx = state.repo, state.parentctx
+    repo, ctxnode = state.repo, state.parentctxnode
+    ctx = repo[ctxnode]
     oldctx = repo[ha]
     hg.update(repo, ctx.node())
     applychanges(ui, repo, oldctx, opts)
@@ -382,17 +392,18 @@
     return fold(ui, state, ha, rollupopts)
 
 def fold(ui, state, ha, opts):
-    repo, ctx = state.repo, state.parentctx
+    repo, ctxnode = state.repo, state.parentctxnode
+    ctx = repo[ctxnode]
     oldctx = repo[ha]
     hg.update(repo, ctx.node())
     stats = applychanges(ui, repo, oldctx, opts)
     if stats and stats[3] > 0:
         raise error.InterventionRequired(
             _('Fix up the change and run hg histedit --continue'))
-    n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
+    n = repo.commit(text='fold-temp-revision %s' % ha[:12], user=oldctx.user(),
                     date=oldctx.date(), extra=oldctx.extra())
     if n is None:
-        ui.warn(_('%s: empty changeset') % node.hex(ha))
+        ui.warn(_('%s: empty changeset') % ha[:12])
         return ctx, []
     return finishfold(ui, repo, ctx, oldctx, n, opts, [])
 
@@ -438,12 +449,14 @@
     return repo[n], replacements
 
 def drop(ui, state, ha, opts):
-    repo, ctx = state.repo, state.parentctx
+    repo, ctxnode = state.repo, state.parentctxnode
+    ctx = repo[ctxnode]
     return ctx, [(repo[ha].node(), ())]
 
 
 def message(ui, state, ha, opts):
-    repo, ctx = state.repo, state.parentctx
+    repo, ctxnode = state.repo, state.parentctxnode
+    ctx = repo[ctxnode]
     oldctx = repo[ha]
     hg.update(repo, ctx.node())
     stats = applychanges(ui, repo, oldctx, opts)
@@ -501,15 +514,16 @@
 
 @command('histedit',
     [('', 'commands', '',
-      _('Read history edits from the specified file.')),
+      _('read history edits from the specified file'), _('FILE')),
      ('c', 'continue', False, _('continue an edit already in progress')),
+     ('', 'edit-plan', False, _('edit remaining actions list')),
      ('k', 'keep', False,
       _("don't strip old nodes after edit is complete")),
      ('', 'abort', False, _('abort an edit in progress')),
      ('o', 'outgoing', False, _('changesets not found in destination')),
      ('f', 'force', False,
       _('force outgoing even for unrelated repositories')),
-     ('r', 'rev', [], _('first revision to be edited'))],
+     ('r', 'rev', [], _('first revision to be edited'), _('REV'))],
      _("ANCESTOR | --outgoing [URL]"))
 def histedit(ui, repo, *freeargs, **opts):
     """interactively edit changeset history
@@ -552,6 +566,7 @@
     # basic argument incompatibility processing
     outg = opts.get('outgoing')
     cont = opts.get('continue')
+    editplan = opts.get('edit_plan')
     abort = opts.get('abort')
     force = opts.get('force')
     rules = opts.get('commands', '')
@@ -560,13 +575,18 @@
     if force and not outg:
         raise util.Abort(_('--force only allowed with --outgoing'))
     if cont:
-        if util.any((outg, abort, revs, freeargs, rules)):
+        if util.any((outg, abort, revs, freeargs, rules, editplan)):
             raise util.Abort(_('no arguments allowed with --continue'))
         goal = 'continue'
     elif abort:
-        if util.any((outg, revs, freeargs, rules)):
+        if util.any((outg, revs, freeargs, rules, editplan)):
             raise util.Abort(_('no arguments allowed with --abort'))
         goal = 'abort'
+    elif editplan:
+        if util.any((outg, revs, freeargs)):
+            raise util.Abort(_('only --commands argument allowed with'
+                               '--edit-plan'))
+        goal = 'edit-plan'
     else:
         if os.path.exists(os.path.join(repo.path, 'histedit-state')):
             raise util.Abort(_('history edit already in progress, try '
@@ -579,6 +599,10 @@
                     _('only one repo argument allowed with --outgoing'))
         else:
             revs.extend(freeargs)
+            if len(revs) == 0:
+                histeditdefault = ui.config('histedit', 'defaultrev')
+                if histeditdefault:
+                    revs.append(histeditdefault)
             if len(revs) != 1:
                 raise util.Abort(
                     _('histedit requires exactly one ancestor revision'))
@@ -589,17 +613,33 @@
 
     # rebuild state
     if goal == 'continue':
-        state = histeditstate(repo)
         state.read()
         state = bootstrapcontinue(ui, state, opts)
+    elif goal == 'edit-plan':
+        state.read()
+        if not rules:
+            comment = editcomment % (state.parentctx, node.short(state.topmost))
+            rules = ruleeditor(repo, ui, state.rules, comment)
+        else:
+            if rules == '-':
+                f = sys.stdin
+            else:
+                f = open(rules)
+            rules = f.read()
+            f.close()
+        rules = [l for l in (r.strip() for r in rules.splitlines())
+                 if l and not l.startswith('#')]
+        rules = verifyrules(rules, repo, [repo[c] for [_a, c] in state.rules])
+        state.rules = rules
+        state.write()
+        return
     elif goal == 'abort':
-        state = histeditstate(repo)
         state.read()
         mapping, tmpnodes, leafs, _ntm = processreplacement(state)
         ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
         # check whether we should update away
         parentnodes = [c.node() for c in repo[None].parents()]
-        for n in leafs | set([state.parentctx.node()]):
+        for n in leafs | set([state.parentctxnode]):
             if n in parentnodes:
                 hg.clean(repo, state.topmost)
                 break
@@ -634,16 +674,8 @@
 
         ctxs = [repo[r] for r in revs]
         if not rules:
-            rules = '\n'.join([makedesc(c) for c in ctxs])
-            rules += '\n\n'
-            rules += editcomment % (node.short(root), node.short(topmost))
-            rules = ui.edit(rules, ui.username())
-            # Save edit rules in .hg/histedit-last-edit.txt in case
-            # the user needs to ask for help after something
-            # surprising happens.
-            f = open(repo.join('histedit-last-edit.txt'), 'w')
-            f.write(rules)
-            f.close()
+            comment = editcomment % (node.short(root), node.short(topmost))
+            rules = ruleeditor(repo, ui, [['pick', c] for c in ctxs], comment)
         else:
             if rules == '-':
                 f = sys.stdin
@@ -655,9 +687,9 @@
                  if l and not l.startswith('#')]
         rules = verifyrules(rules, repo, ctxs)
 
-        parentctx = repo[root].parents()[0]
+        parentctxnode = repo[root].parents()[0].node()
 
-        state.parentctx = parentctx
+        state.parentctxnode = parentctxnode
         state.rules = rules
         state.keep = keep
         state.topmost = topmost
@@ -666,12 +698,14 @@
     while state.rules:
         state.write()
         action, ha = state.rules.pop(0)
-        ui.debug('histedit: processing %s %s\n' % (action, ha))
+        ui.debug('histedit: processing %s %s\n' % (action, ha[:12]))
         actfunc = actiontable[action]
-        state.parentctx, replacement_ = actfunc(ui, state, ha, opts)
+        parentctx, replacement_ = actfunc(ui, state, ha, opts)
+        state.parentctxnode = parentctx.node()
         state.replacements.extend(replacement_)
+    state.write()
 
-    hg.update(repo, state.parentctx.node())
+    hg.update(repo, state.parentctxnode)
 
     mapping, tmpnodes, created, ntm = processreplacement(state)
     if mapping:
@@ -724,7 +758,8 @@
     return newchildren
 
 def bootstrapcontinue(ui, state, opts):
-    repo, parentctx = state.repo, state.parentctx
+    repo, parentctxnode = state.repo, state.parentctxnode
+    parentctx = repo[parentctxnode]
     action, currentnode = state.rules.pop(0)
     ctx = repo[currentnode]
 
@@ -736,7 +771,7 @@
     if s.modified or s.added or s.removed or s.deleted:
         # prepare the message for the commit to comes
         if action in ('f', 'fold', 'r', 'roll'):
-            message = 'fold-temp-revision %s' % currentnode
+            message = 'fold-temp-revision %s' % currentnode[:12]
         else:
             message = ctx.description()
         editopt = action in ('e', 'edit', 'm', 'mess')
@@ -780,7 +815,7 @@
         # otherwise update "parentctx" before proceeding to further operation
         parentctx = repo[newchildren[-1]]
 
-    state.parentctx = parentctx
+    state.parentctxnode = parentctx.node()
     state.replacements.extend(replacements)
 
     return state
@@ -801,19 +836,41 @@
             raise util.Abort(_('cannot edit immutable changeset: %s') % root)
     return [c.node() for c in ctxs]
 
-def makedesc(c):
-    """build a initial action line for a ctx `c`
+def makedesc(repo, action, rev):
+    """build a initial action line for a ctx
 
     line are in the form:
 
-      pick <hash> <rev> <summary>
+      <action> <hash> <rev> <summary>
     """
+    ctx = repo[rev]
     summary = ''
-    if c.description():
-        summary = c.description().splitlines()[0]
-    line = 'pick %s %d %s' % (c, c.rev(), summary)
+    if ctx.description():
+        summary = ctx.description().splitlines()[0]
+    line = '%s %s %d %s' % (action, ctx, ctx.rev(), summary)
     # trim to 80 columns so it's not stupidly wide in my editor
-    return util.ellipsis(line, 80)
+    maxlen = repo.ui.configint('histedit', 'linelen', default=80)
+    maxlen = max(maxlen, 22) # avoid truncating hash
+    return util.ellipsis(line, maxlen)
+
+def ruleeditor(repo, ui, rules, editcomment=""):
+    """open an editor to edit rules
+
+    rules are in the format [ [act, ctx], ...] like in state.rules
+    """
+    rules = '\n'.join([makedesc(repo, act, rev) for [act, rev] in rules])
+    rules += '\n\n'
+    rules += editcomment
+    rules = ui.edit(rules, ui.username())
+
+    # Save edit rules in .hg/histedit-last-edit.txt in case
+    # the user needs to ask for help after something
+    # surprising happens.
+    f = open(repo.join('histedit-last-edit.txt'), 'w')
+    f.write(rules)
+    f.close()
+
+    return rules
 
 def verifyrules(rules, repo, ctxs):
     """Verify that there exists exactly one edit rule per given changeset.
@@ -822,7 +879,7 @@
     or a rule on a changeset outside of the user-given range.
     """
     parsed = []
-    expected = set(str(c) for c in ctxs)
+    expected = set(c.hex() for c in ctxs)
     seen = set()
     for r in rules:
         if ' ' not in r:
@@ -830,22 +887,24 @@
         action, rest = r.split(' ', 1)
         ha = rest.strip().split(' ', 1)[0]
         try:
-            ha = str(repo[ha])  # ensure its a short hash
+            ha = repo[ha].hex()
         except error.RepoError:
-            raise util.Abort(_('unknown changeset %s listed') % ha)
+            raise util.Abort(_('unknown changeset %s listed') % ha[:12])
         if ha not in expected:
             raise util.Abort(
                 _('may not use changesets other than the ones listed'))
         if ha in seen:
-            raise util.Abort(_('duplicated command for changeset %s') % ha)
+            raise util.Abort(_('duplicated command for changeset %s') %
+                    ha[:12])
         seen.add(ha)
         if action not in actiontable:
             raise util.Abort(_('unknown action "%s"') % action)
         parsed.append([action, ha])
     missing = sorted(expected - seen)  # sort to stabilize output
     if missing:
-        raise util.Abort(_('missing rules for changeset %s') % missing[0],
-                         hint=_('do you want to use the drop action?'))
+        raise util.Abort(_('missing rules for changeset %s') %
+                missing[0][:12],
+                hint=_('do you want to use the drop action?'))
     return parsed
 
 def processreplacement(state):
@@ -965,6 +1024,22 @@
     finally:
         release(lock)
 
+def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
+    if isinstance(nodelist, str):
+        nodelist = [nodelist]
+    if os.path.exists(os.path.join(repo.path, 'histedit-state')):
+        state = histeditstate(repo)
+        state.read()
+        histedit_nodes = set([ctx for (action, ctx) in state.rules])
+        strip_nodes = set([repo[n].hex() for n in nodelist])
+        common_nodes = histedit_nodes & strip_nodes
+        if common_nodes:
+            raise util.Abort(_("histedit in progress, can't strip %s")
+                             % ', '.join(node.short(x) for x in common_nodes))
+    return orig(ui, repo, nodelist, *args, **kwargs)
+
+extensions.wrapfunction(repair, 'strip', stripwrapper)
+
 def summaryhook(ui, repo):
     if not os.path.exists(repo.join('histedit-state')):
         return
--- a/hgext/keyword.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/keyword.py	Fri Mar 13 17:55:04 2015 -0500
@@ -737,13 +737,7 @@
     extensions.wrapfunction(patch, 'diff', kw_diff)
     extensions.wrapfunction(cmdutil, 'amend', kw_amend)
     extensions.wrapfunction(cmdutil, 'copy', kw_copy)
+    extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
     for c in 'annotate changeset rev filediff diff'.split():
         extensions.wrapfunction(webcommands, c, kwweb_skip)
-    for name in recordextensions.split():
-        try:
-            record = extensions.find(name)
-            extensions.wrapfunction(record, 'dorecord', kw_dorecord)
-        except KeyError:
-            pass
-
     repo.__class__ = kwrepo
--- a/hgext/largefiles/lfcommands.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/largefiles/lfcommands.py	Fri Mar 13 17:55:04 2015 -0500
@@ -471,9 +471,8 @@
                     if lfile not in repo[None]: # not switched to normal file
                         util.unlinkpath(abslfile, ignoremissing=True)
                     # use normallookup() to allocate an entry in largefiles
-                    # dirstate, because lack of it misleads
-                    # lfilesrepo.status() into recognition that such cache
-                    # missing files are removed.
+                    # dirstate to prevent lfilesrepo.status() from reporting
+                    # missing files as removed.
                     lfdirstate.normallookup(lfile)
                     update[lfile] = expecthash
             else:
--- a/hgext/largefiles/overrides.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/largefiles/overrides.py	Fri Mar 13 17:55:04 2015 -0500
@@ -304,17 +304,47 @@
             return matchandpats
 
         pats = set(p)
-        # TODO: handling of patterns in both cases below
+
+        def fixpats(pat, tostandin=lfutil.standin):
+            kindpat = match_._patsplit(pat, None)
+
+            if kindpat[0] is not None:
+                return kindpat[0] + ':' + tostandin(kindpat[1])
+            return tostandin(kindpat[1])
+
         if m._cwd:
-            if os.path.isabs(m._cwd):
-                # TODO: handle largefile magic when invoked from other cwd
-                return matchandpats
-            back = (m._cwd.count('/') + 1) * '../'
-            pats.update(back + lfutil.standin(m._cwd + '/' + f) for f in p)
+            hglf = lfutil.shortname
+            back = util.pconvert(m.rel(hglf)[:-len(hglf)])
+
+            def tostandin(f):
+                # The file may already be a standin, so trucate the back
+                # prefix and test before mangling it.  This avoids turning
+                # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
+                if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
+                    return f
+
+                # An absolute path is from outside the repo, so truncate the
+                # path to the root before building the standin.  Otherwise cwd
+                # is somewhere in the repo, relative to root, and needs to be
+                # prepended before building the standin.
+                if os.path.isabs(m._cwd):
+                    f = f[len(back):]
+                else:
+                    f = m._cwd + '/' + f
+                return back + lfutil.standin(f)
+
+            pats.update(fixpats(f, tostandin) for f in p)
         else:
-            pats.update(lfutil.standin(f) for f in p)
+            def tostandin(f):
+                if lfutil.splitstandin(f):
+                    return f
+                return lfutil.standin(f)
+            pats.update(fixpats(f, tostandin) for f in p)
 
         for i in range(0, len(m._files)):
+            # Don't add '.hglf' to m.files, since that is already covered by '.'
+            if m._files[i] == '.':
+                continue
             standin = lfutil.standin(m._files[i])
             # If the "standin" is a directory, append instead of replace to
             # support naming a directory on the command line with only
@@ -325,7 +355,6 @@
             elif m._files[i] not in repo[ctx.node()] \
                     and repo.wvfs.isdir(standin):
                 m._files.append(standin)
-            pats.add(standin)
 
         m._fmap = set(m._files)
         m._always = False
@@ -338,6 +367,7 @@
             return r
         m.matchfn = lfmatchfn
 
+        ui.debug('updated patterns: %s\n' % sorted(pats))
         return m, pats
 
     # For hg log --patch, the match object is used in two different senses:
@@ -548,6 +578,15 @@
         repo.wwrite(fcd.path(), fco.data(), fco.flags())
     return 0
 
+def copiespathcopies(orig, ctx1, ctx2):
+    copies = orig(ctx1, ctx2)
+    updated = {}
+
+    for k, v in copies.iteritems():
+        updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
+
+    return updated
+
 # Copy first changes the matchers to match standins instead of
 # largefiles.  Then it overrides util.copyfile in that function it
 # checks if the destination largefile already exists. It also keeps a
@@ -559,16 +598,6 @@
         # this isn't legal, let the original function deal with it
         return orig(ui, repo, pats, opts, rename)
 
-    def makestandin(relpath):
-        path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
-        return os.path.join(repo.wjoin(lfutil.standin(path)))
-
-    fullpats = scmutil.expandpats(pats)
-    dest = fullpats[-1]
-
-    if os.path.isdir(dest):
-        if not os.path.isdir(makestandin(dest)):
-            os.makedirs(makestandin(dest))
     # This could copy both lfiles and normal files in one command,
     # but we don't want to do that. First replace their matcher to
     # only match normal files and run it, then replace it to just
@@ -595,6 +624,17 @@
     except OSError:
         return result
 
+    def makestandin(relpath):
+        path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
+        return os.path.join(repo.wjoin(lfutil.standin(path)))
+
+    fullpats = scmutil.expandpats(pats)
+    dest = fullpats[-1]
+
+    if os.path.isdir(dest):
+        if not os.path.isdir(makestandin(dest)):
+            os.makedirs(makestandin(dest))
+
     try:
         try:
             # When we call orig below it creates the standins but we don't add
@@ -715,10 +755,17 @@
                 default='relpath'):
             match = oldmatch(ctx, pats, opts, globbed, default)
             m = copy.copy(match)
+
+            # revert supports recursing into subrepos, and though largefiles
+            # currently doesn't work correctly in that case, this match is
+            # called, so the lfdirstate above may not be the correct one for
+            # this invocation of match.
+            lfdirstate = lfutil.openlfdirstate(ctx._repo.ui, ctx._repo, False)
+
             def tostandin(f):
                 if lfutil.standin(f) in ctx:
                     return lfutil.standin(f)
-                elif lfutil.standin(f) in repo[None]:
+                elif lfutil.standin(f) in repo[None] or lfdirstate[f] == 'r':
                     return None
                 return f
             m._files = [tostandin(f) for f in m._files]
@@ -820,6 +867,14 @@
         sourcerepo, destrepo = result
         repo = destrepo.local()
 
+        # If largefiles is required for this repo, permanently enable it locally
+        if 'largefiles' in repo.requirements:
+            fp = repo.vfs('hgrc', 'a', text=True)
+            try:
+                fp.write('\n[extensions]\nlargefiles=\n')
+            finally:
+                fp.close()
+
         # Caching is implicitly limited to 'rev' option, since the dest repo was
         # truncated at that point.  The user may expect a download count with
         # this option, so attempt whether or not this is a largefile repo.
@@ -845,7 +900,7 @@
         repo._lfcommithooks.pop()
 
 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
-            prefix=None, mtime=None, subrepos=None):
+            prefix='', mtime=None, subrepos=None):
     # No need to lock because we are only reading history and
     # largefile caches, neither of which are modified.
     lfcommands.cachelfiles(repo.ui, repo, node)
--- a/hgext/largefiles/reposetup.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/largefiles/reposetup.py	Fri Mar 13 17:55:04 2015 -0500
@@ -10,7 +10,7 @@
 import copy
 import os
 
-from mercurial import error, manifest, match as match_, util
+from mercurial import error, match as match_, util
 from mercurial.i18n import _
 from mercurial import scmutil, localrepo
 
@@ -38,17 +38,18 @@
         def __getitem__(self, changeid):
             ctx = super(lfilesrepo, self).__getitem__(changeid)
             if self.lfstatus:
-                class lfilesmanifestdict(manifest.manifestdict):
-                    def __contains__(self, filename):
-                        orig = super(lfilesmanifestdict, self).__contains__
-                        return orig(filename) or orig(lfutil.standin(filename))
                 class lfilesctx(ctx.__class__):
                     def files(self):
                         filenames = super(lfilesctx, self).files()
                         return [lfutil.splitstandin(f) or f for f in filenames]
                     def manifest(self):
                         man1 = super(lfilesctx, self).manifest()
-                        man1.__class__ = lfilesmanifestdict
+                        class lfilesmanifest(man1.__class):
+                            def __contains__(self, filename):
+                                orig = super(lfilesmanifest, self).__contains__
+                                return (orig(filename) or
+                                        orig(lfutil.standin(filename)))
+                        man1.__class__ = lfilesmanifest
                         return man1
                     def filectx(self, path, fileid=None, filelog=None):
                         orig = super(lfilesctx, self).filectx
@@ -329,10 +330,10 @@
                             actualfiles.append(lf)
                             if not matcheddir:
                                 # There may still be normal files in the dir, so
-                                # make sure a directory is in the list, which
-                                # forces status to walk and call the match
-                                # function on the matcher.  Windows does NOT
-                                # require this.
+                                # add a directory to the list, which
+                                # forces status/dirstate to walk all files and
+                                # call the match function on the matcher, even
+                                # on case sensitive filesystems.
                                 actualfiles.append('.')
                                 matcheddir = True
                 # Nothing in dir, so readd it
--- a/hgext/largefiles/uisetup.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/largefiles/uisetup.py	Fri Mar 13 17:55:04 2015 -0500
@@ -9,7 +9,7 @@
 '''setup for largefiles extension: uisetup'''
 
 from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
-    httppeer, merge, scmutil, sshpeer, wireproto, revset, subrepo
+    httppeer, merge, scmutil, sshpeer, wireproto, revset, subrepo, copies
 from mercurial.i18n import _
 from mercurial.hgweb import hgweb_mod, webcommands
 
@@ -37,6 +37,8 @@
     extensions.wrapfunction(cmdutil, 'remove', overrides.cmdutilremove)
     extensions.wrapfunction(cmdutil, 'forget', overrides.cmdutilforget)
 
+    extensions.wrapfunction(copies, 'pathcopies', overrides.copiespathcopies)
+
     # Subrepos call status function
     entry = extensions.wrapcommand(commands.table, 'status',
                                    overrides.overridestatus)
--- a/hgext/pager.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/pager.py	Fri Mar 13 17:55:04 2015 -0500
@@ -149,6 +149,8 @@
                     usepager = True
                     break
 
+        setattr(ui, 'pageractive', usepager)
+
         if usepager:
             ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
             ui.setconfig('ui', 'interactive', False, 'pager')
@@ -157,7 +159,12 @@
             _runpager(ui, p)
         return orig(ui, options, cmd, cmdfunc)
 
-    extensions.wrapfunction(dispatch, '_runcommand', pagecmd)
+    # Wrap dispatch._runcommand after color is loaded so color can see
+    # ui.pageractive. Otherwise, if we loaded first, color's wrapped
+    # dispatch._runcommand would run without having access to ui.pageractive.
+    def afterloaded(loaded):
+        extensions.wrapfunction(dispatch, '_runcommand', pagecmd)
+    extensions.afterloaded('color', afterloaded)
 
 def extsetup(ui):
     commands.globalopts.append(
--- a/hgext/record.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/hgext/record.py	Fri Mar 13 17:55:04 2015 -0500
@@ -8,409 +8,18 @@
 '''commands to interactively select changes for commit/qrefresh'''
 
 from mercurial.i18n import _
-from mercurial import cmdutil, commands, extensions, hg, patch
+from mercurial import cmdutil, commands, extensions
 from mercurial import util
-import copy, cStringIO, errno, os, re, shutil, tempfile
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
 testedwith = 'internal'
 
-lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
-
-def scanpatch(fp):
-    """like patch.iterhunks, but yield different events
-
-    - ('file',    [header_lines + fromfile + tofile])
-    - ('context', [context_lines])
-    - ('hunk',    [hunk_lines])
-    - ('range',   (-start,len, +start,len, proc))
-    """
-    lr = patch.linereader(fp)
-
-    def scanwhile(first, p):
-        """scan lr while predicate holds"""
-        lines = [first]
-        while True:
-            line = lr.readline()
-            if not line:
-                break
-            if p(line):
-                lines.append(line)
-            else:
-                lr.push(line)
-                break
-        return lines
-
-    while True:
-        line = lr.readline()
-        if not line:
-            break
-        if line.startswith('diff --git a/') or line.startswith('diff -r '):
-            def notheader(line):
-                s = line.split(None, 1)
-                return not s or s[0] not in ('---', 'diff')
-            header = scanwhile(line, notheader)
-            fromfile = lr.readline()
-            if fromfile.startswith('---'):
-                tofile = lr.readline()
-                header += [fromfile, tofile]
-            else:
-                lr.push(fromfile)
-            yield 'file', header
-        elif line[0] == ' ':
-            yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
-        elif line[0] in '-+':
-            yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
-        else:
-            m = lines_re.match(line)
-            if m:
-                yield 'range', m.groups()
-            else:
-                yield 'other', line
-
-class header(object):
-    """patch header
-
-    XXX shouldn't we move this to mercurial/patch.py ?
-    """
-    diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
-    diff_re = re.compile('diff -r .* (.*)$')
-    allhunks_re = re.compile('(?:index|new file|deleted file) ')
-    pretty_re = re.compile('(?:new file|deleted file) ')
-    special_re = re.compile('(?:index|new|deleted|copy|rename) ')
-
-    def __init__(self, header):
-        self.header = header
-        self.hunks = []
-
-    def binary(self):
-        return util.any(h.startswith('index ') for h in self.header)
-
-    def pretty(self, fp):
-        for h in self.header:
-            if h.startswith('index '):
-                fp.write(_('this modifies a binary file (all or nothing)\n'))
-                break
-            if self.pretty_re.match(h):
-                fp.write(h)
-                if self.binary():
-                    fp.write(_('this is a binary file\n'))
-                break
-            if h.startswith('---'):
-                fp.write(_('%d hunks, %d lines changed\n') %
-                         (len(self.hunks),
-                          sum([max(h.added, h.removed) for h in self.hunks])))
-                break
-            fp.write(h)
-
-    def write(self, fp):
-        fp.write(''.join(self.header))
-
-    def allhunks(self):
-        return util.any(self.allhunks_re.match(h) for h in self.header)
-
-    def files(self):
-        match = self.diffgit_re.match(self.header[0])
-        if match:
-            fromfile, tofile = match.groups()
-            if fromfile == tofile:
-                return [fromfile]
-            return [fromfile, tofile]
-        else:
-            return self.diff_re.match(self.header[0]).groups()
-
-    def filename(self):
-        return self.files()[-1]
-
-    def __repr__(self):
-        return '<header %s>' % (' '.join(map(repr, self.files())))
-
-    def special(self):
-        return util.any(self.special_re.match(h) for h in self.header)
-
-def countchanges(hunk):
-    """hunk -> (n+,n-)"""
-    add = len([h for h in hunk if h[0] == '+'])
-    rem = len([h for h in hunk if h[0] == '-'])
-    return add, rem
-
-class hunk(object):
-    """patch hunk
-
-    XXX shouldn't we merge this with patch.hunk ?
-    """
-    maxcontext = 3
-
-    def __init__(self, header, fromline, toline, proc, before, hunk, after):
-        def trimcontext(number, lines):
-            delta = len(lines) - self.maxcontext
-            if False and delta > 0:
-                return number + delta, lines[:self.maxcontext]
-            return number, lines
-
-        self.header = header
-        self.fromline, self.before = trimcontext(fromline, before)
-        self.toline, self.after = trimcontext(toline, after)
-        self.proc = proc
-        self.hunk = hunk
-        self.added, self.removed = countchanges(self.hunk)
-
-    def write(self, fp):
-        delta = len(self.before) + len(self.after)
-        if self.after and self.after[-1] == '\\ No newline at end of file\n':
-            delta -= 1
-        fromlen = delta + self.removed
-        tolen = delta + self.added
-        fp.write('@@ -%d,%d +%d,%d @@%s\n' %
-                 (self.fromline, fromlen, self.toline, tolen,
-                  self.proc and (' ' + self.proc)))
-        fp.write(''.join(self.before + self.hunk + self.after))
-
-    pretty = write
-
-    def filename(self):
-        return self.header.filename()
-
-    def __repr__(self):
-        return '<hunk %r@%d>' % (self.filename(), self.fromline)
-
-def parsepatch(fp):
-    """patch -> [] of headers -> [] of hunks """
-    class parser(object):
-        """patch parsing state machine"""
-        def __init__(self):
-            self.fromline = 0
-            self.toline = 0
-            self.proc = ''
-            self.header = None
-            self.context = []
-            self.before = []
-            self.hunk = []
-            self.headers = []
-
-        def addrange(self, limits):
-            fromstart, fromend, tostart, toend, proc = limits
-            self.fromline = int(fromstart)
-            self.toline = int(tostart)
-            self.proc = proc
-
-        def addcontext(self, context):
-            if self.hunk:
-                h = hunk(self.header, self.fromline, self.toline, self.proc,
-                         self.before, self.hunk, context)
-                self.header.hunks.append(h)
-                self.fromline += len(self.before) + h.removed
-                self.toline += len(self.before) + h.added
-                self.before = []
-                self.hunk = []
-                self.proc = ''
-            self.context = context
-
-        def addhunk(self, hunk):
-            if self.context:
-                self.before = self.context
-                self.context = []
-            self.hunk = hunk
-
-        def newfile(self, hdr):
-            self.addcontext([])
-            h = header(hdr)
-            self.headers.append(h)
-            self.header = h
-
-        def addother(self, line):
-            pass # 'other' lines are ignored
-
-        def finished(self):
-            self.addcontext([])
-            return self.headers
-
-        transitions = {
-            'file': {'context': addcontext,
-                     'file': newfile,
-                     'hunk': addhunk,
-                     'range': addrange},
-            'context': {'file': newfile,
-                        'hunk': addhunk,
-                        'range': addrange,
-                        'other': addother},
-            'hunk': {'context': addcontext,
-                     'file': newfile,
-                     'range': addrange},
-            'range': {'context': addcontext,
-                      'hunk': addhunk},
-            'other': {'other': addother},
-            }
-
-    p = parser()
-
-    state = 'context'
-    for newstate, data in scanpatch(fp):
-        try:
-            p.transitions[state][newstate](p, data)
-        except KeyError:
-            raise patch.PatchError('unhandled transition: %s -> %s' %
-                                   (state, newstate))
-        state = newstate
-    return p.finished()
-
-def filterpatch(ui, headers):
-    """Interactively filter patch chunks into applied-only chunks"""
-
-    def prompt(skipfile, skipall, query, chunk):
-        """prompt query, and process base inputs
-
-        - y/n for the rest of file
-        - y/n for the rest
-        - ? (help)
-        - q (quit)
-
-        Return True/False and possibly updated skipfile and skipall.
-        """
-        newpatches = None
-        if skipall is not None:
-            return skipall, skipfile, skipall, newpatches
-        if skipfile is not None:
-            return skipfile, skipfile, skipall, newpatches
-        while True:
-            resps = _('[Ynesfdaq?]'
-                      '$$ &Yes, record this change'
-                      '$$ &No, skip this change'
-                      '$$ &Edit this change manually'
-                      '$$ &Skip remaining changes to this file'
-                      '$$ Record remaining changes to this &file'
-                      '$$ &Done, skip remaining changes and files'
-                      '$$ Record &all changes to all remaining files'
-                      '$$ &Quit, recording no changes'
-                      '$$ &? (display help)')
-            r = ui.promptchoice("%s %s" % (query, resps))
-            ui.write("\n")
-            if r == 8: # ?
-                for c, t in ui.extractchoices(resps)[1]:
-                    ui.write('%s - %s\n' % (c, t.lower()))
-                continue
-            elif r == 0: # yes
-                ret = True
-            elif r == 1: # no
-                ret = False
-            elif r == 2: # Edit patch
-                if chunk is None:
-                    ui.write(_('cannot edit patch for whole file'))
-                    ui.write("\n")
-                    continue
-                if chunk.header.binary():
-                    ui.write(_('cannot edit patch for binary file'))
-                    ui.write("\n")
-                    continue
-                # Patch comment based on the Git one (based on comment at end of
-                # http://mercurial.selenic.com/wiki/RecordExtension)
-                phelp = '---' + _("""
-To remove '-' lines, make them ' ' lines (context).
-To remove '+' lines, delete them.
-Lines starting with # will be removed from the patch.
-
-If the patch applies cleanly, the edited hunk will immediately be
-added to the record list. If it does not apply cleanly, a rejects
-file will be generated: you can use that when you try again. If
-all lines of the hunk are removed, then the edit is aborted and
-the hunk is left unchanged.
-""")
-                (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
-                        suffix=".diff", text=True)
-                ncpatchfp = None
-                try:
-                    # Write the initial patch
-                    f = os.fdopen(patchfd, "w")
-                    chunk.header.write(f)
-                    chunk.write(f)
-                    f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
-                    f.close()
-                    # Start the editor and wait for it to complete
-                    editor = ui.geteditor()
-                    ui.system("%s \"%s\"" % (editor, patchfn),
-                              environ={'HGUSER': ui.username()},
-                              onerr=util.Abort, errprefix=_("edit failed"))
-                    # Remove comment lines
-                    patchfp = open(patchfn)
-                    ncpatchfp = cStringIO.StringIO()
-                    for line in patchfp:
-                        if not line.startswith('#'):
-                            ncpatchfp.write(line)
-                    patchfp.close()
-                    ncpatchfp.seek(0)
-                    newpatches = parsepatch(ncpatchfp)
-                finally:
-                    os.unlink(patchfn)
-                    del ncpatchfp
-                # Signal that the chunk shouldn't be applied as-is, but
-                # provide the new patch to be used instead.
-                ret = False
-            elif r == 3: # Skip
-                ret = skipfile = False
-            elif r == 4: # file (Record remaining)
-                ret = skipfile = True
-            elif r == 5: # done, skip remaining
-                ret = skipall = False
-            elif r == 6: # all
-                ret = skipall = True
-            elif r == 7: # quit
-                raise util.Abort(_('user quit'))
-            return ret, skipfile, skipall, newpatches
-
-    seen = set()
-    applied = {}        # 'filename' -> [] of chunks
-    skipfile, skipall = None, None
-    pos, total = 1, sum(len(h.hunks) for h in headers)
-    for h in headers:
-        pos += len(h.hunks)
-        skipfile = None
-        fixoffset = 0
-        hdr = ''.join(h.header)
-        if hdr in seen:
-            continue
-        seen.add(hdr)
-        if skipall is None:
-            h.pretty(ui)
-        msg = (_('examine changes to %s?') %
-               _(' and ').join("'%s'" % f for f in h.files()))
-        r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
-        if not r:
-            continue
-        applied[h.filename()] = [h]
-        if h.allhunks():
-            applied[h.filename()] += h.hunks
-            continue
-        for i, chunk in enumerate(h.hunks):
-            if skipfile is None and skipall is None:
-                chunk.pretty(ui)
-            if total == 1:
-                msg = _("record this change to '%s'?") % chunk.filename()
-            else:
-                idx = pos - len(h.hunks) + i
-                msg = _("record change %d/%d to '%s'?") % (idx, total,
-                                                           chunk.filename())
-            r, skipfile, skipall, newpatches = prompt(skipfile,
-                    skipall, msg, chunk)
-            if r:
-                if fixoffset:
-                    chunk = copy.copy(chunk)
-                    chunk.toline += fixoffset
-                applied[chunk.filename()].append(chunk)
-            elif newpatches is not None:
-                for newpatch in newpatches:
-                    for newhunk in newpatch.hunks:
-                        if fixoffset:
-                            newhunk.toline += fixoffset
-                        applied[newhunk.filename()].append(newhunk)
-            else:
-                fixoffset += chunk.removed - chunk.added
-    return sum([h for h in applied.itervalues()
-               if h[0].special() or len(h) > 1], [])
 
 @command("record",
          # same options as commit + white space diff options
-         commands.table['^commit|ci'][1][:] + commands.diffwsopts,
+        [c for c in commands.table['^commit|ci'][1][:]
+            if c[1] != "interactive"] + commands.diffwsopts,
           _('hg record [OPTION]... [FILE]...'))
 def record(ui, repo, *pats, **opts):
     '''interactively select changes to commit
@@ -440,7 +49,7 @@
 
     This command is not available when committing a merge.'''
 
-    dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
+    cmdutil.dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
 
 def qrefresh(origfn, ui, repo, *pats, **opts):
     if not opts['interactive']:
@@ -456,7 +65,7 @@
         mq.refresh(ui, repo, **opts)
 
     # backup all changed files
-    dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
+    cmdutil.dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
 
 # This command registration is replaced during uisetup().
 @command('qrecord',
@@ -481,162 +90,13 @@
         opts['checkname'] = False
         mq.new(ui, repo, patch, *pats, **opts)
 
-    dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
+    cmdutil.dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
 
 def qnew(origfn, ui, repo, patch, *args, **opts):
     if opts['interactive']:
         return qrecord(ui, repo, patch, *args, **opts)
     return origfn(ui, repo, patch, *args, **opts)
 
-def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
-    if not ui.interactive():
-        raise util.Abort(_('running non-interactively, use %s instead') %
-                         cmdsuggest)
-
-    # make sure username is set before going interactive
-    if not opts.get('user'):
-        ui.username() # raise exception, username not provided
-
-    def recordfunc(ui, repo, message, match, opts):
-        """This is generic record driver.
-
-        Its job is to interactively filter local changes, and
-        accordingly prepare working directory into a state in which the
-        job can be delegated to a non-interactive commit command such as
-        'commit' or 'qrefresh'.
-
-        After the actual job is done by non-interactive command, the
-        working directory is restored to its original state.
-
-        In the end we'll record interesting changes, and everything else
-        will be left in place, so the user can continue working.
-        """
-
-        cmdutil.checkunfinished(repo, commit=True)
-        merge = len(repo[None].parents()) > 1
-        if merge:
-            raise util.Abort(_('cannot partially commit a merge '
-                               '(use "hg commit" instead)'))
-
-        status = repo.status(match=match)
-        diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
-        diffopts.nodates = True
-        diffopts.git = True
-        chunks = patch.diff(repo, changes=status, opts=diffopts)
-        fp = cStringIO.StringIO()
-        fp.write(''.join(chunks))
-        fp.seek(0)
-
-        # 1. filter patch, so we have intending-to apply subset of it
-        try:
-            chunks = filterpatch(ui, parsepatch(fp))
-        except patch.PatchError, err:
-            raise util.Abort(_('error parsing patch: %s') % err)
-
-        del fp
-
-        contenders = set()
-        for h in chunks:
-            try:
-                contenders.update(set(h.files()))
-            except AttributeError:
-                pass
-
-        changed = status.modified + status.added + status.removed
-        newfiles = [f for f in changed if f in contenders]
-        if not newfiles:
-            ui.status(_('no changes to record\n'))
-            return 0
-
-        modified = set(status.modified)
-
-        # 2. backup changed files, so we can restore them in the end
-        if backupall:
-            tobackup = changed
-        else:
-            tobackup = [f for f in newfiles if f in modified]
-
-        backups = {}
-        if tobackup:
-            backupdir = repo.join('record-backups')
-            try:
-                os.mkdir(backupdir)
-            except OSError, err:
-                if err.errno != errno.EEXIST:
-                    raise
-        try:
-            # backup continues
-            for f in tobackup:
-                fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
-                                               dir=backupdir)
-                os.close(fd)
-                ui.debug('backup %r as %r\n' % (f, tmpname))
-                util.copyfile(repo.wjoin(f), tmpname)
-                shutil.copystat(repo.wjoin(f), tmpname)
-                backups[f] = tmpname
-
-            fp = cStringIO.StringIO()
-            for c in chunks:
-                if c.filename() in backups:
-                    c.write(fp)
-            dopatch = fp.tell()
-            fp.seek(0)
-
-            # 3a. apply filtered patch to clean repo  (clean)
-            if backups:
-                hg.revert(repo, repo.dirstate.p1(),
-                          lambda key: key in backups)
-
-            # 3b. (apply)
-            if dopatch:
-                try:
-                    ui.debug('applying patch\n')
-                    ui.debug(fp.getvalue())
-                    patch.internalpatch(ui, repo, fp, 1, eolmode=None)
-                except patch.PatchError, err:
-                    raise util.Abort(str(err))
-            del fp
-
-            # 4. We prepared working directory according to filtered
-            #    patch. Now is the time to delegate the job to
-            #    commit/qrefresh or the like!
-
-            # Make all of the pathnames absolute.
-            newfiles = [repo.wjoin(nf) for nf in newfiles]
-            commitfunc(ui, repo, *newfiles, **opts)
-
-            return 0
-        finally:
-            # 5. finally restore backed-up files
-            try:
-                for realname, tmpname in backups.iteritems():
-                    ui.debug('restoring %r to %r\n' % (tmpname, realname))
-                    util.copyfile(tmpname, repo.wjoin(realname))
-                    # Our calls to copystat() here and above are a
-                    # hack to trick any editors that have f open that
-                    # we haven't modified them.
-                    #
-                    # Also note that this racy as an editor could
-                    # notice the file's mtime before we've finished
-                    # writing it.
-                    shutil.copystat(tmpname, repo.wjoin(realname))
-                    os.unlink(tmpname)
-                if tobackup:
-                    os.rmdir(backupdir)
-            except OSError:
-                pass
-
-    # wrap ui.write so diff output can be labeled/colorized
-    def wrapwrite(orig, *args, **kw):
-        label = kw.pop('label', '')
-        for chunk, l in patch.difflabel(lambda: args):
-            orig(chunk, label=label + l)
-    oldwrite = ui.write
-    extensions.wrapfunction(ui, 'write', wrapwrite)
-    try:
-        return cmdutil.commit(ui, repo, recordfunc, pats, opts)
-    finally:
-        ui.write = oldwrite
 
 def uisetup(ui):
     try:
--- a/mercurial/archival.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/archival.py	Fri Mar 13 17:55:04 2015 -0500
@@ -230,7 +230,7 @@
     }
 
 def archive(repo, dest, node, kind, decode=True, matchfn=None,
-            prefix=None, mtime=None, subrepos=False):
+            prefix='', mtime=None, subrepos=False):
     '''create archive of repo as it was at node.
 
     dest can be name of directory, name of archive file, or file
--- a/mercurial/bookmarks.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/bookmarks.py	Fri Mar 13 17:55:04 2015 -0500
@@ -456,5 +456,5 @@
     elif repo.obsstore:
         return new.node() in obsolete.foreground(repo, [old.node()])
     else:
-        # still an independent clause as it is lazyer (and therefore faster)
+        # still an independent clause as it is lazier (and therefore faster)
         return old.descendant(new)
--- a/mercurial/branchmap.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/branchmap.py	Fri Mar 13 17:55:04 2015 -0500
@@ -411,9 +411,6 @@
             try:
                 if self._rbcnamescount != 0:
                     f = repo.vfs.open(_rbcnames, 'ab')
-                    # The position after open(x, 'a') is implementation defined-
-                    # see issue3543.  SEEK_END was added in 2.5
-                    f.seek(0, 2) #os.SEEK_END
                     if f.tell() == self._rbcsnameslen:
                         f.write('\0')
                     else:
@@ -438,9 +435,6 @@
             revs = min(len(repo.changelog), len(self._rbcrevs) // _rbcrecsize)
             try:
                 f = repo.vfs.open(_rbcrevs, 'ab')
-                # The position after open(x, 'a') is implementation defined-
-                # see issue3543.  SEEK_END was added in 2.5
-                f.seek(0, 2) #os.SEEK_END
                 if f.tell() != start:
                     repo.ui.debug("truncating %s to %s\n" % (_rbcrevs, start))
                     f.seek(start)
--- a/mercurial/bundle2.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/bundle2.py	Fri Mar 13 17:55:04 2015 -0500
@@ -145,6 +145,7 @@
 preserve.
 """
 
+import errno
 import sys
 import util
 import struct
@@ -312,7 +313,7 @@
     except Exception, exc:
         for part in iterparts:
             # consume the bundle content
-            part.read()
+            part.seek(0, 2)
         # Small hack to let caller code distinguish exceptions from bundle2
         # processing from processing the old format. This is mostly
         # needed to handle different return codes to unbundle according to the
@@ -364,7 +365,7 @@
             outpart.addparam('in-reply-to', str(part.id), mandatory=False)
     finally:
         # consume the part content to not corrupt the stream.
-        part.read()
+        part.seek(0, 2)
 
 
 def decodecaps(blob):
@@ -484,6 +485,8 @@
 
     def __init__(self, fp):
         self._fp = fp
+        self._seekable = (util.safehasattr(fp, 'seek') and
+                          util.safehasattr(fp, 'tell'))
 
     def _unpack(self, format):
         """unpack this struct format from the stream"""
@@ -494,6 +497,29 @@
         """read exactly <size> bytes from the stream"""
         return changegroup.readexactly(self._fp, size)
 
+    def seek(self, offset, whence=0):
+        """move the underlying file pointer"""
+        if self._seekable:
+            return self._fp.seek(offset, whence)
+        else:
+            raise NotImplementedError(_('File pointer is not seekable'))
+
+    def tell(self):
+        """return the file offset, or None if file is not seekable"""
+        if self._seekable:
+            try:
+                return self._fp.tell()
+            except IOError, e:
+                if e.errno == errno.ESPIPE:
+                    self._seekable = False
+                else:
+                    raise
+        return None
+
+    def close(self):
+        """close underlying file"""
+        if util.safehasattr(self._fp, 'close'):
+            return self._fp.close()
 
 class unbundle20(unpackermixin):
     """interpret a bundle2 stream
@@ -564,6 +590,7 @@
         while headerblock is not None:
             part = unbundlepart(self.ui, headerblock, self._fp)
             yield part
+            part.seek(0, 2)
             headerblock = self._readpartheader()
         self.ui.debug('end of bundle2 stream\n')
 
@@ -580,6 +607,8 @@
             return self._readexact(headersize)
         return None
 
+    def compressed(self):
+        return False
 
 class bundlepart(object):
     """A bundle2 part contains application level payload
@@ -801,6 +830,8 @@
         self._payloadstream = None
         self._readheader()
         self._mandatory = None
+        self._chunkindex = [] #(payload, file) position tuples for chunk starts
+        self._pos = 0
 
     def _fromheader(self, size):
         """return the next <size> byte from the header"""
@@ -826,6 +857,47 @@
         self.params.update(dict(self.advisoryparams))
         self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
 
+    def _payloadchunks(self, chunknum=0):
+        '''seek to specified chunk and start yielding data'''
+        if len(self._chunkindex) == 0:
+            assert chunknum == 0, 'Must start with chunk 0'
+            self._chunkindex.append((0, super(unbundlepart, self).tell()))
+        else:
+            assert chunknum < len(self._chunkindex), \
+                   'Unknown chunk %d' % chunknum
+            super(unbundlepart, self).seek(self._chunkindex[chunknum][1])
+
+        pos = self._chunkindex[chunknum][0]
+        payloadsize = self._unpack(_fpayloadsize)[0]
+        self.ui.debug('payload chunk size: %i\n' % payloadsize)
+        while payloadsize:
+            if payloadsize == flaginterrupt:
+                # interruption detection, the handler will now read a
+                # single part and process it.
+                interrupthandler(self.ui, self._fp)()
+            elif payloadsize < 0:
+                msg = 'negative payload chunk size: %i' %  payloadsize
+                raise error.BundleValueError(msg)
+            else:
+                result = self._readexact(payloadsize)
+                chunknum += 1
+                pos += payloadsize
+                if chunknum == len(self._chunkindex):
+                    self._chunkindex.append((pos,
+                                             super(unbundlepart, self).tell()))
+                yield result
+            payloadsize = self._unpack(_fpayloadsize)[0]
+            self.ui.debug('payload chunk size: %i\n' % payloadsize)
+
+    def _findchunk(self, pos):
+        '''for a given payload position, return a chunk number and offset'''
+        for chunk, (ppos, fpos) in enumerate(self._chunkindex):
+            if ppos == pos:
+                return chunk, 0
+            elif ppos > pos:
+                return chunk - 1, pos - self._chunkindex[chunk - 1][0]
+        raise ValueError('Unknown chunk')
+
     def _readheader(self):
         """read the header and setup the object"""
         typesize = self._unpackheader(_fparttypesize)[0]
@@ -857,22 +929,7 @@
             advparams.append((self._fromheader(key), self._fromheader(value)))
         self._initparams(manparams, advparams)
         ## part payload
-        def payloadchunks():
-            payloadsize = self._unpack(_fpayloadsize)[0]
-            self.ui.debug('payload chunk size: %i\n' % payloadsize)
-            while payloadsize:
-                if payloadsize == flaginterrupt:
-                    # interruption detection, the handler will now read a
-                    # single part and process it.
-                    interrupthandler(self.ui, self._fp)()
-                elif payloadsize < 0:
-                    msg = 'negative payload chunk size: %i' %  payloadsize
-                    raise error.BundleValueError(msg)
-                else:
-                    yield self._readexact(payloadsize)
-                payloadsize = self._unpack(_fpayloadsize)[0]
-                self.ui.debug('payload chunk size: %i\n' % payloadsize)
-        self._payloadstream = util.chunkbuffer(payloadchunks())
+        self._payloadstream = util.chunkbuffer(self._payloadchunks())
         # we read the data, tell it
         self._initialized = True
 
@@ -886,8 +943,37 @@
             data = self._payloadstream.read(size)
         if size is None or len(data) < size:
             self.consumed = True
+        self._pos += len(data)
         return data
 
+    def tell(self):
+        return self._pos
+
+    def seek(self, offset, whence=0):
+        if whence == 0:
+            newpos = offset
+        elif whence == 1:
+            newpos = self._pos + offset
+        elif whence == 2:
+            if not self.consumed:
+                self.read()
+            newpos = self._chunkindex[-1][0] - offset
+        else:
+            raise ValueError('Unknown whence value: %r' % (whence,))
+
+        if newpos > self._chunkindex[-1][0] and not self.consumed:
+            self.read()
+        if not 0 <= newpos <= self._chunkindex[-1][0]:
+            raise ValueError('Offset out of range')
+
+        if self._pos != newpos:
+            chunk, internaloffset = self._findchunk(newpos)
+            self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
+            adjust = self.read(internaloffset)
+            if len(adjust) != internaloffset:
+                raise util.Abort(_('Seek failed\n'))
+            self._pos = newpos
+
 capabilities = {'HG2Y': (),
                 'b2x:listkeys': (),
                 'b2x:pushkey': (),
--- a/mercurial/bundlerepo.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/bundlerepo.py	Fri Mar 13 17:55:04 2015 -0500
@@ -15,7 +15,7 @@
 from i18n import _
 import os, tempfile, shutil
 import changegroup, util, mdiff, discovery, cmdutil, scmutil, exchange
-import localrepo, changelog, manifest, filelog, revlog, error, phases
+import localrepo, changelog, manifest, filelog, revlog, error, phases, bundle2
 
 class bundlerevlog(revlog.revlog):
     def __init__(self, opener, indexfile, bundle, linkmapper):
@@ -177,9 +177,6 @@
     def baserevision(self, nodeorrev):
         return filelog.filelog.revision(self, nodeorrev)
 
-    def _file(self, f):
-        self._repo.file(f)
-
 class bundlepeer(localrepo.localpeer):
     def canpush(self):
         return False
@@ -219,7 +216,7 @@
 
         self.tempfile = None
         f = util.posixfile(bundlename, "rb")
-        self.bundle = exchange.readbundle(ui, f, bundlename)
+        self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
         if self.bundle.compressed():
             fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
                                             suffix=".hg10un")
@@ -237,7 +234,27 @@
                 fptemp.close()
 
             f = self.vfs.open(self.tempfile, mode="rb")
-            self.bundle = exchange.readbundle(ui, f, bundlename, self.vfs)
+            self.bundlefile = self.bundle = exchange.readbundle(ui, f,
+                                                                bundlename,
+                                                                self.vfs)
+
+        if isinstance(self.bundle, bundle2.unbundle20):
+            cgparts = [part for part in self.bundle.iterparts()
+                       if (part.type == 'b2x:changegroup')
+                       and (part.params.get('version', '01')
+                            in changegroup.packermap)]
+
+            if not cgparts:
+                raise util.Abort('No changegroups found')
+            version = cgparts[0].params.get('version', '01')
+            cgparts = [p for p in cgparts
+                       if p.params.get('version', '01') == version]
+            if len(cgparts) > 1:
+                raise NotImplementedError("Can't process multiple changegroups")
+            part = cgparts[0]
+
+            part.seek(0)
+            self.bundle = changegroup.packermap[version][1](part, 'UN')
 
         # dict with the mapping 'filename' -> position in the bundle
         self.bundlefilespos = {}
@@ -303,7 +320,7 @@
 
     def close(self):
         """Close assigned bundle file immediately."""
-        self.bundle.close()
+        self.bundlefile.close()
         if self.tempfile is not None:
             self.vfs.unlink(self.tempfile)
         if self._tempparent:
--- a/mercurial/changegroup.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/changegroup.py	Fri Mar 13 17:55:04 2015 -0500
@@ -111,7 +111,8 @@
             chunkiter = bundle.getchunks()
         else:
             if cg.version != '01':
-                raise util.Abort(_('Bundle1 only supports v1 changegroups\n'))
+                raise util.Abort(_('old bundle types only supports v1 '
+                                   'changegroups'))
             header, compressor = bundletypes[bundletype]
             fh.write(header)
             z = compressor()
@@ -481,7 +482,17 @@
         base = self.deltaparent(revlog, rev, p1, p2, prev)
 
         prefix = ''
-        if base == nullrev:
+        if revlog.iscensored(base) or revlog.iscensored(rev):
+            try:
+                delta = revlog.revision(node)
+            except error.CensoredNodeError, e:
+                delta = e.tombstone
+            if base == nullrev:
+                prefix = mdiff.trivialdiffheader(len(delta))
+            else:
+                baselen = revlog.rawsize(base)
+                prefix = mdiff.replacediffheader(baselen, len(delta))
+        elif base == nullrev:
             delta = revlog.revision(node)
             prefix = mdiff.trivialdiffheader(len(delta))
         else:
@@ -659,8 +670,11 @@
         pr()
         fl = repo.file(f)
         o = len(fl)
-        if not fl.addgroup(source, revmap, trp):
-            raise util.Abort(_("received file revlog group is empty"))
+        try:
+            if not fl.addgroup(source, revmap, trp):
+                raise util.Abort(_("received file revlog group is empty"))
+        except error.CensoredBaseError, e:
+            raise util.Abort(_("received delta base is censored: %s") % e)
         revisions += len(fl) - o
         files += 1
         if f in needfiles:
--- a/mercurial/changelog.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/changelog.py	Fri Mar 13 17:55:04 2015 -0500
@@ -143,6 +143,11 @@
             if i not in self.filteredrevs:
                 return self.node(i)
 
+    def __contains__(self, rev):
+        """filtered version of revlog.__contains__"""
+        return (revlog.revlog.__contains__(self, rev)
+                and rev not in self.filteredrevs)
+
     def __iter__(self):
         """filtered version of revlog.__iter__"""
         if len(self.filteredrevs) == 0:
--- a/mercurial/cmdutil.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/cmdutil.py	Fri Mar 13 17:55:04 2015 -0500
@@ -7,7 +7,7 @@
 
 from node import hex, nullid, nullrev, short
 from i18n import _
-import os, sys, errno, re, tempfile
+import os, sys, errno, re, tempfile, cStringIO, shutil
 import util, scmutil, templater, patch, error, templatekw, revlog, copies
 import match as matchmod
 import context, repair, graphmod, revset, phases, obsolete, pathutil
@@ -19,6 +19,177 @@
 def parsealiases(cmd):
     return cmd.lstrip("^").split("|")
 
+def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
+    import merge as mergemod
+    if not ui.interactive():
+        raise util.Abort(_('running non-interactively, use %s instead') %
+                         cmdsuggest)
+
+    # make sure username is set before going interactive
+    if not opts.get('user'):
+        ui.username() # raise exception, username not provided
+
+    def recordfunc(ui, repo, message, match, opts):
+        """This is generic record driver.
+
+        Its job is to interactively filter local changes, and
+        accordingly prepare working directory into a state in which the
+        job can be delegated to a non-interactive commit command such as
+        'commit' or 'qrefresh'.
+
+        After the actual job is done by non-interactive command, the
+        working directory is restored to its original state.
+
+        In the end we'll record interesting changes, and everything else
+        will be left in place, so the user can continue working.
+        """
+
+        checkunfinished(repo, commit=True)
+        merge = len(repo[None].parents()) > 1
+        if merge:
+            raise util.Abort(_('cannot partially commit a merge '
+                               '(use "hg commit" instead)'))
+
+        status = repo.status(match=match)
+        diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
+        diffopts.nodates = True
+        diffopts.git = True
+        originalchunks = patch.diff(repo, changes=status, opts=diffopts)
+        fp = cStringIO.StringIO()
+        fp.write(''.join(originalchunks))
+        fp.seek(0)
+
+        # 1. filter patch, so we have intending-to apply subset of it
+        try:
+            chunks = patch.filterpatch(ui, patch.parsepatch(fp))
+        except patch.PatchError, err:
+            raise util.Abort(_('error parsing patch: %s') % err)
+
+        del fp
+
+        contenders = set()
+        for h in chunks:
+            try:
+                contenders.update(set(h.files()))
+            except AttributeError:
+                pass
+
+        changed = status.modified + status.added + status.removed
+        newfiles = [f for f in changed if f in contenders]
+        if not newfiles:
+            ui.status(_('no changes to record\n'))
+            return 0
+
+        newandmodifiedfiles = set()
+        for h in chunks:
+            ishunk = isinstance(h, patch.recordhunk)
+            isnew = h.filename() in status.added
+            if ishunk and isnew and not h in originalchunks:
+                newandmodifiedfiles.add(h.filename())
+
+        modified = set(status.modified)
+
+        # 2. backup changed files, so we can restore them in the end
+
+        if backupall:
+            tobackup = changed
+        else:
+            tobackup = [f for f in newfiles
+                        if f in modified or f in newandmodifiedfiles]
+
+        backups = {}
+        if tobackup:
+            backupdir = repo.join('record-backups')
+            try:
+                os.mkdir(backupdir)
+            except OSError, err:
+                if err.errno != errno.EEXIST:
+                    raise
+        try:
+            # backup continues
+            for f in tobackup:
+                fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
+                                               dir=backupdir)
+                os.close(fd)
+                ui.debug('backup %r as %r\n' % (f, tmpname))
+                util.copyfile(repo.wjoin(f), tmpname)
+                shutil.copystat(repo.wjoin(f), tmpname)
+                backups[f] = tmpname
+
+            fp = cStringIO.StringIO()
+            for c in chunks:
+                fname = c.filename()
+                if fname in backups or fname in newandmodifiedfiles:
+                    c.write(fp)
+            dopatch = fp.tell()
+            fp.seek(0)
+
+            [os.unlink(c) for c in newandmodifiedfiles]
+
+            # 3a. apply filtered patch to clean repo  (clean)
+            if backups:
+                # Equivalent to hg.revert
+                choices = lambda key: key in backups
+                mergemod.update(repo, repo.dirstate.p1(),
+                        False, True, choices)
+
+
+            # 3b. (apply)
+            if dopatch:
+                try:
+                    ui.debug('applying patch\n')
+                    ui.debug(fp.getvalue())
+                    patch.internalpatch(ui, repo, fp, 1, eolmode=None)
+                except patch.PatchError, err:
+                    raise util.Abort(str(err))
+            del fp
+
+            # 4. We prepared working directory according to filtered
+            #    patch. Now is the time to delegate the job to
+            #    commit/qrefresh or the like!
+
+            # Make all of the pathnames absolute.
+            newfiles = [repo.wjoin(nf) for nf in newfiles]
+            commitfunc(ui, repo, *newfiles, **opts)
+
+            return 0
+        finally:
+            # 5. finally restore backed-up files
+            try:
+                for realname, tmpname in backups.iteritems():
+                    ui.debug('restoring %r to %r\n' % (tmpname, realname))
+                    util.copyfile(tmpname, repo.wjoin(realname))
+                    # Our calls to copystat() here and above are a
+                    # hack to trick any editors that have f open that
+                    # we haven't modified them.
+                    #
+                    # Also note that this racy as an editor could
+                    # notice the file's mtime before we've finished
+                    # writing it.
+                    shutil.copystat(tmpname, repo.wjoin(realname))
+                    os.unlink(tmpname)
+                if tobackup:
+                    os.rmdir(backupdir)
+            except OSError:
+                pass
+
+    # wrap ui.write so diff output can be labeled/colorized
+    def wrapwrite(orig, *args, **kw):
+        label = kw.pop('label', '')
+        for chunk, l in patch.difflabel(lambda: args):
+            orig(chunk, label=label + l)
+
+    oldwrite = ui.write
+    def wrap(*args, **kwargs):
+        return wrapwrite(oldwrite, *args, **kwargs)
+    setattr(ui, 'write', wrap)
+
+    try:
+        return commit(ui, repo, recordfunc, pats, opts)
+    finally:
+        ui.write = oldwrite
+
+
 def findpossible(cmd, table, strict=False):
     """
     Return cmd -> (aliases, command table entry)
@@ -34,8 +205,10 @@
     else:
         keys = table.keys()
 
+    allcmds = []
     for e in keys:
         aliases = parsealiases(e)
+        allcmds.extend(aliases)
         found = None
         if cmd in aliases:
             found = cmd
@@ -53,11 +226,11 @@
     if not choice and debugchoice:
         choice = debugchoice
 
-    return choice
+    return choice, allcmds
 
 def findcmd(cmd, table, strict=True):
     """Return (aliases, command table entry) for command string."""
-    choice = findpossible(cmd, table, strict)
+    choice, allcmds = findpossible(cmd, table, strict)
 
     if cmd in choice:
         return choice[cmd]
@@ -70,7 +243,7 @@
     if choice:
         return choice.values()[0]
 
-    raise error.UnknownCommand(cmd)
+    raise error.UnknownCommand(cmd, allcmds)
 
 def findrepo(p):
     while not os.path.isdir(os.path.join(p, ".hg")):
@@ -110,22 +283,22 @@
                              (logfile, inst.strerror))
     return message
 
-def mergeeditform(ctxorbool, baseform):
-    """build appropriate editform from ctxorbool and baseform
-
-    'ctxorbool' is one of a ctx to be committed, or a bool whether
+def mergeeditform(ctxorbool, baseformname):
+    """return appropriate editform name (referencing a committemplate)
+
+    'ctxorbool' is either a ctx to be committed, or a bool indicating whether
     merging is committed.
 
-    This returns editform 'baseform' with '.merge' if merging is
-    committed, or one with '.normal' suffix otherwise.
+    This returns baseformname with '.merge' appended if it is a merge,
+    otherwise '.normal' is appended.
     """
     if isinstance(ctxorbool, bool):
         if ctxorbool:
-            return baseform + ".merge"
+            return baseformname + ".merge"
     elif 1 < len(ctxorbool.parents()):
-        return baseform + ".merge"
-
-    return baseform + ".normal"
+        return baseformname + ".merge"
+
+    return baseformname + ".normal"
 
 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
                     editform='', **opts):
@@ -613,6 +786,7 @@
 
     update = not opts.get('bypass')
     strip = opts["strip"]
+    prefix = opts["prefix"]
     sim = float(opts.get('similarity') or 0)
     if not tmpname:
         return (None, None, False)
@@ -672,8 +846,8 @@
             partial = opts.get('partial', False)
             files = set()
             try:
-                patch.patch(ui, repo, tmpname, strip=strip, files=files,
-                            eolmode=None, similarity=sim / 100.0)
+                patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
+                            files=files, eolmode=None, similarity=sim / 100.0)
             except patch.PatchError, e:
                 if not partial:
                     raise util.Abort(str(e))
@@ -710,7 +884,7 @@
             try:
                 files = set()
                 try:
-                    patch.patchrepo(ui, repo, p1, store, tmpname, strip,
+                    patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
                                     files, eolmode=None)
                 except patch.PatchError, e:
                     raise util.Abort(str(e))
@@ -1473,14 +1647,7 @@
     function on each context in the window in forward order.'''
 
     follow = opts.get('follow') or opts.get('follow_first')
-
-    if opts.get('rev'):
-        revs = scmutil.revrange(repo, opts.get('rev'))
-    elif follow:
-        revs = repo.revs('reverse(:.)')
-    else:
-        revs = revset.spanset(repo)
-        revs.reverse()
+    revs = _logrevs(repo, opts)
     if not revs:
         return []
     wanted = set()
@@ -1822,6 +1989,21 @@
         expr = None
     return expr, filematcher
 
+def _logrevs(repo, opts):
+    # Default --rev value depends on --follow but --follow behaviour
+    # depends on revisions resolved from --rev...
+    follow = opts.get('follow') or opts.get('follow_first')
+    if opts.get('rev'):
+        revs = scmutil.revrange(repo, opts['rev'])
+    elif follow and repo.dirstate.p1() == nullid:
+        revs = revset.baseset()
+    elif follow:
+        revs = repo.revs('reverse(:.)')
+    else:
+        revs = revset.spanset(repo)
+        revs.reverse()
+    return revs
+
 def getgraphlogrevs(repo, pats, opts):
     """Return (revs, expr, filematcher) where revs is an iterable of
     revision numbers, expr is a revset string built from log options
@@ -1830,28 +2012,14 @@
     callable taking a revision number and returning a match objects
     filtering the files to be detailed when displaying the revision.
     """
-    if not len(repo):
-        return [], None, None
     limit = loglimit(opts)
-    # Default --rev value depends on --follow but --follow behaviour
-    # depends on revisions resolved from --rev...
-    follow = opts.get('follow') or opts.get('follow_first')
-    possiblyunsorted = False # whether revs might need sorting
-    if opts.get('rev'):
-        revs = scmutil.revrange(repo, opts['rev'])
-        # Don't sort here because _makelogrevset might depend on the
-        # order of revs
-        possiblyunsorted = True
-    else:
-        if follow and len(repo) > 0:
-            revs = repo.revs('reverse(:.)')
-        else:
-            revs = revset.spanset(repo)
-            revs.reverse()
+    revs = _logrevs(repo, opts)
     if not revs:
         return revset.baseset(), None, None
     expr, filematcher = _makelogrevset(repo, pats, opts, revs)
-    if possiblyunsorted:
+    if opts.get('rev'):
+        # User-specified revs might be unsorted, but don't sort before
+        # _makelogrevset because it might depend on the order of revs
         revs.sort(reverse=True)
     if expr:
         # Revset matchers often operate faster on revisions in changelog
@@ -1882,16 +2050,7 @@
     filtering the files to be detailed when displaying the revision.
     """
     limit = loglimit(opts)
-    # Default --rev value depends on --follow but --follow behaviour
-    # depends on revisions resolved from --rev...
-    follow = opts.get('follow') or opts.get('follow_first')
-    if opts.get('rev'):
-        revs = scmutil.revrange(repo, opts['rev'])
-    elif follow:
-        revs = repo.revs('reverse(:.)')
-    else:
-        revs = revset.spanset(repo)
-        revs.reverse()
+    revs = _logrevs(repo, opts)
     if not revs:
         return revset.baseset([]), None, None
     expr, filematcher = _makelogrevset(repo, pats, opts, revs)
@@ -1930,6 +2089,8 @@
             char = '@'
         elif ctx.obsolete():
             char = 'x'
+        elif ctx.closesbranch():
+            char = '_'
         copies = None
         if getrenamed and ctx.rev():
             copies = []
@@ -2058,6 +2219,24 @@
     forgot.extend(f for f in forget if f not in rejected)
     return bad, forgot
 
+def files(ui, ctx, m, fm, fmt):
+    rev = ctx.rev()
+    ret = 1
+    ds = ctx.repo().dirstate
+
+    for f in ctx.matches(m):
+        if rev is None and ds[f] == 'r':
+            continue
+        fm.startitem()
+        if ui.verbose:
+            fc = ctx[f]
+            fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
+        fm.data(abspath=f)
+        fm.write('path', fmt, m.rel(f))
+        ret = 0
+
+    return ret
+
 def remove(ui, repo, m, prefix, after, force, subrepos):
     join = lambda f: os.path.join(prefix, f)
     ret = 0
@@ -2086,6 +2265,7 @@
                                % join(subpath))
 
     # warn about failure to delete explicit files/dirs
+    deleteddirs = scmutil.dirs(deleted)
     for f in m.files():
         def insubrepo():
             for subpath in wctx.substate:
@@ -2093,7 +2273,8 @@
                     return True
             return False
 
-        if f in repo.dirstate or f in wctx.dirs() or f == '.' or insubrepo():
+        isdir = f in deleteddirs or f in wctx.dirs()
+        if f in repo.dirstate or isdir or f == '.' or insubrepo():
             continue
 
         if repo.wvfs.exists(f):
@@ -2615,9 +2796,8 @@
         deladded = _deleted - smf
         deleted = _deleted - deladded
 
-        # We need to account for the state of file in the dirstate.
-        #
-        # Even, when we revert against something else than parent. This will
+        # We need to account for the state of the file in the dirstate,
+        # even when we revert against 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:
@@ -2800,14 +2980,14 @@
 
             _performrevert(repo, parents, ctx, actions)
 
-            # get the list of subrepos that must be reverted
-            subrepomatch = scmutil.match(ctx, pats, opts)
-            targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
-
-            if targetsubs:
-                # Revert the subrepos on the revert list
-                for sub in targetsubs:
-                    ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
+        # get the list of subrepos that must be reverted
+        subrepomatch = scmutil.match(ctx, pats, opts)
+        targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
+
+        if targetsubs:
+            # Revert the subrepos on the revert list
+            for sub in targetsubs:
+                ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
     finally:
         wlock.release()
 
--- a/mercurial/commands.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/commands.py	Fri Mar 13 17:55:04 2015 -0500
@@ -1383,6 +1383,7 @@
     ('', 'amend', None, _('amend the parent of the working dir')),
     ('s', 'secret', None, _('use the secret phase for committing')),
     ('e', 'edit', None, _('invoke editor on commit messages')),
+    ('i', 'interactive', None, _('use interactive mode')),
     ] + walkopts + commitopts + commitopts2 + subrepoopts,
     _('[OPTION]... [FILE]...'),
     inferrepo=True)
@@ -1422,6 +1423,11 @@
 
     Returns 0 on success, 1 if nothing changed.
     """
+    if opts.get('interactive'):
+        opts.pop('interactive')
+        cmdutil.dorecord(ui, repo, commit, 'commit', False, *pats, **opts)
+        return
+
     if opts.get('subrepos'):
         if opts.get('amend'):
             raise util.Abort(_('cannot amend with --subrepos'))
@@ -1946,7 +1952,7 @@
         ui.write("%s\n" % "\n".join(options))
         return
 
-    cmdlist = cmdutil.findpossible(cmd, table)
+    cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
     if ui.verbose:
         cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
     ui.write("%s\n" % "\n".join(sorted(cmdlist)))
@@ -2885,7 +2891,7 @@
             weight, optimizedtree = revset.optimize(newtree, True)
             ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
     func = revset.match(ui, expr)
-    for c in func(repo, revset.spanset(repo)):
+    for c in func(repo):
         ui.write("%s\n" % c)
 
 @command('debugsetparents', [], _('REV1 [REV2]'))
@@ -2893,7 +2899,8 @@
     """manually set the parents of the current working directory
 
     This is useful for writing repository conversion tools, but should
-    be used with care.
+    be used with care. For example, neither the working copy nor the dirstate
+    is updated, so file status may be incorrect after running this command.
 
     Returns 0 on success.
     """
@@ -3257,8 +3264,6 @@
 
     """
     ctx = scmutil.revsingle(repo, opts.get('rev'), None)
-    rev = ctx.rev()
-    ret = 1
 
     end = '\n'
     if opts.get('print0'):
@@ -3267,17 +3272,7 @@
     fmt = '%s' + end
 
     m = scmutil.match(ctx, pats, opts)
-    ds = repo.dirstate
-    for f in ctx.matches(m):
-        if rev is None and ds[f] == 'r':
-            continue
-        fm.startitem()
-        if ui.verbose:
-            fc = ctx[f]
-            fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
-        fm.data(abspath=f)
-        fm.write('path', fmt, m.rel(f))
-        ret = 0
+    ret = cmdutil.files(ui, ctx, m, fm, fmt)
 
     fm.end()
 
@@ -4056,6 +4051,8 @@
      _('commit even if some hunks fail')),
     ('', 'exact', None,
      _('apply patch to the nodes from which it was generated')),
+    ('', 'prefix', '',
+     _('apply patch to directory relative to the root'), _('DIR')),
     ('', 'import-branch', None,
      _('use any branch information in patch (implied by --exact)'))] +
     commitopts + commitopts2 + similarityopts,
@@ -4155,6 +4152,8 @@
         raise util.Abort(_('cannot use --similarity with --bypass'))
     if opts.get('exact') and opts.get('edit'):
         raise util.Abort(_('cannot use --exact with --edit'))
+    if opts.get('exact') and opts.get('prefix'):
+        raise util.Abort(_('cannot use --exact with --prefix'))
 
     if update:
         cmdutil.checkunfinished(repo)
@@ -4243,13 +4242,13 @@
     pull location. These are the changesets that would have been pulled
     if a pull at the time you issued this command.
 
-    For remote repository, using --bundle avoids downloading the
-    changesets twice if the incoming is followed by a pull.
-
     See pull for valid source format details.
 
     .. container:: verbose
 
+      For remote repository, using --bundle avoids downloading the
+      changesets twice if the incoming is followed by a pull.
+
       Examples:
 
       - show incoming changes with patches and full description::
@@ -4477,6 +4476,10 @@
     Returns 0 on success.
 
     """
+    if opts.get('follow') and opts.get('rev'):
+        opts['rev'] = [revset.formatspec('reverse(::%lr)', opts.get('rev'))]
+        del opts['follow']
+
     if opts.get('graph'):
         return cmdutil.graphlog(ui, repo, *pats, **opts)
 
@@ -4821,19 +4824,20 @@
     Returns 0 on success.
     """
     if search:
-        for name, path in ui.configitems("paths"):
+        for name, path in sorted(ui.paths.iteritems()):
             if name == search:
-                ui.status("%s\n" % util.hidepassword(path))
+                ui.status("%s\n" % util.hidepassword(path.loc))
                 return
         if not ui.quiet:
             ui.warn(_("not found!\n"))
         return 1
     else:
-        for name, path in ui.configitems("paths"):
+        for name, path in sorted(ui.paths.iteritems()):
             if ui.quiet:
                 ui.write("%s\n" % name)
             else:
-                ui.write("%s = %s\n" % (name, util.hidepassword(path)))
+                ui.write("%s = %s\n" % (name,
+                                        util.hidepassword(path.loc)))
 
 @command('phase',
     [('p', 'public', False, _('set changeset phase to public')),
@@ -4984,9 +4988,9 @@
     Returns 0 on success, 1 if an update had unresolved files.
     """
     source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
+    ui.status(_('pulling from %s\n') % util.hidepassword(source))
     other = hg.peer(repo, opts, source)
     try:
-        ui.status(_('pulling from %s\n') % util.hidepassword(source))
         revs, checkout = hg.addbranchrevs(repo, other, branches,
                                           opts.get('rev'))
 
@@ -5225,7 +5229,7 @@
     ('m', 'mark', None, _('mark files as resolved')),
     ('u', 'unmark', None, _('mark files as unresolved')),
     ('n', 'no-status', None, _('hide status prefix'))]
-    + mergetoolopts + walkopts,
+    + mergetoolopts + walkopts + formatteropts,
     _('[OPTION]... [FILE]...'),
     inferrepo=True)
 def resolve(ui, repo, *pats, **opts):
@@ -5277,11 +5281,25 @@
         raise util.Abort(_('no files or directories specified'),
                          hint=('use --all to remerge all files'))
 
+    if show:
+        fm = ui.formatter('resolve', opts)
+        ms = mergemod.mergestate(repo)
+        m = scmutil.match(repo[None], pats, opts)
+        for f in ms:
+            if not m(f):
+                continue
+            l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved'}[ms[f]]
+            fm.startitem()
+            fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
+            fm.write('path', '%s\n', f, label=l)
+        fm.end()
+        return 0
+
     wlock = repo.wlock()
     try:
         ms = mergemod.mergestate(repo)
 
-        if not (ms.active() or repo.dirstate.p2() != nullid) and not show:
+        if not (ms.active() or repo.dirstate.p2() != nullid):
             raise util.Abort(
                 _('resolve command not applicable when not merging'))
 
@@ -5295,14 +5313,7 @@
 
             didwork = True
 
-            if show:
-                if nostatus:
-                    ui.write("%s\n" % f)
-                else:
-                    ui.write("%s %s\n" % (ms[f].upper(), f),
-                             label='resolve.' +
-                             {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
-            elif mark:
+            if mark:
                 ms.mark(f, "r")
             elif unmark:
                 ms.mark(f, "u")
@@ -5334,10 +5345,8 @@
     finally:
         wlock.release()
 
-    # Nudge users into finishing an unfinished operation. We don't print
-    # this with the list/show operation because we want list/show to remain
-    # machine readable.
-    if not list(ms.unresolved()) and not show:
+    # Nudge users into finishing an unfinished operation
+    if not list(ms.unresolved()):
         ui.status(_('(no more unresolved files)\n'))
 
     return ret
@@ -6303,7 +6312,7 @@
              % util.version())
     ui.status(_(
         "(see http://mercurial.selenic.com for more information)\n"
-        "\nCopyright (C) 2005-2014 Matt Mackall and others\n"
+        "\nCopyright (C) 2005-2015 Matt Mackall and others\n"
         "This is free software; see the source for copying conditions. "
         "There is NO\nwarranty; "
         "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
--- a/mercurial/context.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/context.py	Fri Mar 13 17:55:04 2015 -0500
@@ -66,8 +66,7 @@
         return self.filectx(key)
 
     def __iter__(self):
-        for f in sorted(self._manifest):
-            yield f
+        return iter(self._manifest)
 
     def _manifestmatches(self, match, s):
         """generate a new manifest filtered by the match argument
@@ -153,6 +152,8 @@
         return hex(self.node())
     def manifest(self):
         return self._manifest
+    def repo(self):
+        return self._repo
     def phasestr(self):
         return phases.phasenames[self.phase()]
     def mutable(self):
@@ -376,10 +377,6 @@
                 return
             if isinstance(changeid, long):
                 changeid = str(changeid)
-            if changeid == '.':
-                self._node = repo.dirstate.p1()
-                self._rev = repo.changelog.rev(self._node)
-                return
             if changeid == 'null':
                 self._node = nullid
                 self._rev = nullrev
@@ -388,6 +385,12 @@
                 self._node = repo.changelog.tip()
                 self._rev = repo.changelog.rev(self._node)
                 return
+            if changeid == '.' or changeid == repo.dirstate.p1():
+                # this is a hack to delay/avoid loading obsmarkers
+                # when we know that '.' won't be hidden
+                self._node = repo.dirstate.p1()
+                self._rev = repo.unfiltered().changelog.rev(self._node)
+                return
             if len(changeid) == 20:
                 try:
                     self._node = changeid
@@ -752,7 +755,7 @@
         return True
 
     def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
-        """return the first ancestor of <srcrev> introducting <fnode>
+        """return the first ancestor of <srcrev> introducing <fnode>
 
         If the linkrev of the file revision does not point to an ancestor of
         srcrev, we'll walk down the ancestors until we find one introducing
@@ -824,7 +827,7 @@
             # be replaced with the rename information. This parent is -always-
             # the first one.
             #
-            # As null id have alway been filtered out in the previous list
+            # As null id have always been filtered out in the previous list
             # comprehension, inserting to 0 will always result in "replacing
             # first nullid parent with rename information.
             pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
@@ -1287,9 +1290,6 @@
         self._repo.dirstate.setparents(node)
         self._repo.dirstate.endparentchange()
 
-    def dirs(self):
-        return self._repo.dirstate.dirs()
-
 class workingctx(committablectx):
     """A workingctx object makes access to data related to
     the current working directory convenient.
--- a/mercurial/copies.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/copies.py	Fri Mar 13 17:55:04 2015 -0500
@@ -8,10 +8,6 @@
 import util
 import heapq
 
-def _nonoverlap(d1, d2, d3):
-    "Return list of elements in d1 not in d2 or d3"
-    return sorted([d for d in d1 if d not in d3 and d not in d2])
-
 def _dirname(f):
     s = f.rfind("/")
     if s == -1:
@@ -144,6 +140,13 @@
             del c[k]
     return c
 
+def _computeforwardmissing(a, b):
+    """Computes which files are in b but not a.
+    This is its own function so extensions can easily wrap this call to see what
+    files _forwardcopies is about to process.
+    """
+    return b.manifest().filesnotin(a.manifest())
+
 def _forwardcopies(a, b):
     '''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
 
@@ -167,9 +170,7 @@
     # we currently don't try to find where old files went, too expensive
     # this means we can miss a case like 'hg rm b; hg cp a b'
     cm = {}
-    missing = set(b.manifest().iterkeys())
-    missing.difference_update(a.manifest().iterkeys())
-
+    missing = _computeforwardmissing(a, b)
     ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
     for f in missing:
         fctx = b[f]
@@ -208,6 +209,25 @@
         return _backwardrenames(x, y)
     return _chain(x, y, _backwardrenames(x, a), _forwardcopies(a, y))
 
+def _computenonoverlap(repo, m1, m2, addedinm1, addedinm2):
+    """Computes, based on addedinm1 and addedinm2, the files exclusive to m1
+    and m2. This is its own function so extensions can easily wrap this call
+    to see what files mergecopies is about to process.
+
+    Even though m1 and m2 are not used in this function, they are useful in
+    other extensions for being able to read the file nodes of the changed files.
+    """
+    u1 = sorted(addedinm1 - addedinm2)
+    u2 = sorted(addedinm2 - addedinm1)
+
+    if u1:
+        repo.ui.debug("  unmatched files in local:\n   %s\n"
+                      % "\n   ".join(u1))
+    if u2:
+        repo.ui.debug("  unmatched files in other:\n   %s\n"
+                      % "\n   ".join(u2))
+    return u1, u2
+
 def mergecopies(repo, c1, c2, ca):
     """
     Find moves and copies between context c1 and c2 that are relevant
@@ -261,15 +281,9 @@
 
     repo.ui.debug("  searching for copies back to rev %d\n" % limit)
 
-    u1 = _nonoverlap(m1, m2, ma)
-    u2 = _nonoverlap(m2, m1, ma)
-
-    if u1:
-        repo.ui.debug("  unmatched files in local:\n   %s\n"
-                      % "\n   ".join(u1))
-    if u2:
-        repo.ui.debug("  unmatched files in other:\n   %s\n"
-                      % "\n   ".join(u2))
+    addedinm1 = m1.filesnotin(ma)
+    addedinm2 = m2.filesnotin(ma)
+    u1, u2 = _computenonoverlap(repo, m1, m2, addedinm1, addedinm2)
 
     for f in u1:
         checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy)
@@ -291,7 +305,7 @@
         else:
             diverge2.update(fl) # reverse map for below
 
-    bothnew = sorted([d for d in m1 if d in m2 and d not in ma])
+    bothnew = sorted(addedinm1 & addedinm2)
     if bothnew:
         repo.ui.debug("  unmatched files new in both:\n   %s\n"
                       % "\n   ".join(bothnew))
--- a/mercurial/default.d/mergetools.rc	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/default.d/mergetools.rc	Fri Mar 13 17:55:04 2015 -0500
@@ -102,6 +102,13 @@
 bcompare.priority=-1
 bcompare.diffargs=-lro -lefttitle=$plabel1 -righttitle=$clabel -solo -expandall $parent $child
 
+; OS X version of Beyond Compare
+bcomposx.executable = /Applications/Beyond Compare.app/Contents/MacOS/bcomp
+bcomposx.args=$local $other $base -mergeoutput=$output -ro -lefttitle=parent1 -centertitle=base -righttitle=parent2 -outputtitle=merged -automerge -reviewconflicts -solo
+bcomposx.gui=True
+bcomposx.priority=-1
+bcomposx.diffargs=-lro -lefttitle=$plabel1 -righttitle=$clabel -solo -expandall $parent $child
+
 winmerge.args=/e /x /wl /ub /dl other /dr local $other $local $output
 winmerge.regkey=Software\Thingamahoochie\WinMerge
 winmerge.regkeyalt=Software\Wow6432Node\Thingamahoochie\WinMerge\
--- a/mercurial/dirstate.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/dirstate.py	Fri Mar 13 17:55:04 2015 -0500
@@ -887,9 +887,9 @@
                 elif time != mtime and time != mtime & _rangemask:
                     ladd(fn)
                 elif mtime == lastnormaltime:
-                    # fn may have been changed in the same timeslot without
-                    # changing its size. This can happen if we quickly do
-                    # multiple commits in a single transaction.
+                    # fn may have just been marked as normal and it may have
+                    # changed in the same second without changing its size.
+                    # This can happen if we quickly do multiple commits.
                     # Force lookup, so we don't miss such a racy file change.
                     ladd(fn)
                 elif listclean:
--- a/mercurial/dispatch.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/dispatch.py	Fri Mar 13 17:55:04 2015 -0500
@@ -7,6 +7,7 @@
 
 from i18n import _
 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
+import difflib
 import util, commands, hg, fancyopts, extensions, hook, error
 import cmdutil, encoding
 import ui as uimod
@@ -27,6 +28,31 @@
     "run the command in sys.argv"
     sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
 
+def _getsimilar(symbols, value):
+    sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
+    # The cutoff for similarity here is pretty arbitrary. It should
+    # probably be investigated and tweaked.
+    return [s for s in symbols if sim(s) > 0.6]
+
+def _formatparse(write, inst):
+    similar = []
+    if isinstance(inst, error.UnknownIdentifier):
+        # make sure to check fileset first, as revset can invoke fileset
+        similar = _getsimilar(inst.symbols, inst.function)
+    if len(inst.args) > 1:
+        write(_("hg: parse error at %s: %s\n") %
+                         (inst.args[1], inst.args[0]))
+        if (inst.args[0][0] == ' '):
+            write(_("unexpected leading whitespace\n"))
+    else:
+        write(_("hg: parse error: %s\n") % inst.args[0])
+        if similar:
+            if len(similar) == 1:
+                write(_("(did you mean %r?)\n") % similar[0])
+            else:
+                ss = ", ".join(sorted(similar))
+                write(_("(did you mean one of %s?)\n") % ss)
+
 def dispatch(req):
     "run the command specified in req.args"
     if req.ferr:
@@ -55,13 +81,7 @@
             ferr.write(_("(%s)\n") % inst.hint)
         return -1
     except error.ParseError, inst:
-        if len(inst.args) > 1:
-            ferr.write(_("hg: parse error at %s: %s\n") %
-                             (inst.args[1], inst.args[0]))
-            if (inst.args[0][0] == ' '):
-                ferr.write(_("unexpected leading whitespace\n"))
-        else:
-            ferr.write(_("hg: parse error: %s\n") % inst.args[0])
+        _formatparse(ferr.write, inst)
         return -1
 
     msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
@@ -154,13 +174,7 @@
         ui.warn(_("hg: command '%s' is ambiguous:\n    %s\n") %
                 (inst.args[0], " ".join(inst.args[1])))
     except error.ParseError, inst:
-        if len(inst.args) > 1:
-            ui.warn(_("hg: parse error at %s: %s\n") %
-                             (inst.args[1], inst.args[0]))
-            if (inst.args[0][0] == ' '):
-                ui.warn(_("unexpected leading whitespace\n"))
-        else:
-            ui.warn(_("hg: parse error: %s\n") % inst.args[0])
+        _formatparse(ui.warn, inst)
         return -1
     except error.LockHeld, inst:
         if inst.errno == errno.ETIMEDOUT:
@@ -206,7 +220,15 @@
             # (but don't check for extensions themselves)
             commands.help_(ui, inst.args[0], unknowncmd=True)
         except error.UnknownCommand:
-            commands.help_(ui, 'shortlist')
+            suggested = False
+            if len(inst.args) == 2:
+                sim = _getsimilar(inst.args[1], inst.args[0])
+                if sim:
+                    ui.warn(_('(did you mean one of %s?)\n') %
+                            ', '.join(sorted(sim)))
+                    suggested = True
+            if not suggested:
+                commands.help_(ui, 'shortlist')
     except error.InterventionRequired, inst:
         ui.warn("%s\n" % inst)
         return 1
@@ -804,7 +826,7 @@
 
     if cmdoptions.get('insecure', False):
         for ui_ in uis:
-            ui_.setconfig('web', 'cacerts', '', '--insecure')
+            ui_.setconfig('web', 'cacerts', '!', '--insecure')
 
     if options['version']:
         return commands.version_(ui)
--- a/mercurial/error.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/error.py	Fri Mar 13 17:55:04 2015 -0500
@@ -22,6 +22,10 @@
 class LookupError(RevlogError, KeyError):
     def __init__(self, name, index, message):
         self.name = name
+        self.index = index
+        # this can't be called 'message' because at least some installs of
+        # Python 2.6+ complain about the 'message' property being deprecated
+        self.lookupmessage = message
         if isinstance(name, str) and len(name) == 20:
             from node import short
             name = short(name)
@@ -61,7 +65,16 @@
     """Exception raised when a remote repo reports failure"""
 
 class ParseError(Exception):
-    """Exception raised when parsing config files (msg[, pos])"""
+    """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
+
+class UnknownIdentifier(ParseError):
+    """Exception raised when a {rev,file}set references an unknown identifier"""
+
+    def __init__(self, function, symbols):
+        from i18n import _
+        ParseError.__init__(self, _("unknown identifier: %s") % function)
+        self.function = function
+        self.symbols = symbols
 
 class RepoError(Exception):
     def __init__(self, *args, **kw):
@@ -134,8 +147,20 @@
     pass
 
 class CensoredNodeError(RevlogError):
-    """error raised when content verification fails on a censored node"""
+    """error raised when content verification fails on a censored node
 
-    def __init__(self, filename, node):
+    Also contains the tombstone data substituted for the uncensored data.
+    """
+
+    def __init__(self, filename, node, tombstone):
         from node import short
         RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
+        self.tombstone = tombstone
+
+class CensoredBaseError(RevlogError):
+    """error raised when a delta is rejected because its base is censored
+
+    A delta based on a censored revision must be formed as single patch
+    operation which replaces the entire base with new content. This ensures
+    the delta may be applied by clones which have not censored the base.
+    """
--- a/mercurial/extensions.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/extensions.py	Fri Mar 13 17:55:04 2015 -0500
@@ -10,6 +10,7 @@
 from i18n import _, gettext
 
 _extensions = {}
+_aftercallbacks = {}
 _order = []
 _ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify']
 
@@ -87,6 +88,8 @@
             mod = importh(name)
     _extensions[shortname] = mod
     _order.append(shortname)
+    for fn in _aftercallbacks.get(shortname, []):
+        fn(loaded=True)
     return mod
 
 def loadall(ui):
@@ -123,7 +126,33 @@
                     raise
                 extsetup() # old extsetup with no ui argument
 
-def wrapcommand(table, command, wrapper):
+    # Call aftercallbacks that were never met.
+    for shortname in _aftercallbacks:
+        if shortname in _extensions:
+            continue
+
+        for fn in _aftercallbacks[shortname]:
+            fn(loaded=False)
+
+def afterloaded(extension, callback):
+    '''Run the specified function after a named extension is loaded.
+
+    If the named extension is already loaded, the callback will be called
+    immediately.
+
+    If the named extension never loads, the callback will be called after
+    all extensions have been loaded.
+
+    The callback receives the named argument ``loaded``, which is a boolean
+    indicating whether the dependent extension actually loaded.
+    '''
+
+    if extension in _extensions:
+        callback(loaded=True)
+    else:
+        _aftercallbacks.setdefault(extension, []).append(callback)
+
+def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
     '''Wrap the command named `command' in table
 
     Replace command in the command table with wrapper. The wrapped command will
@@ -135,6 +164,22 @@
 
     where orig is the original (wrapped) function, and *args, **kwargs
     are the arguments passed to it.
+
+    Optionally append to the command synopsis and docstring, used for help.
+    For example, if your extension wraps the ``bookmarks`` command to add the
+    flags ``--remote`` and ``--all`` you might call this function like so:
+
+      synopsis = ' [-a] [--remote]'
+      docstring = """
+
+      The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
+      flags to the bookmarks command. Either flag will show the remote bookmarks
+      known to the repository; ``--remote`` will also supress the output of the
+      local bookmarks.
+      """
+
+      extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
+                             synopsis, docstring)
     '''
     assert callable(wrapper)
     aliases, entry = cmdutil.findcmd(command, table)
@@ -148,11 +193,17 @@
         return util.checksignature(wrapper)(
             util.checksignature(origfn), *args, **kwargs)
 
-    wrap.__doc__ = getattr(origfn, '__doc__')
     wrap.__module__ = getattr(origfn, '__module__')
 
+    doc = getattr(origfn, '__doc__')
+    if docstring is not None:
+        doc += docstring
+    wrap.__doc__ = doc
+
     newentry = list(entry)
     newentry[0] = wrap
+    if synopsis is not None:
+        newentry[2] += synopsis
     table[key] = tuple(newentry)
     return entry
 
--- a/mercurial/filelog.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/filelog.py	Fri Mar 13 17:55:04 2015 -0500
@@ -5,8 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import error, revlog
-import re
+import error, mdiff, revlog
+import re, struct
 
 _mdre = re.compile('\1\n')
 def parsemeta(text):
@@ -29,7 +29,7 @@
 
 def _censoredtext(text):
     m, offs = parsemeta(text)
-    return m and "censored" in m and not text[offs:]
+    return m and "censored" in m
 
 class filelog(revlog.revlog):
     def __init__(self, opener, path):
@@ -64,7 +64,7 @@
         node = self.node(rev)
         if self.renamed(node):
             return len(self.read(node))
-        if self._iscensored(rev):
+        if self.iscensored(rev):
             return 0
 
         # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
@@ -85,7 +85,7 @@
             return False
 
         # censored files compare against the empty file
-        if self._iscensored(self.rev(node)):
+        if self.iscensored(self.rev(node)):
             return text != ''
 
         # renaming a file produces a different hash, even if the data
@@ -101,12 +101,29 @@
             super(filelog, self).checkhash(text, p1, p2, node, rev=rev)
         except error.RevlogError:
             if _censoredtext(text):
-                raise error.CensoredNodeError(self.indexfile, node)
+                raise error.CensoredNodeError(self.indexfile, node, text)
             raise
 
-    def _file(self, f):
-        return filelog(self.opener, f)
-
-    def _iscensored(self, rev):
+    def iscensored(self, rev):
         """Check if a file revision is censored."""
         return self.flags(rev) & revlog.REVIDX_ISCENSORED
+
+    def _peek_iscensored(self, baserev, delta, flush):
+        """Quickly check if a delta produces a censored revision."""
+        # Fragile heuristic: unless new file meta keys are added alphabetically
+        # preceding "censored", all censored revisions are prefixed by
+        # "\1\ncensored:". A delta producing such a censored revision must be a
+        # full-replacement delta, so we inspect the first and only patch in the
+        # delta for this prefix.
+        hlen = struct.calcsize(">lll")
+        if len(delta) <= hlen:
+            return False
+
+        oldlen = self.rawsize(baserev)
+        newlen = len(delta) - hlen
+        if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
+            return False
+
+        add = "\1\ncensored:"
+        addlen = len(add)
+        return newlen >= addlen and delta[hlen:hlen + addlen] == add
--- a/mercurial/filemerge.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/filemerge.py	Fri Mar 13 17:55:04 2015 -0500
@@ -21,6 +21,8 @@
     return ui.configlist("merge-tools", tool + "." + part, default)
 
 internals = {}
+# Merge tools to document.
+internalsdoc = {}
 
 def internaltool(name, trymerge, onfailure=None):
     '''return a decorator for populating internal merge tool table'''
@@ -29,6 +31,7 @@
         func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip()
         internals[fullname] = func
         internals['internal:' + name] = func
+        internalsdoc[fullname] = func
         func.trymerge = trymerge
         func.onfailure = onfailure
         return func
--- a/mercurial/fileset.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/fileset.py	Fri Mar 13 17:55:04 2015 -0500
@@ -186,7 +186,7 @@
 def func(mctx, a, b):
     if a[0] == 'symbol' and a[1] in symbols:
         return symbols[a[1]](mctx, b)
-    raise error.ParseError(_("not a function: %s") % a[1])
+    raise error.UnknownIdentifier(a[1], symbols.keys())
 
 def getlist(x):
     if not x:
--- a/mercurial/graphmod.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/graphmod.py	Fri Mar 13 17:55:04 2015 -0500
@@ -122,7 +122,7 @@
             heappush(pendingheap, -currentrev)
             pendingset.add(currentrev)
         # iterates on pending rev until after the current rev have been
-        # processeed.
+        # processed.
         rev = None
         while rev != currentrev:
             rev = -heappop(pendingheap)
--- a/mercurial/help.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help.py	Fri Mar 13 17:55:04 2015 -0500
@@ -6,11 +6,12 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import gettext, _
-import itertools, os
+import itertools, os, textwrap
 import error
 import extensions, revset, fileset, templatekw, templatefilters, filemerge
 import encoding, util, minirst
 import cmdutil
+import hgweb.webcommands as webcommands
 
 def listexts(header, exts, indent=1, showdeprecated=False):
     '''return a text listing of the given extensions'''
@@ -171,7 +172,7 @@
 def addtopichook(topic, rewriter):
     helphooks.setdefault(topic, []).append(rewriter)
 
-def makeitemsdoc(topic, doc, marker, items):
+def makeitemsdoc(topic, doc, marker, items, dedent=False):
     """Extract docstring from the items key to function mapping, build a
     .single documentation block and use it to overwrite the marker in doc
     """
@@ -181,27 +182,35 @@
         if not text:
             continue
         text = gettext(text)
+        if dedent:
+            text = textwrap.dedent(text)
         lines = text.splitlines()
         doclines = [(lines[0])]
         for l in lines[1:]:
             # Stop once we find some Python doctest
             if l.strip().startswith('>>>'):
                 break
-            doclines.append('  ' + l.strip())
+            if dedent:
+                doclines.append(l.rstrip())
+            else:
+                doclines.append('  ' + l.strip())
         entries.append('\n'.join(doclines))
     entries = '\n\n'.join(entries)
     return doc.replace(marker, entries)
 
-def addtopicsymbols(topic, marker, symbols):
+def addtopicsymbols(topic, marker, symbols, dedent=False):
     def add(topic, doc):
-        return makeitemsdoc(topic, doc, marker, symbols)
+        return makeitemsdoc(topic, doc, marker, symbols, dedent=dedent)
     addtopichook(topic, add)
 
 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
-addtopicsymbols('merge-tools', '.. internaltoolsmarker', filemerge.internals)
+addtopicsymbols('merge-tools', '.. internaltoolsmarker',
+                filemerge.internalsdoc)
 addtopicsymbols('revsets', '.. predicatesmarker', revset.symbols)
 addtopicsymbols('templates', '.. keywordsmarker', templatekw.dockeywords)
 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
+addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
+                dedent=True)
 
 def help_(ui, name, unknowncmd=False, full=True, **opts):
     '''
--- a/mercurial/help/config.txt	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/config.txt	Fri Mar 13 17:55:04 2015 -0500
@@ -808,6 +808,29 @@
   changeset to tag is in ``$HG_NODE``. Name of tag is in ``$HG_TAG``. Tag is
   local if ``$HG_LOCAL=1``, in repository if ``$HG_LOCAL=0``.
 
+``pretxnopen``
+  Run before any new repository transaction is open. The reason for the
+  transaction will be in ``$HG_TXNNAME``. A non-zero status will
+  prevent the transaction from being opened.
+
+``pretxnclose``
+  Run right before the transaction is actually finalized. Any
+  repository change will be visible to the hook program. This lets you
+  validate the transaction content or change it. Exit status 0 allows
+  the commit to proceed. Non-zero status will cause the transaction to
+  be rolled back. The reason for the transaction opening will be in
+  ``$HG_TXNNAME``. The rest of the available data will vary according
+  the transaction type. New changesets will add
+  ``$HG_NODE`` (id of the first added changeset), ``$HG_URL`` and
+  ``$HG_SOURCE`` variables, bookmarks and phases changes will set
+  ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1``, etc.
+
+``txnclose``
+  Run after any repository transaction has been commited. At this
+  point, the transaction can no longer be rolled back. The hook will run
+  after the lock is released. see ``pretxnclose`` docs for details about
+  available variables.
+
 ``pretxnchangegroup``
   Run after a changegroup has been added via push, pull or unbundle,
   but before the transaction has been committed. Changegroup is
--- a/mercurial/help/hg.1.txt	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/hg.1.txt	Fri Mar 13 17:55:04 2015 -0500
@@ -112,7 +112,7 @@
 
 Copying
 """""""
-Copyright (C) 2005-2014 Matt Mackall.
+Copyright (C) 2005-2015 Matt Mackall.
 Free use of this software is granted under the terms of the GNU General
 Public License version 2 or any later version.
 
--- a/mercurial/help/hgignore.5.txt	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/hgignore.5.txt	Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
 Copying
 =======
 This manual page is copyright 2006 Vadim Gelfer.
-Mercurial is copyright 2005-2014 Matt Mackall.
+Mercurial is copyright 2005-2015 Matt Mackall.
 Free use of this software is granted under the terms of the GNU General
 Public License version 2 or any later version.
 
--- a/mercurial/help/hgrc.5.txt	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/hgrc.5.txt	Fri Mar 13 17:55:04 2015 -0500
@@ -34,7 +34,7 @@
 Copying
 =======
 This manual page is copyright 2005 Bryan O'Sullivan.
-Mercurial is copyright 2005-2014 Matt Mackall.
+Mercurial is copyright 2005-2015 Matt Mackall.
 Free use of this software is granted under the terms of the GNU General
 Public License version 2 or any later version.
 
--- a/mercurial/help/hgweb.txt	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/hgweb.txt	Fri Mar 13 17:55:04 2015 -0500
@@ -48,3 +48,39 @@
 
 The ``collections`` section is deprecated and has been superseded by
 ``paths``.
+
+URLs and Common Arguments
+=========================
+
+URLs under each repository have the form ``/{command}[/{arguments}]``
+where ``{command}`` represents the name of a command or handler and
+``{arguments}`` represents any number of additional URL parameters
+to that command.
+
+The web server has a default style associated with it. Styles map to
+a collection of named templates. Each template is used to render a
+specific piece of data, such as a changeset or diff.
+
+The style for the current request can be overwritten two ways. First,
+if ``{command}`` contains a hyphen (``-``), the text before the hyphen
+defines the style. For example, ``/atom-log`` will render the ``log``
+command handler with the ``atom`` style. The second way to set the
+style is with the ``style`` query string argument. For example,
+``/log?style=atom``. The hyphenated URL parameter is preferred.
+
+Not all templates are available for all styles. Attempting to use
+a style that doesn't have all templates defined may result in an error
+rendering the page.
+
+Many commands take a ``{revision}`` URL parameter. This defines the
+changeset to operate on. This is commonly specified as the short,
+12 digit hexidecimal abbreviation for the full 40 character unique
+revision identifier. However, any value described by
+:hg:`help revisions` typically works.
+
+Commands and URLs
+=================
+
+The following web commands and their URLs are available:
+
+  .. webcommandsmarker
--- a/mercurial/help/subrepos.txt	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/help/subrepos.txt	Fri Mar 13 17:55:04 2015 -0500
@@ -78,7 +78,7 @@
 :add: add does not recurse in subrepos unless -S/--subrepos is
     specified.  However, if you specify the full path of a file in a
     subrepo, it will be added even without -S/--subrepos specified.
-    Git and Subversion subrepositories are currently silently
+    Subversion subrepositories are currently silently
     ignored.
 
 :addremove: addremove does not recurse into subrepos unless
@@ -91,7 +91,7 @@
     -S/--subrepos is specified.
 
 :cat: cat currently only handles exact file matches in subrepos.
-    Git and Subversion subrepositories are currently ignored.
+    Subversion subrepositories are currently ignored.
 
 :commit: commit creates a consistent snapshot of the state of the
     entire project and its subrepositories. If any subrepositories
--- a/mercurial/hg.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/hg.py	Fri Mar 13 17:55:04 2015 -0500
@@ -672,7 +672,9 @@
         for key, val in src.configitems(sect):
             dst.setconfig(sect, key, val, 'copied')
     v = src.config('web', 'cacerts')
-    if v:
+    if v == '!':
+        dst.setconfig('web', 'cacerts', v, 'copied')
+    elif v:
         dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
 
     return dst
--- a/mercurial/hgweb/webcommands.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/hgweb/webcommands.py	Fri Mar 13 17:55:04 2015 -0500
@@ -13,27 +13,58 @@
 from common import paritygen, staticfile, get_contact, ErrorResponse
 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
 from mercurial import graphmod, patch
-from mercurial import help as helpmod
 from mercurial import scmutil
 from mercurial.i18n import _
 from mercurial.error import ParseError, RepoLookupError, Abort
 from mercurial import revset
 
-# __all__ is populated with the allowed commands. Be sure to add to it if
-# you're adding a new command, or the new command won't work.
+__all__ = []
+commands = {}
+
+class webcommand(object):
+    """Decorator used to register a web command handler.
+
+    The decorator takes as its positional arguments the name/path the
+    command should be accessible under.
+
+    Usage:
+
+    @webcommand('mycommand')
+    def mycommand(web, req, tmpl):
+        pass
+    """
+
+    def __init__(self, name):
+        self.name = name
 
-__all__ = [
-   'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
-   'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
-   'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
-]
+    def __call__(self, func):
+        __all__.append(self.name)
+        commands[self.name] = func
+        return func
+
+@webcommand('log')
+def log(web, req, tmpl):
+    """
+    /log[/{revision}[/{path}]]
+    --------------------------
 
-def log(web, req, tmpl):
+    Show repository or file history.
+
+    For URLs of the form ``/log/{revision}``, a list of changesets starting at
+    the specified changeset identifier is shown. If ``{revision}`` is not
+    defined, the default is ``tip``. This form is equivalent to the
+    ``changelog`` handler.
+
+    For URLs of the form ``/log/{revision}/{file}``, the history for a specific
+    file will be shown. This form is equivalent to the ``filelog`` handler.
+    """
+
     if 'file' in req.form and req.form['file'][0]:
         return filelog(web, req, tmpl)
     else:
         return changelog(web, req, tmpl)
 
+@webcommand('rawfile')
 def rawfile(web, req, tmpl):
     guessmime = web.configbool('web', 'guessmime', False)
 
@@ -98,7 +129,26 @@
                 rename=webutil.renamelink(fctx),
                 permissions=fctx.manifest().flags(f))
 
+@webcommand('file')
 def file(web, req, tmpl):
+    """
+    /file/{revision}[/{path}]
+    -------------------------
+
+    Show information about a directory or file in the repository.
+
+    Info about the ``path`` given as a URL parameter will be rendered.
+
+    If ``path`` is a directory, information about the entries in that
+    directory will be rendered. This form is equivalent to the ``manifest``
+    handler.
+
+    If ``path`` is a file, information about that file will be shown via
+    the ``filerevision`` template.
+
+    If ``path`` is not defined, information about the root directory will
+    be rendered.
+    """
     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
     if not path:
         return manifest(web, req, tmpl)
@@ -187,7 +237,7 @@
 
         mfunc = revset.match(web.repo.ui, revdef)
         try:
-            revs = mfunc(web.repo, revset.baseset(web.repo))
+            revs = mfunc(web.repo)
             return MODE_REVSET, revs
             # ParseError: wrongly placed tokens, wrongs arguments, etc
             # RepoLookupError: no such revision, e.g. in 'revision:'
@@ -267,7 +317,31 @@
                 modedesc=searchfunc[1],
                 showforcekw=showforcekw, showunforcekw=showunforcekw)
 
+@webcommand('changelog')
 def changelog(web, req, tmpl, shortlog=False):
+    """
+    /changelog[/{revision}]
+    -----------------------
+
+    Show information about multiple changesets.
+
+    If the optional ``revision`` URL argument is absent, information about
+    all changesets starting at ``tip`` will be rendered. If the ``revision``
+    argument is present, changesets will be shown starting from the specified
+    revision.
+
+    If ``revision`` is absent, the ``rev`` query string argument may be
+    defined. This will perform a search for changesets.
+
+    The argument for ``rev`` can be a single revision, a revision set,
+    or a literal keyword to search for in changeset data (equivalent to
+    :hg:`log -k`.
+
+    The ``revcount`` query string argument defines the maximum numbers of
+    changesets to render.
+
+    For non-searches, the ``changelog`` template will be rendered.
+    """
 
     query = ''
     if 'node' in req.form:
@@ -326,63 +400,41 @@
                 archives=web.archivelist("tip"), revcount=revcount,
                 morevars=morevars, lessvars=lessvars, query=query)
 
+@webcommand('shortlog')
 def shortlog(web, req, tmpl):
+    """
+    /shortlog
+    ---------
+
+    Show basic information about a set of changesets.
+
+    This accepts the same parameters as the ``changelog`` handler. The only
+    difference is the ``shortlog`` template will be rendered instead of the
+    ``changelog`` template.
+    """
     return changelog(web, req, tmpl, shortlog=True)
 
+@webcommand('changeset')
 def changeset(web, req, tmpl):
-    ctx = webutil.changectx(web.repo, req)
-    basectx = webutil.basechangectx(web.repo, req)
-    if basectx is None:
-        basectx = ctx.p1()
-    showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
-    showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
-                                         ctx.node())
-    showbranch = webutil.nodebranchnodefault(ctx)
+    """
+    /changeset[/{revision}]
+    -----------------------
 
-    files = []
-    parity = paritygen(web.stripecount)
-    for blockno, f in enumerate(ctx.files()):
-        template = f in ctx and 'filenodelink' or 'filenolink'
-        files.append(tmpl(template,
-                          node=ctx.hex(), file=f, blockno=blockno + 1,
-                          parity=parity.next()))
-
-    style = web.config('web', 'style', 'paper')
-    if 'style' in req.form:
-        style = req.form['style'][0]
-
-    parity = paritygen(web.stripecount)
-    diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
+    Show information about a single changeset.
 
-    parity = paritygen(web.stripecount)
-    diffstatgen = webutil.diffstatgen(ctx, basectx)
-    diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
+    A URL path argument is the changeset identifier to show. See ``hg help
+    revisions`` for possible values. If not defined, the ``tip`` changeset
+    will be shown.
 
-    return tmpl('changeset',
-                diff=diffs,
-                rev=ctx.rev(),
-                node=ctx.hex(),
-                parent=tuple(webutil.parents(ctx)),
-                child=webutil.children(ctx),
-                basenode=basectx.hex(),
-                changesettag=showtags,
-                changesetbookmark=showbookmarks,
-                changesetbranch=showbranch,
-                author=ctx.user(),
-                desc=ctx.description(),
-                extra=ctx.extra(),
-                date=ctx.date(),
-                files=files,
-                diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
-                diffstat=diffstat,
-                archives=web.archivelist(ctx.hex()),
-                tags=webutil.nodetagsdict(web.repo, ctx.node()),
-                bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
-                branch=webutil.nodebranchnodefault(ctx),
-                inbranch=webutil.nodeinbranch(web.repo, ctx),
-                branches=webutil.nodebranchdict(web.repo, ctx))
+    The ``changeset`` template is rendered. Contents of the ``changesettag``,
+    ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
+    templates related to diffs may all be used to produce the output.
+    """
+    ctx = webutil.changectx(web.repo, req)
 
-rev = changeset
+    return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
+
+rev = webcommand('rev')(changeset)
 
 def decodepath(path):
     """Hook for mapping a path in the repository to a path in the
@@ -392,7 +444,23 @@
     the virtual file system presented by the manifest command below."""
     return path
 
+@webcommand('manifest')
 def manifest(web, req, tmpl):
+    """
+    /manifest[/{revision}[/{path}]]
+    -------------------------------
+
+    Show information about a directory.
+
+    If the URL path arguments are defined, information about the root
+    directory for the ``tip`` changeset will be shown.
+
+    Because this handler can only show information for directories, it
+    is recommended to use the ``file`` handler instead, as it can handle both
+    directories and files.
+
+    The ``manifest`` template will be rendered for this handler.
+    """
     ctx = webutil.changectx(web.repo, req)
     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
     mf = ctx.manifest()
@@ -474,7 +542,18 @@
                 inbranch=webutil.nodeinbranch(web.repo, ctx),
                 branches=webutil.nodebranchdict(web.repo, ctx))
 
+@webcommand('tags')
 def tags(web, req, tmpl):
+    """
+    /tags
+    -----
+
+    Show information about tags.
+
+    No arguments are accepted.
+
+    The ``tags`` template is rendered.
+    """
     i = list(reversed(web.repo.tagslist()))
     parity = paritygen(web.stripecount)
 
@@ -496,7 +575,18 @@
                 entriesnotip=lambda **x: entries(True, False, **x),
                 latestentry=lambda **x: entries(True, True, **x))
 
+@webcommand('bookmarks')
 def bookmarks(web, req, tmpl):
+    """
+    /bookmarks
+    ----------
+
+    Show information about bookmarks.
+
+    No arguments are accepted.
+
+    The ``bookmarks`` template is rendered.
+    """
     i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
     parity = paritygen(web.stripecount)
 
@@ -516,7 +606,20 @@
                 entries=lambda **x: entries(latestonly=False, **x),
                 latestentry=lambda **x: entries(latestonly=True, **x))
 
+@webcommand('branches')
 def branches(web, req, tmpl):
+    """
+    /branches
+    ---------
+
+    Show information about branches.
+
+    All known branches are contained in the output, even closed branches.
+
+    No arguments are accepted.
+
+    The ``branches`` template is rendered.
+    """
     tips = []
     heads = web.repo.heads()
     parity = paritygen(web.stripecount)
@@ -547,7 +650,19 @@
                 entries=lambda **x: entries(0, **x),
                 latestentry=lambda **x: entries(1, **x))
 
+@webcommand('summary')
 def summary(web, req, tmpl):
+    """
+    /summary
+    --------
+
+    Show a summary of repository state.
+
+    Information about the latest changesets, bookmarks, tags, and branches
+    is captured by this handler.
+
+    The ``summary`` template is rendered.
+    """
     i = reversed(web.repo.tagslist())
 
     def tagentries(**map):
@@ -632,7 +747,19 @@
                 node=tip.hex(),
                 archives=web.archivelist("tip"))
 
+@webcommand('filediff')
 def filediff(web, req, tmpl):
+    """
+    /diff/{revision}/{path}
+    -----------------------
+
+    Show how a file changed in a particular commit.
+
+    The ``filediff`` template is rendered.
+
+    This hander is registered under both the ``/diff`` and ``/filediff``
+    paths. ``/diff`` is used in modern code.
+    """
     fctx, ctx = None, None
     try:
         fctx = webutil.filectx(web.repo, req)
@@ -672,9 +799,25 @@
                 child=webutil.children(ctx),
                 diff=diffs)
 
-diff = filediff
+diff = webcommand('diff')(filediff)
+
+@webcommand('comparison')
+def comparison(web, req, tmpl):
+    """
+    /comparison/{revision}/{path}
+    -----------------------------
 
-def comparison(web, req, tmpl):
+    Show a comparison between the old and new versions of a file from changes
+    made on a particular revision.
+
+    This is similar to the ``diff`` handler. However, this form features
+    a split or side-by-side diff rather than a unified diff.
+
+    The ``context`` query string argument can be used to control the lines of
+    context in the diff.
+
+    The ``filecomparison`` template is rendered.
+    """
     ctx = webutil.changectx(web.repo, req)
     if 'file' not in req.form:
         raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
@@ -732,7 +875,16 @@
                 rightnode=hex(rightnode),
                 comparison=comparison)
 
+@webcommand('annotate')
 def annotate(web, req, tmpl):
+    """
+    /annotate/{revision}/{path}
+    ---------------------------
+
+    Show changeset information for each line in a file.
+
+    The ``fileannotate`` template is rendered.
+    """
     fctx = webutil.filectx(web.repo, req)
     f = fctx.path()
     parity = paritygen(web.stripecount)
@@ -784,7 +936,19 @@
                 child=webutil.children(fctx),
                 permissions=fctx.manifest().flags(f))
 
+@webcommand('filelog')
 def filelog(web, req, tmpl):
+    """
+    /filelog/{revision}/{path}
+    --------------------------
+
+    Show information about the history of a file in the repository.
+
+    The ``revcount`` query string argument can be defined to control the
+    maximum number of entries to show.
+
+    The ``filelog`` template will be rendered.
+    """
 
     try:
         fctx = webutil.filectx(web.repo, req)
@@ -862,7 +1026,27 @@
                 latestentry=latestentry,
                 revcount=revcount, morevars=morevars, lessvars=lessvars)
 
+@webcommand('archive')
 def archive(web, req, tmpl):
+    """
+    /archive/{revision}.{format}[/{path}]
+    -------------------------------------
+
+    Obtain an archive of repository content.
+
+    The content and type of the archive is defined by a URL path parameter.
+    ``format`` is the file extension of the archive type to be generated. e.g.
+    ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
+    server configuration.
+
+    The optional ``path`` URL parameter controls content to include in the
+    archive. If omitted, every file in the specified revision is present in the
+    archive. If included, only the specified file or contents of the specified
+    directory will be included in the archive.
+
+    No template is used for this handler. Raw, binary content is generated.
+    """
+
     type_ = req.form.get('type', [None])[0]
     allowed = web.configlist("web", "allow_archive")
     key = req.form['node'][0]
@@ -911,6 +1095,7 @@
     return []
 
 
+@webcommand('static')
 def static(web, req, tmpl):
     fname = req.form['file'][0]
     # a repo owner may set web.static in .hg/hgrc to get any file
@@ -924,7 +1109,24 @@
     staticfile(static, fname, req)
     return []
 
+@webcommand('graph')
 def graph(web, req, tmpl):
+    """
+    /graph[/{revision}]
+    -------------------
+
+    Show information about the graphical topology of the repository.
+
+    Information rendered by this handler can be used to create visual
+    representations of repository topology.
+
+    The ``revision`` URL parameter controls the starting changeset.
+
+    The ``revcount`` query string argument can define the number of changesets
+    to show information for.
+
+    This handler will render the ``graph`` template.
+    """
 
     ctx = webutil.changectx(web.repo, req)
     rev = ctx.rev()
@@ -1047,8 +1249,23 @@
         doc = _('(no help text available)')
     return doc
 
+@webcommand('help')
 def help(web, req, tmpl):
+    """
+    /help[/{topic}]
+    ---------------
+
+    Render help documentation.
+
+    This web command is roughly equivalent to :hg:`help`. If a ``topic``
+    is defined, that help topic will be rendered. If not, an index of
+    available help topics will be rendered.
+
+    The ``help`` template will be rendered when requesting help for a topic.
+    ``helptopics`` will be rendered for the index of help topics.
+    """
     from mercurial import commands # avoid cycle
+    from mercurial import help as helpmod # avoid cycle
 
     topicname = req.form.get('node', [None])[0]
     if not topicname:
--- a/mercurial/hgweb/webutil.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/hgweb/webutil.py	Fri Mar 13 17:55:04 2015 -0500
@@ -10,7 +10,7 @@
 from mercurial import match, patch, error, ui, util, pathutil, context
 from mercurial.i18n import _
 from mercurial.node import hex, nullid
-from common import ErrorResponse
+from common import ErrorResponse, paritygen
 from common import HTTP_NOT_FOUND
 import difflib
 
@@ -138,9 +138,10 @@
         yield d
 
 def parents(ctx, hide=None):
-    if (isinstance(ctx, context.basefilectx) and
-        ctx.changectx().rev() != ctx.linkrev()):
-        return _siblings([ctx._repo[ctx.linkrev()]], hide)
+    if isinstance(ctx, context.basefilectx):
+        introrev = ctx.introrev()
+        if ctx.changectx().rev() != introrev:
+            return _siblings([ctx._repo[introrev]], hide)
     return _siblings(ctx.parents(), hide)
 
 def children(ctx, hide=None):
@@ -278,6 +279,61 @@
         "branches": nodebranchdict(repo, ctx)
     }
 
+def changesetentry(web, req, tmpl, ctx):
+    '''Obtain a dictionary to be used to render the "changeset" template.'''
+
+    showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
+    showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
+                                 ctx.node())
+    showbranch = nodebranchnodefault(ctx)
+
+    files = []
+    parity = paritygen(web.stripecount)
+    for blockno, f in enumerate(ctx.files()):
+        template = f in ctx and 'filenodelink' or 'filenolink'
+        files.append(tmpl(template,
+                          node=ctx.hex(), file=f, blockno=blockno + 1,
+                          parity=parity.next()))
+
+    basectx = basechangectx(web.repo, req)
+    if basectx is None:
+        basectx = ctx.p1()
+
+    style = web.config('web', 'style', 'paper')
+    if 'style' in req.form:
+        style = req.form['style'][0]
+
+    parity = paritygen(web.stripecount)
+    diff = diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
+
+    parity = paritygen(web.stripecount)
+    diffstatsgen = diffstatgen(ctx, basectx)
+    diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
+
+    return dict(
+        diff=diff,
+        rev=ctx.rev(),
+        node=ctx.hex(),
+        parent=tuple(parents(ctx)),
+        child=children(ctx),
+        basenode=basectx.hex(),
+        changesettag=showtags,
+        changesetbookmark=showbookmarks,
+        changesetbranch=showbranch,
+        author=ctx.user(),
+        desc=ctx.description(),
+        extra=ctx.extra(),
+        date=ctx.date(),
+        files=files,
+        diffsummary=lambda **x: diffsummary(diffstatsgen),
+        diffstat=diffstats,
+        archives=web.archivelist(ctx.hex()),
+        tags=nodetagsdict(web.repo, ctx.node()),
+        bookmarks=nodebookmarksdict(web.repo, ctx.node()),
+        branch=nodebranchnodefault(ctx),
+        inbranch=nodeinbranch(web.repo, ctx),
+        branches=nodebranchdict(web.repo, ctx))
+
 def listfilediffs(tmpl, files, node, max):
     for f in files[:max]:
         yield tmpl('filedifflink', node=hex(node), file=f)
--- a/mercurial/localrepo.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/localrepo.py	Fri Mar 13 17:55:04 2015 -0500
@@ -323,6 +323,9 @@
         maxchainlen = self.ui.configint('format', 'maxchainlen')
         if maxchainlen is not None:
             self.svfs.options['maxchainlen'] = maxchainlen
+        manifestcachesize = self.ui.configint('format', 'manifestcachesize')
+        if manifestcachesize is not None:
+            self.svfs.options['manifestcachesize'] = manifestcachesize
 
     def _writerequirements(self):
         reqfile = self.vfs("requires", "w")
@@ -479,7 +482,7 @@
         '''Return a list of revisions matching the given revset'''
         expr = revset.formatspec(expr, *args)
         m = revset.match(None, expr)
-        return m(self, revset.spanset(self))
+        return m(self)
 
     def set(self, expr, *args):
         '''
@@ -906,19 +909,38 @@
                 _("abandoned transaction found"),
                 hint=_("run 'hg recover' to clean up transaction"))
 
+        self.hook('pretxnopen', throw=True, txnname=desc)
+
         self._writejournal(desc)
         renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
         rp = report and report or self.ui.warn
         vfsmap = {'plain': self.vfs} # root of .hg/
-        tr = transaction.transaction(rp, self.svfs, vfsmap,
+        # we must avoid cyclic reference between repo and transaction.
+        reporef = weakref.ref(self)
+        def validate(tr):
+            """will run pre-closing hooks"""
+            pending = lambda: tr.writepending() and self.root or ""
+            reporef().hook('pretxnclose', throw=True, pending=pending,
+                           xnname=desc)
+
+        tr = transaction.transaction(rp, self.sopener, vfsmap,
                                      "journal",
                                      "undo",
                                      aftertrans(renames),
-                                     self.store.createmode)
+                                     self.store.createmode,
+                                     validator=validate)
         # note: writing the fncache only during finalize mean that the file is
         # outdated when running hooks. As fncache is used for streaming clone,
         # this is not expected to break anything that happen during the hooks.
         tr.addfinalize('flush-fncache', self.store.write)
+        def txnclosehook(tr2):
+            """To be run if transaction is successful, will schedule a hook run
+            """
+            def hook():
+                reporef().hook('txnclose', throw=False, txnname=desc,
+                               **tr2.hookargs)
+            reporef()._afterlock(hook)
+        tr.addfinalize('txnclose-hook', txnclosehook)
         self._transref = weakref.ref(tr)
         return tr
 
@@ -1208,7 +1230,7 @@
 
             # Here, we used to search backwards through history to try to find
             # where the file copy came from if the source of a copy was not in
-            # the parent diretory. However, this doesn't actually make sense to
+            # the parent directory. However, this doesn't actually make sense to
             # do (what does a copy from something not in your working copy even
             # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
             # the user that copy information was dropped, so if they didn't
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/manifest.c	Fri Mar 13 17:55:04 2015 -0500
@@ -0,0 +1,926 @@
+/*
+ * manifest.c - manifest type that does on-demand parsing.
+ *
+ * Copyright 2015, Google Inc.
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <Python.h>
+
+/* VC9 doesn't include bool and lacks stdbool.h based on my searching */
+#ifdef _MSC_VER
+#define true 1
+#define false 0
+typedef unsigned char bool;
+#else
+#include <stdbool.h>
+#endif
+
+#define DEFAULT_LINES 100000
+
+typedef struct {
+	char *start;
+	Py_ssize_t len; /* length of line including terminal newline */
+	char hash_suffix;
+	bool from_malloc;
+	bool deleted;
+} line;
+
+typedef struct {
+	PyObject_HEAD
+	PyObject *pydata;
+	line *lines;
+	int numlines; /* number of line entries */
+	int livelines; /* number of non-deleted lines */
+	int maxlines; /* allocated number of lines */
+	bool dirty;
+} lazymanifest;
+
+#define MANIFEST_OOM -1
+#define MANIFEST_NOT_SORTED -2
+#define MANIFEST_MALFORMED -3
+
+/* defined in parsers.c */
+PyObject *unhexlify(const char *str, int len);
+
+/* get the length of the path for a line */
+static size_t pathlen(line *l) {
+	return strlen(l->start);
+}
+
+/* get the node value of a single line */
+static PyObject *nodeof(line *l) {
+	char *s = l->start;
+	ssize_t llen = pathlen(l);
+	PyObject *hash = unhexlify(s + llen + 1, 40);
+	if (!hash) {
+		return NULL;
+	}
+	if (l->hash_suffix != '\0') {
+		char newhash[21];
+		memcpy(newhash, PyString_AsString(hash), 20);
+		Py_DECREF(hash);
+		newhash[20] = l->hash_suffix;
+		hash = PyString_FromStringAndSize(newhash, 21);
+	}
+	return hash;
+}
+
+/* get the node hash and flags of a line as a tuple */
+static PyObject *hashflags(line *l)
+{
+	char *s = l->start;
+	size_t plen = pathlen(l);
+	PyObject *hash = nodeof(l);
+
+	/* 40 for hash, 1 for null byte, 1 for newline */
+	size_t hplen = plen + 42;
+	Py_ssize_t flen = l->len - hplen;
+	PyObject *flags;
+	PyObject *tup;
+
+	if (!hash)
+		return NULL;
+	flags = PyString_FromStringAndSize(s + hplen - 1, flen);
+	if (!flags) {
+		Py_DECREF(hash);
+		return NULL;
+	}
+	tup = PyTuple_Pack(2, hash, flags);
+	Py_DECREF(flags);
+	Py_DECREF(hash);
+	return tup;
+}
+
+/* if we're about to run out of space in the line index, add more */
+static bool realloc_if_full(lazymanifest *self)
+{
+	if (self->numlines == self->maxlines) {
+		self->maxlines *= 2;
+		self->lines = realloc(self->lines, self->maxlines * sizeof(line));
+	}
+	return self->lines;
+}
+
+/*
+ * Find the line boundaries in the manifest that 'data' points to and store
+ * information about each line in 'self'.
+ */
+static int find_lines(lazymanifest *self, char *data, Py_ssize_t len)
+{
+	char *prev = NULL;
+	while (len > 0) {
+		line *l;
+		char *next = memchr(data, '\n', len);
+		if (!next) {
+			return MANIFEST_MALFORMED;
+		}
+		next++; /* advance past newline */
+		if (!realloc_if_full(self)) {
+			return MANIFEST_OOM; /* no memory */
+		}
+		if (prev && strcmp(prev, data) > -1) {
+			/* This data isn't sorted, so we have to abort. */
+			return MANIFEST_NOT_SORTED;
+		}
+		l = self->lines + ((self->numlines)++);
+		l->start = data;
+		l->len = next - data;
+		l->hash_suffix = '\0';
+		l->from_malloc = false;
+		l->deleted = false;
+		len = len - l->len;
+		prev = data;
+		data = next;
+	}
+	self->livelines = self->numlines;
+	return 0;
+}
+
+static int lazymanifest_init(lazymanifest *self, PyObject *args)
+{
+	char *data;
+	Py_ssize_t len;
+	int err, ret;
+	PyObject *pydata;
+	if (!PyArg_ParseTuple(args, "S", &pydata)) {
+		return -1;
+	}
+	err = PyString_AsStringAndSize(pydata, &data, &len);
+
+	self->dirty = false;
+	if (err == -1)
+		return -1;
+	self->pydata = pydata;
+	Py_INCREF(self->pydata);
+	Py_BEGIN_ALLOW_THREADS
+	self->lines = malloc(DEFAULT_LINES * sizeof(line));
+	self->maxlines = DEFAULT_LINES;
+	self->numlines = 0;
+	if (!self->lines)
+		ret = MANIFEST_OOM;
+	else
+		ret = find_lines(self, data, len);
+	Py_END_ALLOW_THREADS
+	switch (ret) {
+	case 0:
+		break;
+	case MANIFEST_OOM:
+		PyErr_NoMemory();
+		break;
+	case MANIFEST_NOT_SORTED:
+		PyErr_Format(PyExc_ValueError,
+			     "Manifest lines not in sorted order.");
+		break;
+	case MANIFEST_MALFORMED:
+		PyErr_Format(PyExc_ValueError,
+			     "Manifest did not end in a newline.");
+		break;
+	default:
+		PyErr_Format(PyExc_ValueError,
+			     "Unknown problem parsing manifest.");
+	}
+	return ret == 0 ? 0 : -1;
+}
+
+static void lazymanifest_dealloc(lazymanifest *self)
+{
+	/* free any extra lines we had to allocate */
+	int i;
+	for (i = 0; i < self->numlines; i++) {
+		if (self->lines[i].from_malloc) {
+			free(self->lines[i].start);
+		}
+	}
+	if (self->lines) {
+		free(self->lines);
+		self->lines = NULL;
+	}
+	if (self->pydata) {
+		Py_DECREF(self->pydata);
+		self->pydata = NULL;
+	}
+	PyObject_Del(self);
+}
+
+/* iteration support */
+
+typedef struct {
+	PyObject_HEAD lazymanifest *m;
+	Py_ssize_t pos;
+} lmIter;
+
+static void lmiter_dealloc(PyObject *o)
+{
+	lmIter *self = (lmIter *)o;
+	Py_DECREF(self->m);
+	PyObject_Del(self);
+}
+
+static line *lmiter_nextline(lmIter *self)
+{
+	do {
+		self->pos++;
+		if (self->pos >= self->m->numlines) {
+			return NULL;
+		}
+		/* skip over deleted manifest entries */
+	} while (self->m->lines[self->pos].deleted);
+	return self->m->lines + self->pos;
+}
+
+static PyObject *lmiter_iterentriesnext(PyObject *o)
+{
+	size_t pl;
+	line *l;
+	Py_ssize_t consumed;
+	PyObject *path = NULL, *hash = NULL, *flags = NULL;
+	l = lmiter_nextline((lmIter *)o);
+	if (!l) {
+		goto bail;
+	}
+	pl = pathlen(l);
+	path = PyString_FromStringAndSize(l->start, pl);
+	hash = nodeof(l);
+	consumed = pl + 41;
+	flags = PyString_FromStringAndSize(l->start + consumed,
+									   l->len - consumed - 1);
+	if (!path || !hash || !flags) {
+		goto bail;
+	}
+	return PyTuple_Pack(3, path, hash, flags);
+ bail:
+	Py_XDECREF(path);
+	Py_XDECREF(hash);
+	Py_XDECREF(flags);
+	return NULL;
+}
+
+static PyTypeObject lazymanifestEntriesIterator = {
+	PyObject_HEAD_INIT(NULL)
+	0,                               /*ob_size */
+	"parsers.lazymanifest.entriesiterator", /*tp_name */
+	sizeof(lmIter),                  /*tp_basicsize */
+	0,                               /*tp_itemsize */
+	lmiter_dealloc,                  /*tp_dealloc */
+	0,                               /*tp_print */
+	0,                               /*tp_getattr */
+	0,                               /*tp_setattr */
+	0,                               /*tp_compare */
+	0,                               /*tp_repr */
+	0,                               /*tp_as_number */
+	0,                               /*tp_as_sequence */
+	0,                               /*tp_as_mapping */
+	0,                               /*tp_hash */
+	0,                               /*tp_call */
+	0,                               /*tp_str */
+	0,                               /*tp_getattro */
+	0,                               /*tp_setattro */
+	0,                               /*tp_as_buffer */
+	/* tp_flags: Py_TPFLAGS_HAVE_ITER tells python to
+	   use tp_iter and tp_iternext fields. */
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER,
+	"Iterator for 3-tuples in a lazymanifest.",  /* tp_doc */
+	0,                               /* tp_traverse */
+	0,                               /* tp_clear */
+	0,                               /* tp_richcompare */
+	0,                               /* tp_weaklistoffset */
+	PyObject_SelfIter,               /* tp_iter: __iter__() method */
+	lmiter_iterentriesnext,          /* tp_iternext: next() method */
+};
+
+static PyObject *lmiter_iterkeysnext(PyObject *o)
+{
+	size_t pl;
+	line *l = lmiter_nextline((lmIter *)o);
+	if (!l) {
+		return NULL;
+	}
+	pl = pathlen(l);
+	return PyString_FromStringAndSize(l->start, pl);
+}
+
+static PyTypeObject lazymanifestKeysIterator = {
+	PyObject_HEAD_INIT(NULL)
+	0,                               /*ob_size */
+	"parsers.lazymanifest.keysiterator", /*tp_name */
+	sizeof(lmIter),                  /*tp_basicsize */
+	0,                               /*tp_itemsize */
+	lmiter_dealloc,                  /*tp_dealloc */
+	0,                               /*tp_print */
+	0,                               /*tp_getattr */
+	0,                               /*tp_setattr */
+	0,                               /*tp_compare */
+	0,                               /*tp_repr */
+	0,                               /*tp_as_number */
+	0,                               /*tp_as_sequence */
+	0,                               /*tp_as_mapping */
+	0,                               /*tp_hash */
+	0,                               /*tp_call */
+	0,                               /*tp_str */
+	0,                               /*tp_getattro */
+	0,                               /*tp_setattro */
+	0,                               /*tp_as_buffer */
+	/* tp_flags: Py_TPFLAGS_HAVE_ITER tells python to
+	   use tp_iter and tp_iternext fields. */
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER,
+	"Keys iterator for a lazymanifest.",  /* tp_doc */
+	0,                               /* tp_traverse */
+	0,                               /* tp_clear */
+	0,                               /* tp_richcompare */
+	0,                               /* tp_weaklistoffset */
+	PyObject_SelfIter,               /* tp_iter: __iter__() method */
+	lmiter_iterkeysnext,             /* tp_iternext: next() method */
+};
+
+static lazymanifest *lazymanifest_copy(lazymanifest *self);
+
+static PyObject *lazymanifest_getentriesiter(lazymanifest *self)
+{
+	lmIter *i = NULL;
+	lazymanifest *t = lazymanifest_copy(self);
+	if (!t) {
+		PyErr_NoMemory();
+		return NULL;
+	}
+	i = PyObject_New(lmIter, &lazymanifestEntriesIterator);
+	if (i) {
+		i->m = t;
+		i->pos = -1;
+	} else {
+		Py_DECREF(t);
+		PyErr_NoMemory();
+	}
+	return (PyObject *)i;
+}
+
+static PyObject *lazymanifest_getkeysiter(lazymanifest *self)
+{
+	lmIter *i = NULL;
+	lazymanifest *t = lazymanifest_copy(self);
+	if (!t) {
+		PyErr_NoMemory();
+		return NULL;
+	}
+	i = PyObject_New(lmIter, &lazymanifestKeysIterator);
+	if (i) {
+		i->m = t;
+		i->pos = -1;
+	} else {
+		Py_DECREF(t);
+		PyErr_NoMemory();
+	}
+	return (PyObject *)i;
+}
+
+/* __getitem__ and __setitem__ support */
+
+static Py_ssize_t lazymanifest_size(lazymanifest *self)
+{
+	return self->livelines;
+}
+
+static int linecmp(const void *left, const void *right)
+{
+	return strcmp(((const line *)left)->start,
+		      ((const line *)right)->start);
+}
+
+static PyObject *lazymanifest_getitem(lazymanifest *self, PyObject *key)
+{
+	line needle;
+	line *hit;
+	if (!PyString_Check(key)) {
+		PyErr_Format(PyExc_TypeError,
+			     "getitem: manifest keys must be a string.");
+		return NULL;
+	}
+	needle.start = PyString_AsString(key);
+	hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
+		      &linecmp);
+	if (!hit || hit->deleted) {
+		PyErr_Format(PyExc_KeyError, "No such manifest entry.");
+		return NULL;
+	}
+	return hashflags(hit);
+}
+
+static int lazymanifest_delitem(lazymanifest *self, PyObject *key)
+{
+	line needle;
+	line *hit;
+	if (!PyString_Check(key)) {
+		PyErr_Format(PyExc_TypeError,
+			     "delitem: manifest keys must be a string.");
+		return -1;
+	}
+	needle.start = PyString_AsString(key);
+	hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
+		      &linecmp);
+	if (!hit || hit->deleted) {
+		PyErr_Format(PyExc_KeyError,
+			     "Tried to delete nonexistent manifest entry.");
+		return -1;
+	}
+	self->dirty = true;
+	hit->deleted = true;
+	self->livelines--;
+	return 0;
+}
+
+/* Do a binary search for the insertion point for new, creating the
+ * new entry if needed. */
+static int internalsetitem(lazymanifest *self, line *new) {
+	int start = 0, end = self->numlines;
+	while (start < end) {
+		int pos = start + (end - start) / 2;
+		int c = linecmp(new, self->lines + pos);
+		if (c < 0)
+			end = pos;
+		else if (c > 0)
+			start = pos + 1;
+		else {
+			if (self->lines[pos].deleted)
+				self->livelines++;
+			start = pos;
+			goto finish;
+		}
+	}
+	/* being here means we need to do an insert */
+	if (!realloc_if_full(self)) {
+		PyErr_NoMemory();
+		return -1;
+	}
+	memmove(self->lines + start + 1, self->lines + start,
+		(self->numlines - start) * sizeof(line));
+	self->numlines++;
+	self->livelines++;
+finish:
+	self->lines[start] = *new;
+	self->dirty = true;
+	return 0;
+}
+
+static int lazymanifest_setitem(
+	lazymanifest *self, PyObject *key, PyObject *value)
+{
+	char *path;
+	Py_ssize_t plen;
+	PyObject *pyhash;
+	Py_ssize_t hlen;
+	char *hash;
+	PyObject *pyflags;
+	char *flags;
+	Py_ssize_t flen;
+	size_t dlen;
+	char *dest;
+	int i;
+	line new;
+	if (!PyString_Check(key)) {
+		PyErr_Format(PyExc_TypeError,
+			     "setitem: manifest keys must be a string.");
+		return -1;
+	}
+	if (!value) {
+		return lazymanifest_delitem(self, key);
+	}
+	if (!PyTuple_Check(value) || PyTuple_Size(value) != 2) {
+		PyErr_Format(PyExc_TypeError,
+			     "Manifest values must be a tuple of (node, flags).");
+		return -1;
+	}
+	if (PyString_AsStringAndSize(key, &path, &plen) == -1) {
+		return -1;
+	}
+
+	pyhash = PyTuple_GetItem(value, 0);
+	if (!PyString_Check(pyhash)) {
+		PyErr_Format(PyExc_TypeError,
+			     "node must be a 20-byte string");
+		return -1;
+	}
+	hlen = PyString_Size(pyhash);
+	/* Some parts of the codebase try and set 21 or 22
+	 * byte "hash" values in order to perturb things for
+	 * status. We have to preserve at least the 21st
+	 * byte. Sigh. If there's a 22nd byte, we drop it on
+	 * the floor, which works fine.
+	 */
+	if (hlen != 20 && hlen != 21 && hlen != 22) {
+		PyErr_Format(PyExc_TypeError,
+			     "node must be a 20-byte string");
+		return -1;
+	}
+	hash = PyString_AsString(pyhash);
+
+	pyflags = PyTuple_GetItem(value, 1);
+	if (!PyString_Check(pyflags) || PyString_Size(pyflags) > 1) {
+		PyErr_Format(PyExc_TypeError,
+			     "flags must a 0 or 1 byte string");
+		return -1;
+	}
+	if (PyString_AsStringAndSize(pyflags, &flags, &flen) == -1) {
+		return -1;
+	}
+	/* one null byte and one newline */
+	dlen = plen + 41 + flen + 1;
+	dest = malloc(dlen);
+	if (!dest) {
+		PyErr_NoMemory();
+		return -1;
+	}
+	memcpy(dest, path, plen + 1);
+	for (i = 0; i < 20; i++) {
+		/* Cast to unsigned, so it will not get sign-extended when promoted
+		 * to int (as is done when passing to a variadic function)
+		 */
+		sprintf(dest + plen + 1 + (i * 2), "%02x", (unsigned char)hash[i]);
+	}
+	memcpy(dest + plen + 41, flags, flen);
+	dest[plen + 41 + flen] = '\n';
+	new.start = dest;
+	new.len = dlen;
+	new.hash_suffix = '\0';
+	if (hlen > 20) {
+		new.hash_suffix = hash[20];
+	}
+	new.from_malloc = true;     /* is `start` a pointer we allocated? */
+	new.deleted = false;        /* is this entry deleted? */
+	if (internalsetitem(self, &new)) {
+		return -1;
+	}
+	return 0;
+}
+
+static PyMappingMethods lazymanifest_mapping_methods = {
+	(lenfunc)lazymanifest_size,             /* mp_length */
+	(binaryfunc)lazymanifest_getitem,       /* mp_subscript */
+	(objobjargproc)lazymanifest_setitem,    /* mp_ass_subscript */
+};
+
+/* sequence methods (important or __contains__ builds an iterator */
+
+static int lazymanifest_contains(lazymanifest *self, PyObject *key)
+{
+	line needle;
+	line *hit;
+	if (!PyString_Check(key)) {
+		/* Our keys are always strings, so if the contains
+		 * check is for a non-string, just return false. */
+		return 0;
+	}
+	needle.start = PyString_AsString(key);
+	hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
+		      &linecmp);
+	if (!hit || hit->deleted) {
+		return 0;
+	}
+	return 1;
+}
+
+static PySequenceMethods lazymanifest_seq_meths = {
+	(lenfunc)lazymanifest_size, /* sq_length */
+	0, /* sq_concat */
+	0, /* sq_repeat */
+	0, /* sq_item */
+	0, /* sq_slice */
+	0, /* sq_ass_item */
+	0, /* sq_ass_slice */
+	(objobjproc)lazymanifest_contains, /* sq_contains */
+	0, /* sq_inplace_concat */
+	0, /* sq_inplace_repeat */
+};
+
+
+/* Other methods (copy, diff, etc) */
+static PyTypeObject lazymanifestType;
+
+/* If the manifest has changes, build the new manifest text and reindex it. */
+static int compact(lazymanifest *self) {
+	int i;
+	ssize_t need = 0;
+	char *data;
+	line *src, *dst;
+	PyObject *pydata;
+	if (!self->dirty)
+		return 0;
+	for (i = 0; i < self->numlines; i++) {
+		if (!self->lines[i].deleted) {
+			need += self->lines[i].len;
+		}
+	}
+	pydata = PyString_FromStringAndSize(NULL, need);
+	if (!pydata)
+		return -1;
+	data = PyString_AsString(pydata);
+	if (!data) {
+		return -1;
+	}
+	src = self->lines;
+	dst = self->lines;
+	for (i = 0; i < self->numlines; i++, src++) {
+		char *tofree = NULL;
+		if (src->from_malloc) {
+			tofree = src->start;
+		}
+		if (!src->deleted) {
+			memcpy(data, src->start, src->len);
+			*dst = *src;
+			dst->start = data;
+			dst->from_malloc = false;
+			data += dst->len;
+			dst++;
+		}
+		free(tofree);
+	}
+	Py_DECREF(self->pydata);
+	self->pydata = pydata;
+	self->numlines = self->livelines;
+	self->dirty = false;
+	return 0;
+}
+
+static PyObject *lazymanifest_text(lazymanifest *self)
+{
+	if (compact(self) != 0) {
+		PyErr_NoMemory();
+		return NULL;
+	}
+	Py_INCREF(self->pydata);
+	return self->pydata;
+}
+
+static lazymanifest *lazymanifest_copy(lazymanifest *self)
+{
+	lazymanifest *copy = NULL;
+	if (compact(self) != 0) {
+		goto nomem;
+	}
+	copy = PyObject_New(lazymanifest, &lazymanifestType);
+	if (!copy) {
+		goto nomem;
+	}
+	copy->numlines = self->numlines;
+	copy->livelines = self->livelines;
+	copy->dirty = false;
+	copy->lines = malloc(self->maxlines *sizeof(line));
+	if (!copy->lines) {
+		goto nomem;
+	}
+	memcpy(copy->lines, self->lines, self->numlines * sizeof(line));
+	copy->maxlines = self->maxlines;
+	copy->pydata = self->pydata;
+	Py_INCREF(copy->pydata);
+	return copy;
+ nomem:
+	PyErr_NoMemory();
+	Py_XDECREF(copy);
+	return NULL;
+}
+
+static lazymanifest *lazymanifest_filtercopy(
+	lazymanifest *self, PyObject *matchfn)
+{
+	lazymanifest *copy = NULL;
+	int i;
+	if (!PyCallable_Check(matchfn)) {
+		PyErr_SetString(PyExc_TypeError, "matchfn must be callable");
+		return NULL;
+	}
+	/* compact ourselves first to avoid double-frees later when we
+	 * compact tmp so that it doesn't have random pointers to our
+	 * underlying from_malloc-data (self->pydata is safe) */
+	if (compact(self) != 0) {
+		goto nomem;
+	}
+	copy = PyObject_New(lazymanifest, &lazymanifestType);
+	copy->dirty = true;
+	copy->lines = malloc(self->maxlines * sizeof(line));
+	if (!copy->lines) {
+		goto nomem;
+	}
+	copy->maxlines = self->maxlines;
+	copy->numlines = 0;
+	copy->pydata = self->pydata;
+	Py_INCREF(self->pydata);
+	for (i = 0; i < self->numlines; i++) {
+		PyObject *arg = PyString_FromString(self->lines[i].start);
+		PyObject *arglist = PyTuple_Pack(1, arg);
+		PyObject *result = PyObject_CallObject(matchfn, arglist);
+		Py_DECREF(arglist);
+		Py_DECREF(arg);
+		/* if the callback raised an exception, just let it
+		 * through and give up */
+		if (!result) {
+			free(copy->lines);
+			Py_DECREF(self->pydata);
+			return NULL;
+		}
+		if (PyObject_IsTrue(result)) {
+			assert(!(self->lines[i].from_malloc));
+			copy->lines[copy->numlines++] = self->lines[i];
+		}
+		Py_DECREF(result);
+	}
+	copy->livelines = copy->numlines;
+	return copy;
+ nomem:
+	PyErr_NoMemory();
+	Py_XDECREF(copy);
+	return NULL;
+}
+
+static PyObject *lazymanifest_diff(lazymanifest *self, PyObject *args)
+{
+	lazymanifest *other;
+	PyObject *pyclean = NULL;
+	bool listclean;
+	PyObject *emptyTup = NULL, *ret = NULL;
+	PyObject *es;
+	int sneedle = 0, oneedle = 0;
+	if (!PyArg_ParseTuple(args, "O!|O", &lazymanifestType, &other, &pyclean)) {
+		return NULL;
+	}
+	listclean = (!pyclean) ? false : PyObject_IsTrue(pyclean);
+	es = PyString_FromString("");
+	if (!es) {
+		goto nomem;
+	}
+	emptyTup = PyTuple_Pack(2, Py_None, es);
+	Py_DECREF(es);
+	if (!emptyTup) {
+		goto nomem;
+	}
+	ret = PyDict_New();
+	if (!ret) {
+		goto nomem;
+	}
+	while (sneedle != self->numlines || oneedle != other->numlines) {
+		line *left = self->lines + sneedle;
+		line *right = other->lines + oneedle;
+		int result;
+		PyObject *key;
+		PyObject *outer;
+		/* If we're looking at a deleted entry and it's not
+		 * the end of the manifest, just skip it. */
+		if (left->deleted && sneedle < self->numlines) {
+			sneedle++;
+			continue;
+		}
+		if (right->deleted && oneedle < other->numlines) {
+			oneedle++;
+			continue;
+		}
+		/* if we're at the end of either manifest, then we
+		 * know the remaining items are adds so we can skip
+		 * the strcmp. */
+		if (sneedle == self->numlines) {
+			result = 1;
+		} else if (oneedle == other->numlines) {
+			result = -1;
+		} else {
+			result = linecmp(left, right);
+		}
+		key = result <= 0 ?
+			PyString_FromString(left->start) :
+			PyString_FromString(right->start);
+		if (!key)
+			goto nomem;
+		if (result < 0) {
+			PyObject *l = hashflags(left);
+			if (!l) {
+				goto nomem;
+			}
+			outer = PyTuple_Pack(2, l, emptyTup);
+			Py_DECREF(l);
+			if (!outer) {
+				goto nomem;
+			}
+			PyDict_SetItem(ret, key, outer);
+			Py_DECREF(outer);
+			sneedle++;
+		} else if (result > 0) {
+			PyObject *r = hashflags(right);
+			if (!r) {
+				goto nomem;
+			}
+			outer = PyTuple_Pack(2, emptyTup, r);
+			Py_DECREF(r);
+			if (!outer) {
+				goto nomem;
+			}
+			PyDict_SetItem(ret, key, outer);
+			Py_DECREF(outer);
+			oneedle++;
+		} else {
+			/* file exists in both manifests */
+			if (left->len != right->len
+			    || memcmp(left->start, right->start, left->len)
+			    || left->hash_suffix != right->hash_suffix) {
+				PyObject *l = hashflags(left);
+				PyObject *r;
+				if (!l) {
+					goto nomem;
+				}
+				r = hashflags(right);
+				if (!r) {
+					Py_DECREF(l);
+					goto nomem;
+				}
+				outer = PyTuple_Pack(2, l, r);
+				Py_DECREF(l);
+				Py_DECREF(r);
+				if (!outer) {
+					goto nomem;
+				}
+				PyDict_SetItem(ret, key, outer);
+				Py_DECREF(outer);
+			} else if (listclean) {
+				PyDict_SetItem(ret, key, Py_None);
+			}
+			sneedle++;
+			oneedle++;
+		}
+		Py_DECREF(key);
+	}
+	Py_DECREF(emptyTup);
+	return ret;
+ nomem:
+	PyErr_NoMemory();
+	Py_XDECREF(ret);
+	Py_XDECREF(emptyTup);
+	return NULL;
+}
+
+static PyMethodDef lazymanifest_methods[] = {
+	{"iterkeys", (PyCFunction)lazymanifest_getkeysiter, METH_NOARGS,
+	 "Iterate over file names in this lazymanifest."},
+	{"iterentries", (PyCFunction)lazymanifest_getentriesiter, METH_NOARGS,
+	 "Iterate over (path, nodeid, flags) typles in this lazymanifest."},
+	{"copy", (PyCFunction)lazymanifest_copy, METH_NOARGS,
+	 "Make a copy of this lazymanifest."},
+	{"filtercopy", (PyCFunction)lazymanifest_filtercopy, METH_O,
+	 "Make a copy of this manifest filtered by matchfn."},
+	{"diff", (PyCFunction)lazymanifest_diff, METH_VARARGS,
+	 "Compare this lazymanifest to another one."},
+	{"text", (PyCFunction)lazymanifest_text, METH_NOARGS,
+	 "Encode this manifest to text."},
+	{NULL},
+};
+
+static PyTypeObject lazymanifestType = {
+	PyObject_HEAD_INIT(NULL)
+	0,                                                /* ob_size */
+	"parsers.lazymanifest",                           /* tp_name */
+	sizeof(lazymanifest),                             /* tp_basicsize */
+	0,                                                /* tp_itemsize */
+	(destructor)lazymanifest_dealloc,                 /* tp_dealloc */
+	0,                                                /* tp_print */
+	0,                                                /* tp_getattr */
+	0,                                                /* tp_setattr */
+	0,                                                /* tp_compare */
+	0,                                                /* tp_repr */
+	0,                                                /* tp_as_number */
+	&lazymanifest_seq_meths,                          /* tp_as_sequence */
+	&lazymanifest_mapping_methods,                    /* tp_as_mapping */
+	0,                                                /* tp_hash */
+	0,                                                /* tp_call */
+	0,                                                /* tp_str */
+	0,                                                /* tp_getattro */
+	0,                                                /* tp_setattro */
+	0,                                                /* tp_as_buffer */
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_SEQUENCE_IN, /* tp_flags */
+	"TODO(augie)",                                    /* tp_doc */
+	0,                                                /* tp_traverse */
+	0,                                                /* tp_clear */
+	0,                                                /* tp_richcompare */
+	0,                                             /* tp_weaklistoffset */
+	(getiterfunc)lazymanifest_getkeysiter,                /* tp_iter */
+	0,                                                /* tp_iternext */
+	lazymanifest_methods,                             /* tp_methods */
+	0,                                                /* tp_members */
+	0,                                                /* tp_getset */
+	0,                                                /* tp_base */
+	0,                                                /* tp_dict */
+	0,                                                /* tp_descr_get */
+	0,                                                /* tp_descr_set */
+	0,                                                /* tp_dictoffset */
+	(initproc)lazymanifest_init,                      /* tp_init */
+	0,                                                /* tp_alloc */
+};
+
+void manifest_module_init(PyObject * mod)
+{
+	lazymanifestType.tp_new = PyType_GenericNew;
+	if (PyType_Ready(&lazymanifestType) < 0)
+		return;
+	Py_INCREF(&lazymanifestType);
+
+	PyModule_AddObject(mod, "lazymanifest",
+			   (PyObject *)&lazymanifestType);
+}
--- a/mercurial/manifest.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/manifest.py	Fri Mar 13 17:55:04 2015 -0500
@@ -9,37 +9,144 @@
 import mdiff, parsers, error, revlog, util
 import array, struct
 
-class manifestdict(dict):
-    def __init__(self, mapping=None, flags=None):
-        if mapping is None:
-            mapping = {}
-        if flags is None:
-            flags = {}
-        dict.__init__(self, mapping)
-        self._flags = flags
+
+class _lazymanifest(dict):
+    """This is the pure implementation of lazymanifest.
+
+    It has not been optimized *at all* and is not lazy.
+    """
+
+    def __init__(self, data):
+        # This init method does a little bit of excessive-looking
+        # precondition checking. This is so that the behavior of this
+        # class exactly matches its C counterpart to try and help
+        # prevent surprise breakage for anyone that develops against
+        # the pure version.
+        if data and data[-1] != '\n':
+            raise ValueError('Manifest did not end in a newline.')
+        dict.__init__(self)
+        prev = None
+        for l in data.splitlines():
+            if prev is not None and prev > l:
+                raise ValueError('Manifest lines not in sorted order.')
+            prev = l
+            f, n = l.split('\0')
+            if len(n) > 40:
+                self[f] = revlog.bin(n[:40]), n[40:]
+            else:
+                self[f] = revlog.bin(n), ''
+
     def __setitem__(self, k, v):
-        assert v is not None
-        dict.__setitem__(self, k, v)
-    def flags(self, f):
-        return self._flags.get(f, "")
-    def setflag(self, f, flags):
-        """Set the flags (symlink, executable) for path f."""
-        self._flags[f] = flags
+        node, flag = v
+        assert node is not None
+        if len(node) > 21:
+            node = node[:21] # match c implementation behavior
+        dict.__setitem__(self, k, (node, flag))
+
+    def __iter__(self):
+        return iter(sorted(dict.keys(self)))
+
+    def iterkeys(self):
+        return iter(sorted(dict.keys(self)))
+
+    def iterentries(self):
+        return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
+
     def copy(self):
-        return manifestdict(self, dict.copy(self._flags))
+        c = _lazymanifest('')
+        c.update(self)
+        return c
+
+    def diff(self, m2, clean=False):
+        '''Finds changes between the current manifest and m2.'''
+        diff = {}
+
+        for fn, e1 in self.iteritems():
+            if fn not in m2:
+                diff[fn] = e1, (None, '')
+            else:
+                e2 = m2[fn]
+                if e1 != e2:
+                    diff[fn] = e1, e2
+                elif clean:
+                    diff[fn] = None
+
+        for fn, e2 in m2.iteritems():
+            if fn not in self:
+                diff[fn] = (None, ''), e2
+
+        return diff
+
+    def filtercopy(self, filterfn):
+        c = _lazymanifest('')
+        for f, n, fl in self.iterentries():
+            if filterfn(f):
+                c[f] = n, fl
+        return c
+
+    def text(self):
+        """Get the full data of this manifest as a bytestring."""
+        fl = sorted(self.iterentries())
+
+        _hex = revlog.hex
+        # if this is changed to support newlines in filenames,
+        # be sure to check the templates/ dir again (especially *-raw.tmpl)
+        return ''.join("%s\0%s%s\n" % (
+            f, _hex(n[:20]), flag) for f, n, flag in fl)
+
+try:
+    _lazymanifest = parsers.lazymanifest
+except AttributeError:
+    pass
+
+class manifestdict(object):
+    def __init__(self, data=''):
+        self._lm = _lazymanifest(data)
+
+    def __getitem__(self, key):
+        return self._lm[key][0]
+
+    def find(self, key):
+        return self._lm[key]
+
+    def __len__(self):
+        return len(self._lm)
+
+    def __setitem__(self, key, node):
+        self._lm[key] = node, self.flags(key, '')
+
+    def __contains__(self, key):
+        return key in self._lm
+
+    def __delitem__(self, key):
+        del self._lm[key]
+
+    def __iter__(self):
+        return self._lm.__iter__()
+
+    def iterkeys(self):
+        return self._lm.iterkeys()
+
+    def keys(self):
+        return list(self.iterkeys())
+
     def intersectfiles(self, files):
-        '''make a new manifestdict with the intersection of self with files
+        '''make a new lazymanifest with the intersection of self with files
 
         The algorithm assumes that files is much smaller than self.'''
         ret = manifestdict()
+        lm = self._lm
         for fn in files:
-            if fn in self:
-                ret[fn] = self[fn]
-                flags = self._flags.get(fn, None)
-                if flags:
-                    ret._flags[fn] = flags
+            if fn in lm:
+                ret._lm[fn] = self._lm[fn]
         return ret
 
+    def filesnotin(self, m2):
+        '''Set of files in this manifest that are not in the other'''
+        files = set(self)
+        files.difference_update(m2)
+        return files
+
     def matches(self, match):
         '''generate a new manifest filtered by the match argument'''
         if match.always():
@@ -50,11 +157,9 @@
             (not match.anypats() and util.all(fn in self for fn in files))):
             return self.intersectfiles(files)
 
-        mf = self.copy()
-        for fn in mf.keys():
-            if not match(fn):
-                del mf[fn]
-        return mf
+        lm = manifestdict('')
+        lm._lm = self._lm.filtercopy(match)
+        return lm
 
     def diff(self, m2, clean=False):
         '''Finds changes between the current manifest and m2.
@@ -71,35 +176,33 @@
         the nodeid will be None and the flags will be the empty
         string.
         '''
-        diff = {}
+        return self._lm.diff(m2._lm, clean)
+
+    def setflag(self, key, flag):
+        self._lm[key] = self[key], flag
+
+    def get(self, key, default=None):
+        try:
+            return self._lm[key][0]
+        except KeyError:
+            return default
 
-        for fn, n1 in self.iteritems():
-            fl1 = self._flags.get(fn, '')
-            n2 = m2.get(fn, None)
-            fl2 = m2._flags.get(fn, '')
-            if n2 is None:
-                fl2 = ''
-            if n1 != n2 or fl1 != fl2:
-                diff[fn] = ((n1, fl1), (n2, fl2))
-            elif clean:
-                diff[fn] = None
+    def flags(self, key, default=''):
+        try:
+            return self._lm[key][1]
+        except KeyError:
+            return default
 
-        for fn, n2 in m2.iteritems():
-            if fn not in self:
-                fl2 = m2._flags.get(fn, '')
-                diff[fn] = ((None, ''), (n2, fl2))
+    def copy(self):
+        c = manifestdict('')
+        c._lm = self._lm.copy()
+        return c
 
-        return diff
+    def iteritems(self):
+        return (x[:2] for x in self._lm.iterentries())
 
     def text(self):
-        """Get the full data of this manifest as a bytestring."""
-        fl = sorted(self)
-        _checkforbidden(fl)
-
-        hex, flags = revlog.hex, self.flags
-        # if this is changed to support newlines in filenames,
-        # be sure to check the templates/ dir again (especially *-raw.tmpl)
-        return ''.join("%s\0%s%s\n" % (f, hex(self[f]), flags(f)) for f in fl)
+        return self._lm.text()
 
     def fastdelta(self, base, changes):
         """Given a base manifest text as an array.array and a list of changes
@@ -119,7 +222,8 @@
             # bs will either be the index of the item or the insert point
             start, end = _msearch(addbuf, f, start)
             if not todelete:
-                l = "%s\0%s%s\n" % (f, revlog.hex(self[f]), self.flags(f))
+                h, fl = self._lm[f]
+                l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
             else:
                 if start == end:
                     # item we want to delete was not found, error out
@@ -213,21 +317,22 @@
                    + content for start, end, content in x)
     return deltatext, newaddlist
 
-def _parse(lines):
-    mfdict = manifestdict()
-    parsers.parse_manifest(mfdict, mfdict._flags, lines)
-    return mfdict
-
 class manifest(revlog.revlog):
     def __init__(self, opener):
-        # we expect to deal with not more than four revs at a time,
-        # during a commit --amend
-        self._mancache = util.lrucachedict(4)
+        # During normal operations, we expect to deal with not more than four
+        # revs at a time (such as during commit --amend). When rebasing large
+        # stacks of commits, the number can go up, hence the config knob below.
+        cachesize = 4
+        opts = getattr(opener, 'options', None)
+        if opts is not None:
+            cachesize = opts.get('manifestcachesize', cachesize)
+        self._mancache = util.lrucachedict(cachesize)
         revlog.revlog.__init__(self, opener, "00manifest.i")
 
     def readdelta(self, node):
         r = self.rev(node)
-        return _parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
+        d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
+        return manifestdict(d)
 
     def readfast(self, node):
         '''use the faster of readdelta or read'''
@@ -244,25 +349,20 @@
             return self._mancache[node][0]
         text = self.revision(node)
         arraytext = array.array('c', text)
-        mapping = _parse(text)
-        self._mancache[node] = (mapping, arraytext)
-        return mapping
+        m = manifestdict(text)
+        self._mancache[node] = (m, arraytext)
+        return m
 
     def find(self, node, f):
         '''look up entry for a single file efficiently.
         return (node, flags) pair if found, (None, None) if not.'''
-        if node in self._mancache:
-            mapping = self._mancache[node][0]
-            return mapping.get(f), mapping.flags(f)
-        text = self.revision(node)
-        start, end = _msearch(text, f)
-        if start == end:
+        m = self.read(node)
+        try:
+            return m.find(f)
+        except KeyError:
             return None, None
-        l = text[start:end]
-        f, n = l.split('\0')
-        return revlog.bin(n[:40]), n[40:-1]
 
-    def add(self, map, transaction, link, p1, p2, added, removed):
+    def add(self, m, transaction, link, p1, p2, added, removed):
         if p1 in self._mancache:
             # If our first parent is in the manifest cache, we can
             # compute a delta here using properties we know about the
@@ -277,7 +377,7 @@
             # since the lists are already sorted
             work.sort()
 
-            arraytext, deltatext = map.fastdelta(self._mancache[p1][1], work)
+            arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
             cachedelta = self.rev(p1), deltatext
             text = util.buffer(arraytext)
         else:
@@ -285,11 +385,11 @@
             # just encode a fulltext of the manifest and pass that
             # through to the revlog layer, and let it handle the delta
             # process.
-            text = map.text()
+            text = m.text()
             arraytext = array.array('c', text)
             cachedelta = None
 
         n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
-        self._mancache[n] = (map, arraytext)
+        self._mancache[n] = (m, arraytext)
 
         return n
--- a/mercurial/mdiff.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/mdiff.py	Fri Mar 13 17:55:04 2015 -0500
@@ -367,6 +367,9 @@
 def trivialdiffheader(length):
     return struct.pack(">lll", 0, 0, length)
 
+def replacediffheader(oldlen, newlen):
+    return struct.pack(">lll", 0, oldlen, newlen)
+
 patches = mpatch.patches
 patchedsize = mpatch.patchedsize
 textdiff = bdiff.bdiff
--- a/mercurial/namespaces.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/namespaces.py	Fri Mar 13 17:55:04 2015 -0500
@@ -142,7 +142,7 @@
                  is used
         colorname: the name to use for colored log output; if not specified
                    logname is used
-        logfmt: the format to use for (l10n-ed) log output; if not specified
+        logfmt: the format to use for (i18n-ed) log output; if not specified
                 it is composed from logname
         listnames: function to list all names
         namemap: function that inputs a node, output name(s)
--- a/mercurial/obsolete.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/obsolete.py	Fri Mar 13 17:55:04 2015 -0500
@@ -68,15 +68,14 @@
 
 """
 import struct
-import util, base85, node
+import util, base85, node, parsers
 import phases
 from i18n import _
 
 _pack = struct.pack
 _unpack = struct.unpack
 _calcsize = struct.calcsize
-
-_SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
+propertycache = util.propertycache
 
 # the obsolete feature is not mature enough to be enabled by default.
 # you have to rely on third party extension extension to enable this.
@@ -146,7 +145,7 @@
 _fm0fsize = _calcsize(_fm0fixed)
 _fm0fnodesize = _calcsize(_fm0node)
 
-def _fm0readmarkers(data, off=0):
+def _fm0readmarkers(data, off):
     # Loop on markers
     l = len(data)
     while off + _fm0fsize <= l:
@@ -285,7 +284,7 @@
 _fm1metapair = 'BB'
 _fm1metapairsize = _calcsize('BB')
 
-def _fm1readmarkers(data, off=0):
+def _fm1purereadmarkers(data, off):
     # make some global constants local for performance
     noneflag = _fm1parentnone
     sha2flag = usingsha256
@@ -301,6 +300,7 @@
     # Loop on markers
     stop = len(data) - _fm1fsize
     ufixed = util.unpacker(_fm1fixed)
+
     while off <= stop:
         # read fixed part
         o1 = off + fsize
@@ -395,6 +395,13 @@
         data.append(value)
     return ''.join(data)
 
+def _fm1readmarkers(data, off):
+    native = getattr(parsers, 'fm1readmarkers', None)
+    if not native:
+        return _fm1purereadmarkers(data, off)
+    stop = len(data) - _fm1fsize
+    return native(data, off, stop)
+
 # mapping to read/write various marker formats
 # <version> -> (decoder, encoder)
 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
@@ -462,15 +469,35 @@
         """The flags field of the marker"""
         return self._data[2]
 
-def _checkinvalidmarkers(obsstore):
+@util.nogc
+def _addsuccessors(successors, markers):
+    for mark in markers:
+        successors.setdefault(mark[0], set()).add(mark)
+
+@util.nogc
+def _addprecursors(precursors, markers):
+    for mark in markers:
+        for suc in mark[1]:
+            precursors.setdefault(suc, set()).add(mark)
+
+@util.nogc
+def _addchildren(children, markers):
+    for mark in markers:
+        parents = mark[5]
+        if parents is not None:
+            for p in parents:
+                children.setdefault(p, set()).add(mark)
+
+def _checkinvalidmarkers(markers):
     """search for marker with invalid data and raise error if needed
 
     Exist as a separated function to allow the evolve extension for a more
     subtle handling.
     """
-    if node.nullid in obsstore.precursors:
-        raise util.Abort(_('bad obsolescence marker detected: '
-                           'invalid successors nullid'))
+    for mark in markers:
+        if node.nullid in mark[1]:
+            raise util.Abort(_('bad obsolescence marker detected: '
+                               'invalid successors nullid'))
 
 class obsstore(object):
     """Store obsolete markers
@@ -494,16 +521,13 @@
         # caches for various obsolescence related cache
         self.caches = {}
         self._all = []
-        self.precursors = {}
-        self.successors = {}
-        self.children = {}
         self.sopener = sopener
         data = sopener.tryread('obsstore')
         self._version = defaultformat
         self._readonly = readonly
         if data:
             self._version, markers = _readmarkers(data)
-            self._load(markers)
+            self._addmarkers(markers)
 
     def __iter__(self):
         return iter(self._all)
@@ -566,12 +590,6 @@
         if new:
             f = self.sopener('obsstore', 'ab')
             try:
-                # Whether the file's current position is at the begin or at
-                # the end after opening a file for appending is implementation
-                # defined. So we must seek to the end before calling tell(),
-                # or we may get a zero offset for non-zero sized files on
-                # some platforms (issue3543).
-                f.seek(0, _SEEK_END)
                 offset = f.tell()
                 transaction.add('obsstore', offset)
                 # offset == 0: new file - add the version header
@@ -581,7 +599,7 @@
                 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
                 # call 'filecacheentry.refresh()'  here
                 f.close()
-            self._load(new)
+            self._addmarkers(new)
             # new marker *may* have changed several set. invalidate the cache.
             self.caches.clear()
         # records the number of new markers for the transaction hooks
@@ -596,19 +614,37 @@
         version, markers = _readmarkers(data)
         return self.add(transaction, markers)
 
-    @util.nogc
-    def _load(self, markers):
-        for mark in markers:
-            self._all.append(mark)
-            pre, sucs = mark[:2]
-            self.successors.setdefault(pre, set()).add(mark)
-            for suc in sucs:
-                self.precursors.setdefault(suc, set()).add(mark)
-            parents = mark[5]
-            if parents is not None:
-                for p in parents:
-                    self.children.setdefault(p, set()).add(mark)
-        _checkinvalidmarkers(self)
+    @propertycache
+    def successors(self):
+        successors = {}
+        _addsuccessors(successors, self._all)
+        return successors
+
+    @propertycache
+    def precursors(self):
+        precursors = {}
+        _addprecursors(precursors, self._all)
+        return precursors
+
+    @propertycache
+    def children(self):
+        children = {}
+        _addchildren(children, self._all)
+        return children
+
+    def _cached(self, attr):
+        return attr in self.__dict__
+
+    def _addmarkers(self, markers):
+        markers = list(markers) # to allow repeated iteration
+        self._all.extend(markers)
+        if self._cached('successors'):
+            _addsuccessors(self.successors, markers)
+        if self._cached('precursors'):
+            _addprecursors(self.precursors, markers)
+        if self._cached('children'):
+            _addchildren(self.children, markers)
+        _checkinvalidmarkers(markers)
 
     def relevantmarkers(self, nodes):
         """return a set of all obsolescence markers relevant to a set of nodes.
--- a/mercurial/parsers.c	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/parsers.c	Fri Mar 13 17:55:04 2015 -0500
@@ -71,7 +71,7 @@
 /*
  * Turn a hex-encoded string into binary.
  */
-static PyObject *unhexlify(const char *str, int len)
+PyObject *unhexlify(const char *str, int len)
 {
 	PyObject *ret;
 	char *d;
@@ -1676,108 +1676,6 @@
 }
 
 /*
- * Given a (possibly overlapping) set of revs, return the greatest
- * common ancestors: those with the longest path to the root.
- */
-static PyObject *index_ancestors(indexObject *self, PyObject *args)
-{
-	PyObject *ret = NULL, *gca = NULL;
-	Py_ssize_t argcount, i, len;
-	bitmask repeat = 0;
-	int revcount = 0;
-	int *revs;
-
-	argcount = PySequence_Length(args);
-	revs = malloc(argcount * sizeof(*revs));
-	if (argcount > 0 && revs == NULL)
-		return PyErr_NoMemory();
-	len = index_length(self) - 1;
-
-	for (i = 0; i < argcount; i++) {
-		static const int capacity = 24;
-		PyObject *obj = PySequence_GetItem(args, i);
-		bitmask x;
-		long val;
-
-		if (!PyInt_Check(obj)) {
-			PyErr_SetString(PyExc_TypeError,
-					"arguments must all be ints");
-			Py_DECREF(obj);
-			goto bail;
-		}
-		val = PyInt_AsLong(obj);
-		Py_DECREF(obj);
-		if (val == -1) {
-			ret = PyList_New(0);
-			goto done;
-		}
-		if (val < 0 || val >= len) {
-			PyErr_SetString(PyExc_IndexError,
-					"index out of range");
-			goto bail;
-		}
-		/* this cheesy bloom filter lets us avoid some more
-		 * expensive duplicate checks in the common set-is-disjoint
-		 * case */
-		x = 1ull << (val & 0x3f);
-		if (repeat & x) {
-			int k;
-			for (k = 0; k < revcount; k++) {
-				if (val == revs[k])
-					goto duplicate;
-			}
-		}
-		else repeat |= x;
-		if (revcount >= capacity) {
-			PyErr_Format(PyExc_OverflowError,
-				     "bitset size (%d) > capacity (%d)",
-				     revcount, capacity);
-			goto bail;
-		}
-		revs[revcount++] = (int)val;
-	duplicate:;
-	}
-
-	if (revcount == 0) {
-		ret = PyList_New(0);
-		goto done;
-	}
-	if (revcount == 1) {
-		PyObject *obj;
-		ret = PyList_New(1);
-		if (ret == NULL)
-			goto bail;
-		obj = PyInt_FromLong(revs[0]);
-		if (obj == NULL)
-			goto bail;
-		PyList_SET_ITEM(ret, 0, obj);
-		goto done;
-	}
-
-	gca = find_gca_candidates(self, revs, revcount);
-	if (gca == NULL)
-		goto bail;
-
-	if (PyList_GET_SIZE(gca) <= 1) {
-		ret = gca;
-		Py_INCREF(gca);
-	}
-	else ret = find_deepest(self, gca);
-
-done:
-	free(revs);
-	Py_XDECREF(gca);
-
-	return ret;
-
-bail:
-	free(revs);
-	Py_XDECREF(gca);
-	Py_XDECREF(ret);
-	return NULL;
-}
-
-/*
  * Given a (possibly overlapping) set of revs, return all the
  * common ancestors heads: heads(::args[0] and ::a[1] and ...)
  */
@@ -1871,6 +1769,24 @@
 }
 
 /*
+ * Given a (possibly overlapping) set of revs, return the greatest
+ * common ancestors: those with the longest path to the root.
+ */
+static PyObject *index_ancestors(indexObject *self, PyObject *args)
+{
+	PyObject *gca = index_commonancestorsheads(self, args);
+	if (gca == NULL)
+		return NULL;
+
+	if (PyList_GET_SIZE(gca) <= 1) {
+		Py_INCREF(gca);
+		return gca;
+	}
+
+	return find_deepest(self, gca);
+}
+
+/*
  * Invalidate any trie entries introduced by added revs.
  */
 static void nt_invalidate_added(indexObject *self, Py_ssize_t start)
@@ -2230,6 +2146,157 @@
 	return NULL;
 }
 
+#define BUMPED_FIX 1
+#define USING_SHA_256 2
+
+static PyObject *readshas(
+	const char *source, unsigned char num, Py_ssize_t hashwidth)
+{
+	int i;
+	PyObject *list = PyTuple_New(num);
+	if (list == NULL) {
+		return NULL;
+	}
+	for (i = 0; i < num; i++) {
+		PyObject *hash = PyString_FromStringAndSize(source, hashwidth);
+		if (hash == NULL) {
+			Py_DECREF(list);
+			return NULL;
+		}
+		PyTuple_SetItem(list, i, hash);
+		source += hashwidth;
+	}
+	return list;
+}
+
+static PyObject *fm1readmarker(const char *data, uint32_t *msize)
+{
+	const char *meta;
+
+	double mtime;
+	int16_t tz;
+	uint16_t flags;
+	unsigned char nsuccs, nparents, nmetadata;
+	Py_ssize_t hashwidth = 20;
+
+	PyObject *prec = NULL, *parents = NULL, *succs = NULL;
+	PyObject *metadata = NULL, *ret = NULL;
+	int i;
+
+	*msize = getbe32(data);
+	data += 4;
+	mtime = getbefloat64(data);
+	data += 8;
+	tz = getbeint16(data);
+	data += 2;
+	flags = getbeuint16(data);
+	data += 2;
+
+	if (flags & USING_SHA_256) {
+		hashwidth = 32;
+	}
+
+	nsuccs = (unsigned char)(*data++);
+	nparents = (unsigned char)(*data++);
+	nmetadata = (unsigned char)(*data++);
+
+	prec = PyString_FromStringAndSize(data, hashwidth);
+	data += hashwidth;
+	if (prec == NULL) {
+		goto bail;
+	}
+
+	succs = readshas(data, nsuccs, hashwidth);
+	if (succs == NULL) {
+		goto bail;
+	}
+	data += nsuccs * hashwidth;
+
+	if (nparents == 1 || nparents == 2) {
+		parents = readshas(data, nparents, hashwidth);
+		if (parents == NULL) {
+			goto bail;
+		}
+		data += nparents * hashwidth;
+	} else {
+		parents = Py_None;
+	}
+
+	meta = data + (2 * nmetadata);
+	metadata = PyTuple_New(nmetadata);
+	if (metadata == NULL) {
+		goto bail;
+	}
+	for (i = 0; i < nmetadata; i++) {
+		PyObject *tmp, *left = NULL, *right = NULL;
+		Py_ssize_t metasize = (unsigned char)(*data++);
+		left = PyString_FromStringAndSize(meta, metasize);
+		meta += metasize;
+		metasize = (unsigned char)(*data++);
+		right = PyString_FromStringAndSize(meta, metasize);
+		meta += metasize;
+		if (!left || !right) {
+			Py_XDECREF(left);
+			Py_XDECREF(right);
+			goto bail;
+		}
+		tmp = PyTuple_Pack(2, left, right);
+		Py_DECREF(left);
+		Py_DECREF(right);
+		if (!tmp) {
+			goto bail;
+		}
+		PyTuple_SetItem(metadata, i, tmp);
+	}
+	ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags,
+			    metadata, mtime, (int)tz * 60, parents);
+bail:
+	Py_XDECREF(prec);
+	Py_XDECREF(succs);
+	Py_XDECREF(metadata);
+	if (parents != Py_None)
+		Py_XDECREF(parents);
+	return ret;
+}
+
+
+static PyObject *fm1readmarkers(PyObject *self, PyObject *args) {
+	const char *data;
+	Py_ssize_t datalen;
+	/* only unsigned long because python 2.4, should be Py_ssize_t */
+	unsigned long offset, stop;
+	PyObject *markers = NULL;
+
+	/* replace kk with nn when we drop Python 2.4 */
+	if (!PyArg_ParseTuple(args, "s#kk", &data, &datalen, &offset, &stop)) {
+		return NULL;
+	}
+	data += offset;
+	markers = PyList_New(0);
+	if (!markers) {
+		return NULL;
+	}
+	while (offset < stop) {
+		uint32_t msize;
+		int error;
+		PyObject *record = fm1readmarker(data, &msize);
+		if (!record) {
+			goto bail;
+		}
+		error = PyList_Append(markers, record);
+		Py_DECREF(record);
+		if (error) {
+			goto bail;
+		}
+		data += msize;
+		offset += msize;
+	}
+	return markers;
+bail:
+	Py_DECREF(markers);
+	return NULL;
+}
+
 static char parsers_doc[] = "Efficient content parsing.";
 
 PyObject *encodedir(PyObject *self, PyObject *args);
@@ -2245,10 +2312,13 @@
 	{"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
 	{"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
 	{"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
+	{"fm1readmarkers", fm1readmarkers, METH_VARARGS,
+			"parse v1 obsolete markers\n"},
 	{NULL, NULL}
 };
 
 void dirs_module_init(PyObject *mod);
+void manifest_module_init(PyObject *mod);
 
 static void module_init(PyObject *mod)
 {
@@ -2263,6 +2333,7 @@
 	PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
 
 	dirs_module_init(mod);
+	manifest_module_init(mod);
 
 	indexType.tp_new = PyType_GenericNew;
 	if (PyType_Ready(&indexType) < 0 ||
--- a/mercurial/patch.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/patch.py	Fri Mar 13 17:55:04 2015 -0500
@@ -6,7 +6,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import cStringIO, email, os, errno, re, posixpath
+import cStringIO, email, os, errno, re, posixpath, copy
 import tempfile, zlib, shutil
 # On python2.4 you have to import these by name or they fail to
 # load. This was not a problem on Python 2.7.
@@ -804,6 +804,262 @@
         self.write_rej()
         return len(self.rej)
 
+class header(object):
+    """patch header
+    """
+    diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
+    diff_re = re.compile('diff -r .* (.*)$')
+    allhunks_re = re.compile('(?:index|deleted file) ')
+    pretty_re = re.compile('(?:new file|deleted file) ')
+    special_re = re.compile('(?:index|new|deleted|copy|rename) ')
+
+    def __init__(self, header):
+        self.header = header
+        self.hunks = []
+
+    def binary(self):
+        return util.any(h.startswith('index ') for h in self.header)
+
+    def pretty(self, fp):
+        for h in self.header:
+            if h.startswith('index '):
+                fp.write(_('this modifies a binary file (all or nothing)\n'))
+                break
+            if self.pretty_re.match(h):
+                fp.write(h)
+                if self.binary():
+                    fp.write(_('this is a binary file\n'))
+                break
+            if h.startswith('---'):
+                fp.write(_('%d hunks, %d lines changed\n') %
+                         (len(self.hunks),
+                          sum([max(h.added, h.removed) for h in self.hunks])))
+                break
+            fp.write(h)
+
+    def write(self, fp):
+        fp.write(''.join(self.header))
+
+    def allhunks(self):
+        return util.any(self.allhunks_re.match(h) for h in self.header)
+
+    def files(self):
+        match = self.diffgit_re.match(self.header[0])
+        if match:
+            fromfile, tofile = match.groups()
+            if fromfile == tofile:
+                return [fromfile]
+            return [fromfile, tofile]
+        else:
+            return self.diff_re.match(self.header[0]).groups()
+
+    def filename(self):
+        return self.files()[-1]
+
+    def __repr__(self):
+        return '<header %s>' % (' '.join(map(repr, self.files())))
+
+    def special(self):
+        return util.any(self.special_re.match(h) for h in self.header)
+
+class recordhunk(object):
+    """patch hunk
+
+    XXX shouldn't we merge this with the other hunk class?
+    """
+    maxcontext = 3
+
+    def __init__(self, header, fromline, toline, proc, before, hunk, after):
+        def trimcontext(number, lines):
+            delta = len(lines) - self.maxcontext
+            if False and delta > 0:
+                return number + delta, lines[:self.maxcontext]
+            return number, lines
+
+        self.header = header
+        self.fromline, self.before = trimcontext(fromline, before)
+        self.toline, self.after = trimcontext(toline, after)
+        self.proc = proc
+        self.hunk = hunk
+        self.added, self.removed = self.countchanges(self.hunk)
+
+    def countchanges(self, hunk):
+        """hunk -> (n+,n-)"""
+        add = len([h for h in hunk if h[0] == '+'])
+        rem = len([h for h in hunk if h[0] == '-'])
+        return add, rem
+
+    def write(self, fp):
+        delta = len(self.before) + len(self.after)
+        if self.after and self.after[-1] == '\\ No newline at end of file\n':
+            delta -= 1
+        fromlen = delta + self.removed
+        tolen = delta + self.added
+        fp.write('@@ -%d,%d +%d,%d @@%s\n' %
+                 (self.fromline, fromlen, self.toline, tolen,
+                  self.proc and (' ' + self.proc)))
+        fp.write(''.join(self.before + self.hunk + self.after))
+
+    pretty = write
+
+    def filename(self):
+        return self.header.filename()
+
+    def __repr__(self):
+        return '<hunk %r@%d>' % (self.filename(), self.fromline)
+
+def filterpatch(ui, headers):
+    """Interactively filter patch chunks into applied-only chunks"""
+
+    def prompt(skipfile, skipall, query, chunk):
+        """prompt query, and process base inputs
+
+        - y/n for the rest of file
+        - y/n for the rest
+        - ? (help)
+        - q (quit)
+
+        Return True/False and possibly updated skipfile and skipall.
+        """
+        newpatches = None
+        if skipall is not None:
+            return skipall, skipfile, skipall, newpatches
+        if skipfile is not None:
+            return skipfile, skipfile, skipall, newpatches
+        while True:
+            resps = _('[Ynesfdaq?]'
+                      '$$ &Yes, record this change'
+                      '$$ &No, skip this change'
+                      '$$ &Edit this change manually'
+                      '$$ &Skip remaining changes to this file'
+                      '$$ Record remaining changes to this &file'
+                      '$$ &Done, skip remaining changes and files'
+                      '$$ Record &all changes to all remaining files'
+                      '$$ &Quit, recording no changes'
+                      '$$ &? (display help)')
+            r = ui.promptchoice("%s %s" % (query, resps))
+            ui.write("\n")
+            if r == 8: # ?
+                for c, t in ui.extractchoices(resps)[1]:
+                    ui.write('%s - %s\n' % (c, t.lower()))
+                continue
+            elif r == 0: # yes
+                ret = True
+            elif r == 1: # no
+                ret = False
+            elif r == 2: # Edit patch
+                if chunk is None:
+                    ui.write(_('cannot edit patch for whole file'))
+                    ui.write("\n")
+                    continue
+                if chunk.header.binary():
+                    ui.write(_('cannot edit patch for binary file'))
+                    ui.write("\n")
+                    continue
+                # Patch comment based on the Git one (based on comment at end of
+                # http://mercurial.selenic.com/wiki/RecordExtension)
+                phelp = '---' + _("""
+To remove '-' lines, make them ' ' lines (context).
+To remove '+' lines, delete them.
+Lines starting with # will be removed from the patch.
+
+If the patch applies cleanly, the edited hunk will immediately be
+added to the record list. If it does not apply cleanly, a rejects
+file will be generated: you can use that when you try again. If
+all lines of the hunk are removed, then the edit is aborted and
+the hunk is left unchanged.
+""")
+                (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
+                        suffix=".diff", text=True)
+                ncpatchfp = None
+                try:
+                    # Write the initial patch
+                    f = os.fdopen(patchfd, "w")
+                    chunk.header.write(f)
+                    chunk.write(f)
+                    f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
+                    f.close()
+                    # Start the editor and wait for it to complete
+                    editor = ui.geteditor()
+                    ui.system("%s \"%s\"" % (editor, patchfn),
+                              environ={'HGUSER': ui.username()},
+                              onerr=util.Abort, errprefix=_("edit failed"))
+                    # Remove comment lines
+                    patchfp = open(patchfn)
+                    ncpatchfp = cStringIO.StringIO()
+                    for line in patchfp:
+                        if not line.startswith('#'):
+                            ncpatchfp.write(line)
+                    patchfp.close()
+                    ncpatchfp.seek(0)
+                    newpatches = parsepatch(ncpatchfp)
+                finally:
+                    os.unlink(patchfn)
+                    del ncpatchfp
+                # Signal that the chunk shouldn't be applied as-is, but
+                # provide the new patch to be used instead.
+                ret = False
+            elif r == 3: # Skip
+                ret = skipfile = False
+            elif r == 4: # file (Record remaining)
+                ret = skipfile = True
+            elif r == 5: # done, skip remaining
+                ret = skipall = False
+            elif r == 6: # all
+                ret = skipall = True
+            elif r == 7: # quit
+                raise util.Abort(_('user quit'))
+            return ret, skipfile, skipall, newpatches
+
+    seen = set()
+    applied = {}        # 'filename' -> [] of chunks
+    skipfile, skipall = None, None
+    pos, total = 1, sum(len(h.hunks) for h in headers)
+    for h in headers:
+        pos += len(h.hunks)
+        skipfile = None
+        fixoffset = 0
+        hdr = ''.join(h.header)
+        if hdr in seen:
+            continue
+        seen.add(hdr)
+        if skipall is None:
+            h.pretty(ui)
+        msg = (_('examine changes to %s?') %
+               _(' and ').join("'%s'" % f for f in h.files()))
+        r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
+        if not r:
+            continue
+        applied[h.filename()] = [h]
+        if h.allhunks():
+            applied[h.filename()] += h.hunks
+            continue
+        for i, chunk in enumerate(h.hunks):
+            if skipfile is None and skipall is None:
+                chunk.pretty(ui)
+            if total == 1:
+                msg = _("record this change to '%s'?") % chunk.filename()
+            else:
+                idx = pos - len(h.hunks) + i
+                msg = _("record change %d/%d to '%s'?") % (idx, total,
+                                                           chunk.filename())
+            r, skipfile, skipall, newpatches = prompt(skipfile,
+                    skipall, msg, chunk)
+            if r:
+                if fixoffset:
+                    chunk = copy.copy(chunk)
+                    chunk.toline += fixoffset
+                applied[chunk.filename()].append(chunk)
+            elif newpatches is not None:
+                for newpatch in newpatches:
+                    for newhunk in newpatch.hunks:
+                        if fixoffset:
+                            newhunk.toline += fixoffset
+                        applied[newhunk.filename()].append(newhunk)
+            else:
+                fixoffset += chunk.removed - chunk.added
+    return sum([h for h in applied.itervalues()
+               if h[0].special() or len(h) > 1], [])
 class hunk(object):
     def __init__(self, desc, num, lr, context):
         self.number = num
@@ -1087,7 +1343,105 @@
             return s
     return s[:i]
 
-def pathstrip(path, strip):
+def parsepatch(fp):
+    """patch -> [] of headers -> [] of hunks """
+    class parser(object):
+        """patch parsing state machine"""
+        def __init__(self):
+            self.fromline = 0
+            self.toline = 0
+            self.proc = ''
+            self.header = None
+            self.context = []
+            self.before = []
+            self.hunk = []
+            self.headers = []
+
+        def addrange(self, limits):
+            fromstart, fromend, tostart, toend, proc = limits
+            self.fromline = int(fromstart)
+            self.toline = int(tostart)
+            self.proc = proc
+
+        def addcontext(self, context):
+            if self.hunk:
+                h = recordhunk(self.header, self.fromline, self.toline,
+                        self.proc, self.before, self.hunk, context)
+                self.header.hunks.append(h)
+                self.fromline += len(self.before) + h.removed
+                self.toline += len(self.before) + h.added
+                self.before = []
+                self.hunk = []
+                self.proc = ''
+            self.context = context
+
+        def addhunk(self, hunk):
+            if self.context:
+                self.before = self.context
+                self.context = []
+            self.hunk = hunk
+
+        def newfile(self, hdr):
+            self.addcontext([])
+            h = header(hdr)
+            self.headers.append(h)
+            self.header = h
+
+        def addother(self, line):
+            pass # 'other' lines are ignored
+
+        def finished(self):
+            self.addcontext([])
+            return self.headers
+
+        transitions = {
+            'file': {'context': addcontext,
+                     'file': newfile,
+                     'hunk': addhunk,
+                     'range': addrange},
+            'context': {'file': newfile,
+                        'hunk': addhunk,
+                        'range': addrange,
+                        'other': addother},
+            'hunk': {'context': addcontext,
+                     'file': newfile,
+                     'range': addrange},
+            'range': {'context': addcontext,
+                      'hunk': addhunk},
+            'other': {'other': addother},
+            }
+
+    p = parser()
+
+    state = 'context'
+    for newstate, data in scanpatch(fp):
+        try:
+            p.transitions[state][newstate](p, data)
+        except KeyError:
+            raise PatchError('unhandled transition: %s -> %s' %
+                                   (state, newstate))
+        state = newstate
+    return p.finished()
+
+def pathtransform(path, strip, prefix):
+    '''turn a path from a patch into a path suitable for the repository
+
+    prefix, if not empty, is expected to be normalized with a / at the end.
+
+    Returns (stripped components, path in repository).
+
+    >>> pathtransform('a/b/c', 0, '')
+    ('', 'a/b/c')
+    >>> pathtransform('   a/b/c   ', 0, '')
+    ('', '   a/b/c')
+    >>> pathtransform('   a/b/c   ', 2, '')
+    ('a/b/', 'c')
+    >>> pathtransform('   a//b/c   ', 2, 'd/e/')
+    ('a//b/', 'd/e/c')
+    >>> pathtransform('a/b/c', 3, '')
+    Traceback (most recent call last):
+    PatchError: unable to strip away 1 of 3 dirs from a/b/c
+    '''
     pathlen = len(path)
     i = 0
     if strip == 0:
@@ -1103,16 +1457,16 @@
         while i < pathlen - 1 and path[i] == '/':
             i += 1
         count -= 1
-    return path[:i].lstrip(), path[i:].rstrip()
+    return path[:i].lstrip(), prefix + path[i:].rstrip()
 
-def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
+def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
     nulla = afile_orig == "/dev/null"
     nullb = bfile_orig == "/dev/null"
     create = nulla and hunk.starta == 0 and hunk.lena == 0
     remove = nullb and hunk.startb == 0 and hunk.lenb == 0
-    abase, afile = pathstrip(afile_orig, strip)
+    abase, afile = pathtransform(afile_orig, strip, prefix)
     gooda = not nulla and backend.exists(afile)
-    bbase, bfile = pathstrip(bfile_orig, strip)
+    bbase, bfile = pathtransform(bfile_orig, strip, prefix)
     if afile == bfile:
         goodb = gooda
     else:
@@ -1154,6 +1508,58 @@
         gp.op = 'DELETE'
     return gp
 
+def scanpatch(fp):
+    """like patch.iterhunks, but yield different events
+
+    - ('file',    [header_lines + fromfile + tofile])
+    - ('context', [context_lines])
+    - ('hunk',    [hunk_lines])
+    - ('range',   (-start,len, +start,len, proc))
+    """
+    lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
+    lr = linereader(fp)
+
+    def scanwhile(first, p):
+        """scan lr while predicate holds"""
+        lines = [first]
+        while True:
+            line = lr.readline()
+            if not line:
+                break
+            if p(line):
+                lines.append(line)
+            else:
+                lr.push(line)
+                break
+        return lines
+
+    while True:
+        line = lr.readline()
+        if not line:
+            break
+        if line.startswith('diff --git a/') or line.startswith('diff -r '):
+            def notheader(line):
+                s = line.split(None, 1)
+                return not s or s[0] not in ('---', 'diff')
+            header = scanwhile(line, notheader)
+            fromfile = lr.readline()
+            if fromfile.startswith('---'):
+                tofile = lr.readline()
+                header += [fromfile, tofile]
+            else:
+                lr.push(fromfile)
+            yield 'file', header
+        elif line[0] == ' ':
+            yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
+        elif line[0] in '-+':
+            yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
+        else:
+            m = lines_re.match(line)
+            if m:
+                yield 'range', m.groups()
+            else:
+                yield 'other', line
+
 def scangitpatch(lr, firstline):
     """
     Git patches can emit:
@@ -1335,7 +1741,7 @@
             raise PatchError(_('unexpected delta opcode 0'))
     return out
 
-def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
+def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
     """Reads a patch from fp and tries to apply it.
 
     Returns 0 for a clean patch, -1 if any rejects were found and 1 if
@@ -1346,13 +1752,16 @@
     patching then normalized according to 'eolmode'.
     """
     return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
-                      eolmode=eolmode)
+                      prefix=prefix, eolmode=eolmode)
 
-def _applydiff(ui, fp, patcher, backend, store, strip=1,
+def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
                eolmode='strict'):
 
+    if prefix:
+        # clean up double slashes, lack of trailing slashes, etc
+        prefix = util.normpath(prefix) + '/'
     def pstrip(p):
-        return pathstrip(p, strip - 1)[1]
+        return pathtransform(p, strip - 1, prefix)[1]
 
     rejects = 0
     err = 0
@@ -1375,7 +1784,8 @@
                 if gp.oldpath:
                     gp.oldpath = pstrip(gp.oldpath)
             else:
-                gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
+                gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
+                                   prefix)
             if gp.op == 'RENAME':
                 backend.unlink(gp.oldpath)
             if not first_hunk:
@@ -1472,7 +1882,8 @@
                          util.explainexit(code)[0])
     return fuzz
 
-def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
+def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
+                 eolmode='strict'):
     if files is None:
         files = set()
     if eolmode is None:
@@ -1487,7 +1898,7 @@
     except TypeError:
         fp = patchobj
     try:
-        ret = applydiff(ui, fp, backend, store, strip=strip,
+        ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
                         eolmode=eolmode)
     finally:
         if fp != patchobj:
@@ -1498,19 +1909,19 @@
         raise PatchError(_('patch failed to apply'))
     return ret > 0
 
-def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
-                  similarity=0):
+def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
+                  eolmode='strict', similarity=0):
     """use builtin patch to apply <patchobj> to the working directory.
     returns whether patch was applied with fuzz factor."""
     backend = workingbackend(ui, repo, similarity)
-    return patchbackend(ui, backend, patchobj, strip, files, eolmode)
+    return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
 
-def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
+def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
               eolmode='strict'):
     backend = repobackend(ui, repo, ctx, store)
-    return patchbackend(ui, backend, patchobj, strip, files, eolmode)
+    return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
 
-def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
+def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
           similarity=0):
     """Apply <patchname> to the working directory.
 
@@ -1529,7 +1940,7 @@
     if patcher:
         return _externalpatch(ui, repo, patcher, patchname, strip,
                               files, similarity)
-    return internalpatch(ui, repo, patchname, strip, files, eolmode,
+    return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
                          similarity)
 
 def changedfiles(ui, repo, patchpath, strip=1):
@@ -1541,11 +1952,12 @@
             if state == 'file':
                 afile, bfile, first_hunk, gp = values
                 if gp:
-                    gp.path = pathstrip(gp.path, strip - 1)[1]
+                    gp.path = pathtransform(gp.path, strip - 1, '')[1]
                     if gp.oldpath:
-                        gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
+                        gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
                 else:
-                    gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
+                    gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
+                                       '')
                 changed.add(gp.path)
                 if gp.op == 'RENAME':
                     changed.add(gp.oldpath)
@@ -1736,20 +2148,47 @@
     '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
     return difflabel(diff, *args, **kw)
 
+def _filepairs(ctx1, modified, added, removed, copy, opts):
+    '''generates tuples (f1, f2, copyop), where f1 is the name of the file
+    before and f2 is the the name after. For added files, f1 will be None,
+    and for removed files, f2 will be None. copyop may be set to None, 'copy'
+    or 'rename' (the latter two only if opts.git is set).'''
+    gone = set()
+
+    copyto = dict([(v, k) for k, v in copy.items()])
+
+    addedset, removedset = set(added), set(removed)
+    # Fix up  added, since merged-in additions appear as
+    # modifications during merges
+    for f in modified:
+        if f not in ctx1:
+            addedset.add(f)
+
+    for f in sorted(modified + added + removed):
+        copyop = None
+        f1, f2 = f, f
+        if f in addedset:
+            f1 = None
+            if f in copy:
+                if opts.git:
+                    f1 = copy[f]
+                    if f1 in removedset and f1 not in gone:
+                        copyop = 'rename'
+                        gone.add(f1)
+                    else:
+                        copyop = 'copy'
+        elif f in removedset:
+            f2 = None
+            if opts.git:
+                # have we already reported a copy above?
+                if (f in copyto and copyto[f] in addedset
+                    and copy[copyto[f]] == f):
+                    continue
+        yield f1, f2, copyop
+
 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
             copy, getfilectx, opts, losedatafn, prefix):
 
-    def join(f):
-        return posixpath.join(prefix, f)
-
-    def addmodehdr(header, omode, nmode):
-        if omode != nmode:
-            header.append('old mode %s\n' % omode)
-            header.append('new mode %s\n' % nmode)
-
-    def addindexmeta(meta, oindex, nindex):
-        meta.append('index %s..%s\n' % (oindex, nindex))
-
     def gitindex(text):
         if not text:
             text = ""
@@ -1764,120 +2203,79 @@
         aprefix = 'a/'
         bprefix = 'b/'
 
-    def diffline(a, b, revs):
-        if opts.git:
-            line = 'diff --git %s%s %s%s\n' % (aprefix, a, bprefix, b)
-        elif not repo.ui.quiet:
-            if revs:
-                revinfo = ' '.join(["-r %s" % rev for rev in revs])
-                line = 'diff %s %s\n' % (revinfo, a)
-            else:
-                line = 'diff %s\n' % a
-        else:
-            line = ''
-        return line
+    def diffline(f, revs):
+        revinfo = ' '.join(["-r %s" % rev for rev in revs])
+        return 'diff %s %s' % (revinfo, f)
 
     date1 = util.datestr(ctx1.date())
     date2 = util.datestr(ctx2.date())
 
-    gone = set()
     gitmode = {'l': '120000', 'x': '100755', '': '100644'}
 
-    copyto = dict([(v, k) for k, v in copy.items()])
-
-    if opts.git:
-        revs = None
-
-    modifiedset, addedset, removedset = set(modified), set(added), set(removed)
-    # Fix up modified and added, since merged-in additions appear as
-    # modifications during merges
-    for f in modifiedset.copy():
-        if f not in ctx1:
-            addedset.add(f)
-            modifiedset.remove(f)
-    for f in sorted(modified + added + removed):
-        to = None
-        tn = None
-        binarydiff = False
-        header = []
-        if f not in addedset:
-            to = getfilectx(f, ctx1).data()
-        if f not in removedset:
-            tn = getfilectx(f, ctx2).data()
-        a, b = f, f
+    for f1, f2, copyop in _filepairs(
+            ctx1, modified, added, removed, copy, opts):
+        content1 = None
+        content2 = None
+        flag1 = None
+        flag2 = None
+        if f1:
+            content1 = getfilectx(f1, ctx1).data()
+            if opts.git or losedatafn:
+                flag1 = ctx1.flags(f1)
+        if f2:
+            content2 = getfilectx(f2, ctx2).data()
+            if opts.git or losedatafn:
+                flag2 = ctx2.flags(f2)
+        binary = False
         if opts.git or losedatafn:
-            if f in addedset:
-                mode = gitmode[ctx2.flags(f)]
-                if f in copy or f in copyto:
-                    if opts.git:
-                        if f in copy:
-                            a = copy[f]
-                        else:
-                            a = copyto[f]
-                        omode = gitmode[ctx1.flags(a)]
-                        addmodehdr(header, omode, mode)
-                        if a in removedset and a not in gone:
-                            op = 'rename'
-                            gone.add(a)
-                        else:
-                            op = 'copy'
-                        header.append('%s from %s\n' % (op, join(a)))
-                        header.append('%s to %s\n' % (op, join(f)))
-                        to = getfilectx(a, ctx1).data()
-                    else:
-                        losedatafn(f)
-                else:
-                    if opts.git:
-                        header.append('new file mode %s\n' % mode)
-                    elif ctx2.flags(f):
-                        losedatafn(f)
-                if util.binary(to) or util.binary(tn):
-                    if opts.git:
-                        binarydiff = True
-                    else:
-                        losedatafn(f)
-                if not opts.git and not tn:
-                    # regular diffs cannot represent new empty file
-                    losedatafn(f)
-            elif f in removedset:
-                if opts.git:
-                    # have we already reported a copy above?
-                    if ((f in copy and copy[f] in addedset
-                         and copyto[copy[f]] == f) or
-                        (f in copyto and copyto[f] in addedset
-                         and copy[copyto[f]] == f)):
-                        continue
-                    else:
-                        header.append('deleted file mode %s\n' %
-                                      gitmode[ctx1.flags(f)])
-                        if util.binary(to):
-                            binarydiff = True
-                elif not to or util.binary(to):
-                    # regular diffs cannot represent empty file deletion
-                    losedatafn(f)
-            else:
-                oflag = ctx1.flags(f)
-                nflag = ctx2.flags(f)
-                binary = util.binary(to) or util.binary(tn)
-                if opts.git:
-                    addmodehdr(header, gitmode[oflag], gitmode[nflag])
-                    if binary:
-                        binarydiff = True
-                elif binary or nflag != oflag:
-                    losedatafn(f)
+            binary = util.binary(content1) or util.binary(content2)
+
+        if losedatafn and not opts.git:
+            if (binary or
+                # copy/rename
+                f2 in copy or
+                # empty file creation
+                (not f1 and not content2) or
+                # empty file deletion
+                (not content1 and not f2) or
+                # create with flags
+                (not f1 and flag2) or
+                # change flags
+                (f1 and f2 and flag1 != flag2)):
+                losedatafn(f2 or f1)
 
-        if opts.git or revs:
-            header.insert(0, diffline(join(a), join(b), revs))
-        if binarydiff and not opts.nobinary:
-            text = mdiff.b85diff(to, tn)
-            if text and opts.git:
-                addindexmeta(header, gitindex(to), gitindex(tn))
+        path1 = posixpath.join(prefix, f1 or f2)
+        path2 = posixpath.join(prefix, f2 or f1)
+        header = []
+        if opts.git:
+            header.append('diff --git %s%s %s%s' %
+                          (aprefix, path1, bprefix, path2))
+            if not f1: # added
+                header.append('new file mode %s' % gitmode[flag2])
+            elif not f2: # removed
+                header.append('deleted file mode %s' % gitmode[flag1])
+            else:  # modified/copied/renamed
+                mode1, mode2 = gitmode[flag1], gitmode[flag2]
+                if mode1 != mode2:
+                    header.append('old mode %s' % mode1)
+                    header.append('new mode %s' % mode2)
+                if copyop is not None:
+                    header.append('%s from %s' % (copyop, path1))
+                    header.append('%s to %s' % (copyop, path2))
+        elif revs and not repo.ui.quiet:
+            header.append(diffline(path1, revs))
+
+        if binary and opts.git and not opts.nobinary:
+            text = mdiff.b85diff(content1, content2)
+            if text:
+                header.append('index %s..%s' %
+                              (gitindex(content1), gitindex(content2)))
         else:
-            text = mdiff.unidiff(to, date1,
-                                 tn, date2,
-                                 join(a), join(b), opts=opts)
+            text = mdiff.unidiff(content1, date1,
+                                 content2, date2,
+                                 path1, path2, opts=opts)
         if header and (text or len(header) > 1):
-            yield ''.join(header)
+            yield '\n'.join(header) + '\n'
         if text:
             yield text
 
--- a/mercurial/pure/parsers.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/pure/parsers.py	Fri Mar 13 17:55:04 2015 -0500
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from mercurial.node import bin, nullid
+from mercurial.node import nullid
 from mercurial import util
 import struct, zlib, cStringIO
 
@@ -21,15 +21,6 @@
     # x is a tuple
     return x
 
-def parse_manifest(mfdict, fdict, lines):
-    for l in lines.splitlines():
-        f, n = l.split('\0')
-        if len(n) > 40:
-            fdict[f] = n[40:]
-            mfdict[f] = bin(n[:40])
-        else:
-            mfdict[f] = bin(n)
-
 def parse_index2(data, inline):
     def gettype(q):
         return int(q & 0xFFFF)
--- a/mercurial/repair.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/repair.py	Fri Mar 13 17:55:04 2015 -0500
@@ -137,6 +137,7 @@
     # create a changegroup for all the branches we need to keep
     backupfile = None
     vfs = repo.vfs
+    node = nodelist[-1]
     if backup:
         backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
         repo.ui.status(_("saved backup bundle to %s\n") %
@@ -181,6 +182,8 @@
                 repo.ui.pushbuffer()
             if isinstance(gen, bundle2.unbundle20):
                 tr = repo.transaction('strip')
+                tr.hookargs = {'source': 'strip',
+                               'url': 'bundle:' + vfs.join(chgrpfile)}
                 try:
                     bundle2.processbundle(repo, gen, lambda: tr)
                     tr.close()
--- a/mercurial/revlog.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/revlog.py	Fri Mar 13 17:55:04 2015 -0500
@@ -277,6 +277,8 @@
 
     def tip(self):
         return self.node(len(self.index) - 2)
+    def __contains__(self, rev):
+        return 0 <= rev < len(self)
     def __len__(self):
         return len(self.index) - 1
     def __iter__(self):
@@ -1231,8 +1233,18 @@
             if dfh:
                 dfh.flush()
             ifh.flush()
-            basetext = self.revision(self.node(cachedelta[0]))
-            btext[0] = mdiff.patch(basetext, cachedelta[1])
+            baserev = cachedelta[0]
+            delta = cachedelta[1]
+            # special case deltas which replace entire base; no need to decode
+            # base revision. this neatly avoids censored bases, which throw when
+            # they're decoded.
+            hlen = struct.calcsize(">lll")
+            if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
+                                                       len(delta) - hlen):
+                btext[0] = delta[hlen:]
+            else:
+                basetext = self.revision(self.node(baserev))
+                btext[0] = mdiff.patch(basetext, delta)
             try:
                 self.checkhash(btext[0], p1, p2, node)
                 if flags & REVIDX_ISCENSORED:
@@ -1249,8 +1261,14 @@
                 delta = cachedelta[1]
             else:
                 t = buildtext()
-                ptext = self.revision(self.node(rev))
-                delta = mdiff.textdiff(ptext, t)
+                if self.iscensored(rev):
+                    # deltas based on a censored revision must replace the
+                    # full content in one patch, so delta works everywhere
+                    header = mdiff.replacediffheader(self.rawsize(rev), len(t))
+                    delta = header + t
+                else:
+                    ptext = self.revision(self.node(rev))
+                    delta = mdiff.textdiff(ptext, t)
             data = self.compress(delta)
             l = len(data[1]) + len(data[0])
             if basecache[0] == rev:
@@ -1368,7 +1386,10 @@
             transaction.add(self.indexfile, isize, r)
             transaction.add(self.datafile, end)
             dfh = self.opener(self.datafile, "a")
-
+        def flush():
+            if dfh:
+                dfh.flush()
+            ifh.flush()
         try:
             # loop through our set of deltas
             chain = None
@@ -1401,9 +1422,24 @@
                                       _('unknown delta base'))
 
                 baserev = self.rev(deltabase)
+
+                if baserev != nullrev and self.iscensored(baserev):
+                    # if base is censored, delta must be full replacement in a
+                    # single patch operation
+                    hlen = struct.calcsize(">lll")
+                    oldlen = self.rawsize(baserev)
+                    newlen = len(delta) - hlen
+                    if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
+                        raise error.CensoredBaseError(self.indexfile,
+                                                      self.node(baserev))
+
+                flags = REVIDX_DEFAULT_FLAGS
+                if self._peek_iscensored(baserev, delta, flush):
+                    flags |= REVIDX_ISCENSORED
+
                 chain = self._addrevision(node, None, transaction, link,
-                                          p1, p2, REVIDX_DEFAULT_FLAGS,
-                                          (baserev, delta), ifh, dfh)
+                                          p1, p2, flags, (baserev, delta),
+                                          ifh, dfh)
                 if not dfh and not self._inline:
                     # addrevision switched from inline to conventional
                     # reopen the index
@@ -1417,6 +1453,14 @@
 
         return content
 
+    def iscensored(self, rev):
+        """Check if a file revision is censored."""
+        return False
+
+    def _peek_iscensored(self, baserev, delta, flush):
+        """Quickly check if a delta produces a censored revision."""
+        return False
+
     def getstrippoint(self, minlink):
         """find the minimum rev that must be stripped to strip the linkrev
 
--- a/mercurial/revset.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/revset.py	Fri Mar 13 17:55:04 2015 -0500
@@ -323,8 +323,6 @@
 
 def stringset(repo, subset, x):
     x = repo[x].rev()
-    if x == -1 and len(subset) == len(repo):
-        return baseset([-1])
     if x in subset:
         return baseset([x])
     return baseset()
@@ -349,7 +347,7 @@
     return r & subset
 
 def dagrange(repo, subset, x, y):
-    r = spanset(repo)
+    r = fullreposet(repo)
     xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
     return xs & subset
 
@@ -370,7 +368,7 @@
 def func(repo, subset, a, b):
     if a[0] == 'symbol' and a[1] in symbols:
         return symbols[a[1]](repo, subset, b)
-    raise error.ParseError(_("not a function: %s") % a[1])
+    raise error.UnknownIdentifier(a[1], symbols.keys())
 
 # functions
 
@@ -396,7 +394,7 @@
     """
     # i18n: "ancestor" is a keyword
     l = getlist(x)
-    rl = spanset(repo)
+    rl = fullreposet(repo)
     anc = None
 
     # (getset(repo, rl, i) for i in l) generates a list of lists
@@ -412,7 +410,7 @@
     return baseset()
 
 def _ancestors(repo, subset, x, followfirst=False):
-    heads = getset(repo, spanset(repo), x)
+    heads = getset(repo, fullreposet(repo), x)
     if not heads:
         return baseset()
     s = _revancestors(repo, heads, followfirst)
@@ -544,7 +542,7 @@
         else:
             return subset.filter(lambda r: matcher(getbi(ucl, r)[0]))
 
-    s = getset(repo, spanset(repo), x)
+    s = getset(repo, fullreposet(repo), x)
     b = set()
     for r in s:
         b.add(getbi(ucl, r)[0])
@@ -708,7 +706,7 @@
     return subset.filter(matches)
 
 def _descendants(repo, subset, x, followfirst=False):
-    roots = getset(repo, spanset(repo), x)
+    roots = getset(repo, fullreposet(repo), x)
     if not roots:
         return baseset()
     s = _revdescendants(repo, roots, followfirst)
@@ -744,9 +742,9 @@
     is the same as passing all().
     """
     if x is not None:
-        sources = getset(repo, spanset(repo), x)
+        sources = getset(repo, fullreposet(repo), x)
     else:
-        sources = getall(repo, spanset(repo), x)
+        sources = fullreposet(repo)
 
     dests = set()
 
@@ -994,7 +992,7 @@
     """
     # i18n: "all" is a keyword
     getargs(x, 0, 0, _("all takes no arguments"))
-    return subset
+    return subset & spanset(repo)  # drop "null" if any
 
 def grep(repo, subset, x):
     """``grep(regex)``
@@ -1145,7 +1143,7 @@
         # i18n: "limit" is a keyword
         raise error.ParseError(_("limit expects a number"))
     ss = subset
-    os = getset(repo, spanset(repo), l[0])
+    os = getset(repo, fullreposet(repo), l[0])
     result = []
     it = iter(os)
     for x in xrange(lim):
@@ -1172,7 +1170,7 @@
         # i18n: "last" is a keyword
         raise error.ParseError(_("last expects a number"))
     ss = subset
-    os = getset(repo, spanset(repo), l[0])
+    os = getset(repo, fullreposet(repo), l[0])
     os.reverse()
     result = []
     it = iter(os)
@@ -1189,7 +1187,7 @@
     """``max(set)``
     Changeset with highest revision number in set.
     """
-    os = getset(repo, spanset(repo), x)
+    os = getset(repo, fullreposet(repo), x)
     if os:
         m = os.max()
         if m in subset:
@@ -1226,7 +1224,7 @@
     """``min(set)``
     Changeset with lowest revision number in set.
     """
-    os = getset(repo, spanset(repo), x)
+    os = getset(repo, fullreposet(repo), x)
     if os:
         m = os.min()
         if m in subset:
@@ -1322,7 +1320,7 @@
     cl = repo.changelog
     # i18n: "only" is a keyword
     args = getargs(x, 1, 2, _('only takes one or two arguments'))
-    include = getset(repo, spanset(repo), args[0])
+    include = getset(repo, fullreposet(repo), args[0])
     if len(args) == 1:
         if not include:
             return baseset()
@@ -1331,7 +1329,7 @@
         exclude = [rev for rev in cl.headrevs()
             if not rev in descendants and not rev in include]
     else:
-        exclude = getset(repo, spanset(repo), args[1])
+        exclude = getset(repo, fullreposet(repo), args[1])
 
     results = set(cl.findmissingrevs(common=exclude, heads=include))
     return subset & results
@@ -1345,9 +1343,9 @@
     for the first operation is selected.
     """
     if x is not None:
-        dests = getset(repo, spanset(repo), x)
+        dests = getset(repo, fullreposet(repo), x)
     else:
-        dests = getall(repo, spanset(repo), x)
+        dests = fullreposet(repo)
 
     def _firstsrc(rev):
         src = _getrevsource(repo, rev)
@@ -1400,7 +1398,7 @@
 
     ps = set()
     cl = repo.changelog
-    for r in getset(repo, spanset(repo), x):
+    for r in getset(repo, fullreposet(repo), x):
         ps.add(cl.parentrevs(r)[0])
     ps -= set([node.nullrev])
     return subset & ps
@@ -1421,7 +1419,7 @@
 
     ps = set()
     cl = repo.changelog
-    for r in getset(repo, spanset(repo), x):
+    for r in getset(repo, fullreposet(repo), x):
         ps.add(cl.parentrevs(r)[1])
     ps -= set([node.nullrev])
     return subset & ps
@@ -1435,7 +1433,7 @@
     else:
         ps = set()
         cl = repo.changelog
-        for r in getset(repo, spanset(repo), x):
+        for r in getset(repo, fullreposet(repo), x):
             ps.update(cl.parentrevs(r))
     ps -= set([node.nullrev])
     return subset & ps
@@ -1548,7 +1546,7 @@
     except (TypeError, ValueError):
         # i18n: "rev" is a keyword
         raise error.ParseError(_("rev expects a number"))
-    if l not in fullreposet(repo) and l != node.nullrev:
+    if l not in repo.changelog and l != node.nullrev:
         return baseset()
     return subset & baseset([l])
 
@@ -1676,7 +1674,7 @@
     """``roots(set)``
     Changesets in set with no parent changeset in set.
     """
-    s = getset(repo, spanset(repo), x)
+    s = getset(repo, fullreposet(repo), x)
     subset = baseset([r for r in s if r in subset])
     cs = _children(repo, subset, s)
     return subset - cs
@@ -2153,7 +2151,7 @@
     if isinstance(tree, tuple):
         arg = _getaliasarg(tree)
         if arg is not None and (not known or arg not in known):
-            raise error.ParseError(_("not a function: %s") % '_aliasarg')
+            raise error.UnknownIdentifier('_aliasarg', [])
         for t in tree:
             _checkaliasarg(t, known)
 
@@ -2243,6 +2241,71 @@
     except error.ParseError, inst:
         return (decl, None, None, parseerrordetail(inst))
 
+def _parsealiasdefn(defn, args):
+    """Parse alias definition ``defn``
+
+    This function also replaces alias argument references in the
+    specified definition by ``_aliasarg(ARGNAME)``.
+
+    ``args`` is a list of alias argument names, or None if the alias
+    is declared as a symbol.
+
+    This returns "tree" as parsing result.
+
+    >>> args = ['$1', '$2', 'foo']
+    >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
+    (or
+      (func
+        ('symbol', '_aliasarg')
+        ('string', '$1'))
+      (func
+        ('symbol', '_aliasarg')
+        ('string', 'foo')))
+    >>> try:
+    ...     _parsealiasdefn('$1 or $bar', args)
+    ... except error.ParseError, inst:
+    ...     print parseerrordetail(inst)
+    at 6: '$' not for alias arguments
+    >>> args = ['$1', '$10', 'foo']
+    >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
+    (or
+      (func
+        ('symbol', '_aliasarg')
+        ('string', '$10'))
+      ('symbol', 'foobar'))
+    >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
+    (or
+      ('string', '$1')
+      ('string', 'foo'))
+    """
+    def tokenizedefn(program, lookup=None):
+        if args:
+            argset = set(args)
+        else:
+            argset = set()
+
+        for t, value, pos in _tokenizealias(program, lookup=lookup):
+            if t == 'symbol':
+                if value in argset:
+                    # emulate tokenization of "_aliasarg('ARGNAME')":
+                    # "_aliasarg()" is an unknown symbol only used separate
+                    # alias argument placeholders from regular strings.
+                    yield ('symbol', '_aliasarg', pos)
+                    yield ('(', None, pos)
+                    yield ('string', value, pos)
+                    yield (')', None, pos)
+                    continue
+                elif value.startswith('$'):
+                    raise error.ParseError(_("'$' not for alias arguments"),
+                                           pos)
+            yield (t, value, pos)
+
+    p = parser.parser(tokenizedefn, elements)
+    tree, pos = p.parse(defn)
+    if pos != len(defn):
+        raise error.ParseError(_('invalid token'), pos)
+    return tree
+
 class revsetalias(object):
     # whether own `error` information is already shown or not.
     # this avoids showing same warning multiple times at each `findaliases`.
@@ -2260,16 +2323,8 @@
                            ' "%s": %s') % (self.name, self.error)
             return
 
-        if self.args:
-            for arg in self.args:
-                # _aliasarg() is an unknown symbol only used separate
-                # alias argument placeholders from regular strings.
-                value = value.replace(arg, '_aliasarg(%r)' % (arg,))
-
         try:
-            self.replacement, pos = parse(value)
-            if pos != len(value):
-                raise error.ParseError(_('invalid token'), pos)
+            self.replacement = _parsealiasdefn(value, self.args)
             # Check for placeholder injection
             _checkaliasarg(self.replacement, self.args)
         except error.ParseError, inst:
@@ -2392,7 +2447,9 @@
         tree = findaliases(ui, tree, showwarning=ui.warn)
     tree = foldconcat(tree)
     weight, tree = optimize(tree, True)
-    def mfunc(repo, subset):
+    def mfunc(repo, subset=None):
+        if subset is None:
+            subset = fullreposet(repo)
         if util.safehasattr(subset, 'isascending'):
             result = getset(repo, subset, tree)
         else:
@@ -3146,18 +3203,7 @@
             return it().next()
         return None
 
-def spanset(repo, start=None, end=None):
-    """factory function to dispatch between fullreposet and actual spanset
-
-    Feel free to update all spanset call sites and kill this function at some
-    point.
-    """
-    if start is None and end is None:
-        return fullreposet(repo)
-    return _spanset(repo, start, end)
-
-
-class _spanset(abstractsmartset):
+class spanset(abstractsmartset):
     """Duck type for baseset class which represents a range of revisions and
     can work lazily and without having all the range in memory
 
@@ -3261,15 +3307,21 @@
             return x
         return None
 
-class fullreposet(_spanset):
+class fullreposet(spanset):
     """a set containing all revisions in the repo
 
-    This class exists to host special optimization.
+    This class exists to host special optimization and magic to handle virtual
+    revisions such as "null".
     """
 
     def __init__(self, repo):
         super(fullreposet, self).__init__(repo)
 
+    def __contains__(self, rev):
+        # assumes the given rev is valid
+        hidden = self._hiddenrevs
+        return not (hidden and rev in hidden)
+
     def __and__(self, other):
         """As self contains the whole repo, all of the other set should also be
         in self. Therefore `self & other = other`.
--- a/mercurial/scmutil.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/scmutil.py	Fri Mar 13 17:55:04 2015 -0500
@@ -628,12 +628,22 @@
         return repo[val].rev()
 
     seen, l = set(), revset.baseset([])
+
+    revsetaliases = [alias for (alias, _) in
+                     repo.ui.configitems("revsetalias")]
+
     for spec in revs:
         if l and not seen:
             seen = set(l)
         # attempt to parse old-style ranges first to deal with
         # things like old-tag which contain query metacharacters
         try:
+            # ... except for revset aliases without arguments. These
+            # should be parsed as soon as possible, because they might
+            # clash with a hash prefix.
+            if spec in revsetaliases:
+                raise error.RepoLookupError
+
             if isinstance(spec, int):
                 seen.add(spec)
                 l = l + revset.baseset([spec])
@@ -641,6 +651,9 @@
 
             if _revrangesep in spec:
                 start, end = spec.split(_revrangesep, 1)
+                if start in revsetaliases or end in revsetaliases:
+                    raise error.RepoLookupError
+
                 start = revfix(repo, start, 0)
                 end = revfix(repo, end, len(repo) - 1)
                 if end == nullrev and start < 0:
@@ -672,11 +685,11 @@
         # fall through to new-style queries if old-style fails
         m = revset.match(repo.ui, spec, repo)
         if seen or l:
-            dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
+            dl = [r for r in m(repo) if r not in seen]
             l = l + revset.baseset(dl)
             seen.update(dl)
         else:
-            l = m(repo, revset.spanset(repo))
+            l = m(repo)
 
     return l
 
--- a/mercurial/sslutil.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/sslutil.py	Fri Mar 13 17:55:04 2015 -0500
@@ -10,12 +10,16 @@
 
 from mercurial import util
 from mercurial.i18n import _
+
+_canloaddefaultcerts = False
 try:
     # avoid using deprecated/broken FakeSocket in python 2.6
     import ssl
     CERT_REQUIRED = ssl.CERT_REQUIRED
     try:
         ssl_context = ssl.SSLContext
+        _canloaddefaultcerts = util.safehasattr(ssl_context,
+                                                'load_default_certs')
 
         def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
                             ca_certs=None, serverhostname=None):
@@ -35,6 +39,8 @@
             sslcontext.verify_mode = cert_reqs
             if ca_certs is not None:
                 sslcontext.load_verify_locations(cafile=ca_certs)
+            elif _canloaddefaultcerts:
+                sslcontext.load_default_certs()
 
             sslsocket = sslcontext.wrap_socket(sock,
                                                server_hostname=serverhostname)
@@ -129,23 +135,34 @@
     return (exe.startswith('/usr/bin/python') or
             exe.startswith('/system/library/frameworks/python.framework/'))
 
+def _defaultcacerts():
+    """return path to CA certificates; None for system's store; ! to disable"""
+    if _plainapplepython():
+        dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
+        if os.path.exists(dummycert):
+            return dummycert
+    if _canloaddefaultcerts:
+        return None
+    return '!'
+
 def sslkwargs(ui, host):
     kws = {}
     hostfingerprint = ui.config('hostfingerprints', host)
     if hostfingerprint:
         return kws
     cacerts = ui.config('web', 'cacerts')
-    if cacerts:
+    if cacerts == '!':
+        pass
+    elif cacerts:
         cacerts = util.expandpath(cacerts)
         if not os.path.exists(cacerts):
             raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
-    elif cacerts is None and _plainapplepython():
-        dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
-        if os.path.exists(dummycert):
-            ui.debug('using %s to enable OS X system CA\n' % dummycert)
-            ui.setconfig('web', 'cacerts', dummycert, 'dummy')
-            cacerts = dummycert
-    if cacerts:
+    else:
+        cacerts = _defaultcacerts()
+        if cacerts and cacerts != '!':
+            ui.debug('using %s to enable OS X system CA\n' % cacerts)
+        ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
+    if cacerts != '!':
         kws.update({'ca_certs': cacerts,
                     'cert_reqs': CERT_REQUIRED,
                     })
@@ -194,7 +211,7 @@
                                  hint=_('check hostfingerprint configuration'))
             self.ui.debug('%s certificate matched fingerprint %s\n' %
                           (host, nicefingerprint))
-        elif cacerts:
+        elif cacerts != '!':
             msg = _verifycert(peercert2, host)
             if msg:
                 raise util.Abort(_('%s certificate error: %s') % (host, msg),
--- a/mercurial/subrepo.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/subrepo.py	Fri Mar 13 17:55:04 2015 -0500
@@ -127,7 +127,7 @@
             src = src.lstrip() # strip any extra whitespace after ']'
 
         if not util.url(src).isabs():
-            parent = _abssource(ctx._repo, abort=False)
+            parent = _abssource(ctx.repo(), abort=False)
             if parent:
                 parent = util.url(parent)
                 parent.path = posixpath.join(parent.path or '', src)
@@ -332,7 +332,7 @@
     import hg as h
     hg = h
 
-    pathutil.pathauditor(ctx._repo.root)(path)
+    pathutil.pathauditor(ctx.repo().root)(path)
     state = ctx.substate[path]
     if state[2] not in types:
         raise util.Abort(_('unknown subrepo type %s') % state[2])
@@ -516,10 +516,10 @@
 
 class hgsubrepo(abstractsubrepo):
     def __init__(self, ctx, path, state):
-        super(hgsubrepo, self).__init__(ctx._repo.ui)
+        super(hgsubrepo, self).__init__(ctx.repo().ui)
         self._path = path
         self._state = state
-        r = ctx._repo
+        r = ctx.repo()
         root = r.wjoin(path)
         create = not r.wvfs.exists('%s/.hg' % path)
         self._repo = hg.repository(r.baseui, root, create=create)
@@ -626,6 +626,7 @@
                            os.path.join(prefix, self._path), explicitonly,
                            **opts)
 
+    @annotatesubrepoerror
     def addremove(self, m, prefix, opts, dry_run, similarity):
         # In the same way as sub directories are processed, once in a subrepo,
         # always entry any of its subrepos.  Don't corrupt the options that will
@@ -835,7 +836,7 @@
     def files(self):
         rev = self._state[1]
         ctx = self._repo[rev]
-        return ctx.manifest()
+        return ctx.manifest().keys()
 
     def filedata(self, name):
         rev = self._state[1]
@@ -877,13 +878,11 @@
             opts['date'] = None
             opts['rev'] = substate[1]
 
-            pats = []
-            if not opts.get('all'):
-                pats = ['set:modified()']
             self.filerevert(*pats, **opts)
 
         # Update the repo to the revision specified in the given substate
-        self.get(substate, overwrite=True)
+        if not opts.get('dry_run'):
+            self.get(substate, overwrite=True)
 
     def filerevert(self, *pats, **opts):
         ctx = self._repo[opts['rev']]
@@ -899,7 +898,7 @@
 
 class svnsubrepo(abstractsubrepo):
     def __init__(self, ctx, path, state):
-        super(svnsubrepo, self).__init__(ctx._repo.ui)
+        super(svnsubrepo, self).__init__(ctx.repo().ui)
         self._path = path
         self._state = state
         self._ctx = ctx
@@ -923,7 +922,7 @@
                 cmd.append('--non-interactive')
         cmd.extend(commands)
         if filename is not None:
-            path = os.path.join(self._ctx._repo.origroot, self._path, filename)
+            path = os.path.join(self._ctx.repo().origroot, self._path, filename)
             cmd.append(path)
         env = dict(os.environ)
         # Avoid localized output, preserve current locale for everything else.
@@ -1065,7 +1064,7 @@
             os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
             os.remove(path)
 
-        path = self._ctx._repo.wjoin(self._path)
+        path = self._ctx.repo().wjoin(self._path)
         shutil.rmtree(path, onerror=onerror)
         try:
             os.removedirs(os.path.dirname(path))
@@ -1083,7 +1082,7 @@
         # update to a directory which has since been deleted and recreated.
         args.append('%s@%s' % (state[0], state[1]))
         status, err = self._svncommand(args, failok=True)
-        _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
+        _sanitize(self.ui, self._ctx.repo().wjoin(self._path), '.svn')
         if not re.search('Checked out revision [0-9]+.', status):
             if ('is already a working copy for a different URL' in err
                 and (self._wcchanged()[:2] == (False, False))):
@@ -1129,13 +1128,13 @@
 
 class gitsubrepo(abstractsubrepo):
     def __init__(self, ctx, path, state):
-        super(gitsubrepo, self).__init__(ctx._repo.ui)
+        super(gitsubrepo, self).__init__(ctx.repo().ui)
         self._state = state
         self._ctx = ctx
         self._path = path
-        self._relpath = os.path.join(reporelpath(ctx._repo), path)
-        self._abspath = ctx._repo.wjoin(path)
-        self._subparent = ctx._repo
+        self._relpath = os.path.join(reporelpath(ctx.repo()), path)
+        self._abspath = ctx.repo().wjoin(path)
+        self._subparent = ctx.repo()
         self._ensuregit()
 
     def _ensuregit(self):
@@ -1524,6 +1523,47 @@
             return False
 
     @annotatesubrepoerror
+    def add(self, ui, match, prefix, explicitonly, **opts):
+        if self._gitmissing():
+            return []
+
+        (modified, added, removed,
+         deleted, unknown, ignored, clean) = self.status(None, unknown=True,
+                                                         clean=True)
+
+        tracked = set()
+        # dirstates 'amn' warn, 'r' is added again
+        for l in (modified, added, deleted, clean):
+            tracked.update(l)
+
+        # Unknown files not of interest will be rejected by the matcher
+        files = unknown
+        files.extend(match.files())
+
+        rejected = []
+
+        files = [f for f in sorted(set(files)) if match(f)]
+        for f in files:
+            exact = match.exact(f)
+            command = ["add"]
+            if exact:
+                command.append("-f") #should be added, even if ignored
+            if ui.verbose or not exact:
+                ui.status(_('adding %s\n') % match.rel(f))
+
+            if f in tracked:  # hg prints 'adding' even if already tracked
+                if exact:
+                    rejected.append(f)
+                continue
+            if not opts.get('dry_run'):
+                self._gitcommand(command + [f])
+
+        for f in rejected:
+            ui.warn(_("%s already tracked!\n") % match.abs(f))
+
+        return rejected
+
+    @annotatesubrepoerror
     def remove(self):
         if self._gitmissing():
             return
@@ -1577,11 +1617,30 @@
 
 
     @annotatesubrepoerror
+    def cat(self, match, prefix, **opts):
+        rev = self._state[1]
+        if match.anypats():
+            return 1 #No support for include/exclude yet
+
+        if not match.files():
+            return 1
+
+        for f in match.files():
+            output = self._gitcommand(["show", "%s:%s" % (rev, f)])
+            fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
+                                     self._ctx.node(),
+                                     pathname=os.path.join(prefix, f))
+            fp.write(output)
+            fp.close()
+        return 0
+
+
+    @annotatesubrepoerror
     def status(self, rev2, **opts):
         rev1 = self._state[1]
         if self._gitmissing() or not rev1:
             # if the repo is missing, return no results
-            return [], [], [], [], [], [], []
+            return scmutil.status([], [], [], [], [], [], [])
         modified, added, removed = [], [], []
         self._gitupdatestat()
         if rev2:
@@ -1603,13 +1662,42 @@
 
         deleted, unknown, ignored, clean = [], [], [], []
 
-        if not rev2:
-            command = ['ls-files', '--others', '--exclude-standard']
-            out = self._gitcommand(command)
-            for line in out.split('\n'):
-                if len(line) == 0:
-                    continue
-                unknown.append(line)
+        command = ['status', '--porcelain', '-z']
+        if opts.get('unknown'):
+            command += ['--untracked-files=all']
+        if opts.get('ignored'):
+            command += ['--ignored']
+        out = self._gitcommand(command)
+
+        changedfiles = set()
+        changedfiles.update(modified)
+        changedfiles.update(added)
+        changedfiles.update(removed)
+        for line in out.split('\0'):
+            if not line:
+                continue
+            st = line[0:2]
+            #moves and copies show 2 files on one line
+            if line.find('\0') >= 0:
+                filename1, filename2 = line[3:].split('\0')
+            else:
+                filename1 = line[3:]
+                filename2 = None
+
+            changedfiles.add(filename1)
+            if filename2:
+                changedfiles.add(filename2)
+
+            if st == '??':
+                unknown.append(filename1)
+            elif st == '!!':
+                ignored.append(filename1)
+
+        if opts.get('clean'):
+            out = self._gitcommand(['ls-files'])
+            for f in out.split('\n'):
+                if not f in changedfiles:
+                    clean.append(f)
 
         return scmutil.status(modified, added, removed, deleted,
                               unknown, ignored, clean)
@@ -1673,7 +1761,8 @@
                 util.rename(os.path.join(self._abspath, name),
                             os.path.join(self._abspath, bakname))
 
-        self.get(substate, overwrite=True)
+        if not opts.get('dry_run'):
+            self.get(substate, overwrite=True)
         return []
 
     def shortid(self, revid):
--- a/mercurial/tags.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/tags.py	Fri Mar 13 17:55:04 2015 -0500
@@ -337,7 +337,7 @@
     # them local encoding on input, we would lose info writing them to
     # the cache.
     cachefile.write('\n')
-    for (name, (node, hist)) in cachetags.iteritems():
+    for (name, (node, hist)) in sorted(cachetags.iteritems()):
         for n in hist:
             cachefile.write("%s %s\n" % (hex(n), name))
         cachefile.write("%s %s\n" % (hex(node), name))
--- a/mercurial/templatekw.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templatekw.py	Fri Mar 13 17:55:04 2015 -0500
@@ -12,11 +12,15 @@
 # This helper class allows us to handle both:
 #  "{files}" (legacy command-line-specific list hack) and
 #  "{files % '{file}\n'}" (hgweb-style with inlining and function support)
+# and to access raw values:
+#  "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
+#  "{get(extras, key)}"
 
 class _hybrid(object):
-    def __init__(self, gen, values, joinfmt=None):
+    def __init__(self, gen, values, makemap, joinfmt=None):
         self.gen = gen
         self.values = values
+        self._makemap = makemap
         if joinfmt:
             self.joinfmt = joinfmt
         else:
@@ -24,16 +28,23 @@
     def __iter__(self):
         return self.gen
     def __call__(self):
+        makemap = self._makemap
         for x in self.values:
-            yield x
+            yield makemap(x)
+    def __contains__(self, x):
+        return x in self.values
     def __len__(self):
         return len(self.values)
+    def __getattr__(self, name):
+        if name != 'get':
+            raise AttributeError(name)
+        return getattr(self.values, name)
 
 def showlist(name, values, plural=None, element=None, **args):
     if not element:
         element = name
     f = _showlist(name, values, plural, **args)
-    return _hybrid(f, [{element: x} for x in values])
+    return _hybrid(f, values, lambda x: {element: x})
 
 def _showlist(name, values, plural=None, **args):
     '''expand set of values.
@@ -200,9 +211,9 @@
     repo = args['ctx']._repo
     bookmarks = args['ctx'].bookmarks()
     current = repo._bookmarkcurrent
-    c = [{'bookmark': x, 'current': current} for x in bookmarks]
+    makemap = lambda v: {'bookmark': v, 'current': current}
     f = _showlist('bookmark', bookmarks, **args)
-    return _hybrid(f, c, lambda x: x['bookmark'])
+    return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
 
 def showchildren(**args):
     """:children: List of strings. The children of the changeset."""
@@ -241,9 +252,12 @@
     """:extras: List of dicts with key, value entries of the 'extras'
     field of this changeset."""
     extras = args['ctx'].extra()
-    c = [{'key': x[0], 'value': x[1]} for x in sorted(extras.items())]
+    extras = util.sortdict((k, extras[k]) for k in sorted(extras))
+    makemap = lambda k: {'key': k, 'value': extras[k]}
+    c = [makemap(k) for k in extras]
     f = _showlist('extra', c, plural='extras', **args)
-    return _hybrid(f, c, lambda x: '%s=%s' % (x['key'], x['value']))
+    return _hybrid(f, extras, makemap,
+                   lambda x: '%s=%s' % (x['key'], x['value']))
 
 def showfileadds(**args):
     """:file_adds: List of strings. Files added by this changeset."""
@@ -267,9 +281,12 @@
             if rename:
                 copies.append((fn, rename[0]))
 
-    c = [{'name': x[0], 'source': x[1]} for x in copies]
+    copies = util.sortdict(copies)
+    makemap = lambda k: {'name': k, 'source': copies[k]}
+    c = [makemap(k) for k in copies]
     f = _showlist('file_copy', c, plural='file_copies', **args)
-    return _hybrid(f, c, lambda x: '%s (%s)' % (x['name'], x['source']))
+    return _hybrid(f, copies, makemap,
+                   lambda x: '%s (%s)' % (x['name'], x['source']))
 
 # showfilecopiesswitch() displays file copies only if copy records are
 # provided before calling the templater, usually with a --copies
@@ -279,9 +296,12 @@
     only if the --copied switch is set.
     """
     copies = args['revcache'].get('copies') or []
-    c = [{'name': x[0], 'source': x[1]} for x in copies]
+    copies = util.sortdict(copies)
+    makemap = lambda k: {'name': k, 'source': copies[k]}
+    c = [makemap(k) for k in copies]
     f = _showlist('file_copy', c, plural='file_copies', **args)
-    return _hybrid(f, c, lambda x: '%s (%s)' % (x['name'], x['source']))
+    return _hybrid(f, copies, makemap,
+                   lambda x: '%s (%s)' % (x['name'], x['source']))
 
 def showfiledels(**args):
     """:file_dels: List of strings. Files removed by this changeset."""
--- a/mercurial/templater.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templater.py	Fri Mar 13 17:55:04 2015 -0500
@@ -162,8 +162,13 @@
 
 def runfilter(context, mapping, data):
     func, data, filt = data
+    # func() may return string, generator of strings or arbitrary object such
+    # as date tuple, but filter does not want generator.
+    thing = func(context, mapping, data)
+    if isinstance(thing, types.GeneratorType):
+        thing = stringify(thing)
     try:
-        return filt(func(context, mapping, data))
+        return filt(thing)
     except (ValueError, AttributeError, TypeError):
         if isinstance(data, tuple):
             dt = data[1]
@@ -330,10 +335,7 @@
     item = stringify(args[0][0](context, mapping, args[0][1]))
     items = args[1][0](context, mapping, args[1][1])
 
-    # Iterating over items gives a formatted string, so we iterate
-    # directly over the raw values.
-    if ((callable(items) and item in [i.values()[0] for i in items()]) or
-        (isinstance(items, str) and item in items)):
+    if item in items:
         yield _evalifliteral(args[2], context, mapping)
     elif len(args) == 4:
         yield _evalifliteral(args[3], context, mapping)
@@ -393,7 +395,7 @@
 
     def query(expr):
         m = revsetmod.match(repo.ui, expr)
-        return m(repo, revsetmod.spanset(repo))
+        return m(repo)
 
     if len(args) > 1:
         formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
--- a/mercurial/templates/gitweb/map	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/gitweb/map	Fri Mar 13 17:55:04 2015 -0500
@@ -140,7 +140,7 @@
   <tr>
     <td>parent {rev}</td>
     <td style="font-family:monospace">
-      {changesetlink} {ifeq(node, basenode, '(current diff)', \'({difffrom})\')}
+      {changesetlink} {ifeq(node, basenode, '(current diff)', '({difffrom})')}
     </td>
   </tr>'
 difffrom = '<a href="{url|urlescape}rev/{node|short}:{originalnode|short}{sessionvars%urlparameter}">diff</a>'
--- a/mercurial/templates/monoblue/bookmarks.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/bookmarks.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
             <li class="current">bookmarks</li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
             <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
-	    <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+            <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
--- a/mercurial/templates/monoblue/branches.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/branches.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li class="current">branches</li>
             <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
-	    <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+            <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
--- a/mercurial/templates/monoblue/changelog.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/changelog.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -27,7 +27,7 @@
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
             <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
             {archives%archiveentry}
-	    <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+            <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
--- a/mercurial/templates/monoblue/graph.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/graph.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -27,7 +27,7 @@
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
             <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
-	    <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+            <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
--- a/mercurial/templates/monoblue/help.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/help.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
             <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
-	    <li class="current">help</li>
+            <li class="current">help</li>
         </ul>
     </div>
 
--- a/mercurial/templates/monoblue/helptopics.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/helptopics.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">branches</a></li>
             <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
-	    <li class="current">help</li>
+            <li class="current">help</li>
         </ul>
     </div>
 
--- a/mercurial/templates/monoblue/manifest.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/manifest.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
             <li class="current">files</li>
-	    <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+            <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
--- a/mercurial/templates/monoblue/map	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/map	Fri Mar 13 17:55:04 2015 -0500
@@ -93,7 +93,7 @@
   <tr class="parity{parity}">
     <td class="linenr">
       <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l{targetline}"
-	 title="{node|short}: {desc|escape|firstline}">{author|user}@{rev}</a>
+         title="{node|short}: {desc|escape|firstline}">{author|user}@{rev}</a>
     </td>
     <td class="lineno">
       <a href="#{lineid}" id="{lineid}">{linenumber}</a>
@@ -129,7 +129,7 @@
   <dd>{changesetlink}</dd>'
 changesetparentdiff = '
   <dt>parent {rev}</dt>
-  <dd>{changesetlink} {ifeq(node, basenode, '(current diff)', \'({difffrom})\')}</dd>'
+  <dd>{changesetlink} {ifeq(node, basenode, '(current diff)', '({difffrom})')}</dd>'
 difffrom = '<a href="{url|urlescape}rev/{node|short}:{originalnode|short}{sessionvars%urlparameter}">diff</a>'
 filerevbranch = '<dt>branch</dt><dd>{name|escape}</dd>'
 filerevparent = '
--- a/mercurial/templates/monoblue/shortlog.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/shortlog.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -26,8 +26,8 @@
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
             <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
-	    {archives%archiveentry}
-	    <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+            {archives%archiveentry}
+            <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
--- a/mercurial/templates/monoblue/summary.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/summary.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
             <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
-	    <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+            <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
--- a/mercurial/templates/monoblue/tags.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/monoblue/tags.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -26,7 +26,7 @@
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
             <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
-	    <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
+            <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
--- a/mercurial/templates/paper/bookmarks.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/bookmarks.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -23,7 +23,6 @@
 <ul>
 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
 </ul>
-<p></p>
 <div class="atom-logo">
 <a href="{url|urlescape}atom-bookmarks" title="subscribe to atom feed">
 <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
@@ -42,10 +41,12 @@
 </form>
 
 <table class="bigtable">
+<thead>
 <tr>
  <th>bookmark</th>
  <th>node</th>
 </tr>
+</thead>
 <tbody class="stripes2">
 {entries%bookmarkentry}
 </tbody>
--- a/mercurial/templates/paper/branches.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/branches.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -23,7 +23,6 @@
 <ul>
  <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
 </ul>
-<p></p>
 <div class="atom-logo">
 <a href="{url|urlescape}atom-branches" title="subscribe to atom feed">
 <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
@@ -42,10 +41,12 @@
 </form>
 
 <table class="bigtable">
+<thead>
 <tr>
  <th>branch</th>
  <th>node</th>
 </tr>
+</thead>
 <tbody class="stripes2">
 {entries % branchentry}
 </tbody>
--- a/mercurial/templates/paper/changeset.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/changeset.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -48,7 +48,8 @@
 </tr>
 <tr>
  <th class="date">date</th>
- <td class="date age">{date|rfc822date}</td></tr>
+ <td class="date age">{date|rfc822date}</td>
+</tr>
 <tr>
  <th class="author">parents</th>
  <td class="author">{ifeq(count(parent), '2', parent%changesetparentdiff, parent%changesetparent)}</td>
@@ -68,8 +69,7 @@
     <a id="diffstatexpand" href="javascript:toggleDiffstat()">[<tt>+</tt>]</a>
     <div id="diffstatdetails" style="display:none;">
       <a href="javascript:toggleDiffstat()">[<tt>-</tt>]</a>
-      <p></p>
-      <table class="stripes2">{diffstat}</table>
+      <table class="diffstat-table stripes2">{diffstat}</table>
     </div>
   </td>
 </tr>
--- a/mercurial/templates/paper/fileannotate.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/fileannotate.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -68,10 +68,12 @@
 
 <div class="overflow">
 <table class="bigtable">
+<thead>
 <tr>
  <th class="annotate">rev</th>
  <th class="line">&nbsp;&nbsp;line source</th>
 </tr>
+</thead>
 <tbody class="stripes2">
   {annotate%annotateline}
 </tbody>
--- a/mercurial/templates/paper/filelog.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/filelog.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -35,7 +35,6 @@
 <ul>
 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
 </ul>
-<p></p>
 <div class="atom-logo">
 <a href="{url|urlescape}atom-log/{node|short}/{file|urlescape}" title="subscribe to atom feed">
 <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
@@ -59,11 +58,13 @@
 | {nav%filenav}</div>
 
 <table class="bigtable">
+<thead>
  <tr>
   <th class="age">age</th>
   <th class="author">author</th>
   <th class="description">description</th>
  </tr>
+</thead>
 <tbody class="stripes2">
 {entries%filelogentry}
 </tbody>
--- a/mercurial/templates/paper/graph.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/graph.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -28,7 +28,6 @@
 <ul>
  <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
 </ul>
-<p></p>
 <div class="atom-logo">
 <a href="{url|urlescape}atom-log" title="subscribe to atom feed">
 <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
--- a/mercurial/templates/paper/index.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/index.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -12,6 +12,7 @@
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
 
 <table class="bigtable">
+    <thead>
     <tr>
         <th><a href="?sort={sort_name}">Name</a></th>
         <th><a href="?sort={sort_description}">Description</a></th>
@@ -20,6 +21,7 @@
         <th>&nbsp;</th>
         <th>&nbsp;</th>
     </tr>
+    </thead>
     <tbody class="stripes2">
     {entries%indexentry}
     </tbody>
--- a/mercurial/templates/paper/manifest.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/manifest.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -39,11 +39,13 @@
 </form>
 
 <table class="bigtable">
+<thead>
 <tr>
   <th class="name">name</th>
   <th class="size">size</th>
   <th class="permissions">permissions</th>
 </tr>
+</thead>
 <tbody class="stripes2">
 <tr class="fileline">
   <td class="name"><a href="{url|urlescape}file/{node|short}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
--- a/mercurial/templates/paper/search.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/search.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -43,11 +43,13 @@
 </div>
 
 <table class="bigtable">
+<thead>
  <tr>
   <th class="age">age</th>
   <th class="author">author</th>
   <th class="description">description</th>
  </tr>
+</thead>
 <tbody class="stripes2">
 {entries}
 </tbody>
--- a/mercurial/templates/paper/shortlog.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/shortlog.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -30,7 +30,6 @@
 <ul>
  <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
 </ul>
-<p></p>
 <div class="atom-logo">
 <a href="{url|urlescape}atom-log" title="subscribe to atom feed">
 <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
@@ -55,11 +54,13 @@
 </div>
 
 <table class="bigtable">
+<thead>
  <tr>
   <th class="age">age</th>
   <th class="author">author</th>
   <th class="description">description</th>
  </tr>
+</thead>
 <tbody class="stripes2">
 {entries%shortlogentry}
 </tbody>
--- a/mercurial/templates/paper/tags.tmpl	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/paper/tags.tmpl	Fri Mar 13 17:55:04 2015 -0500
@@ -23,7 +23,6 @@
 <ul>
 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
 </ul>
-<p></p>
 <div class="atom-logo">
 <a href="{url|urlescape}atom-tags" title="subscribe to atom feed">
 <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
@@ -42,10 +41,12 @@
 </form>
 
 <table class="bigtable">
+<thead>
 <tr>
  <th>tag</th>
  <th>node</th>
 </tr>
+</thead>
 <tbody class="stripes2">
 {entries%tagentry}
 </tbody>
--- a/mercurial/templates/static/style-paper.css	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/templates/static/style-paper.css	Fri Mar 13 17:55:04 2015 -0500
@@ -60,6 +60,10 @@
   border: 0;
 }
 
+div.atom-logo {
+  margin-top: 10px;
+}
+
 .atom-logo img{
   width: 14px;
   height: 14px;
@@ -104,6 +108,9 @@
 .minusline { color: #dc143c; } /* crimson */
 .atline { color: purple; }
 
+.diffstat-table {
+  margin-top: 1em;
+}
 .diffstat-file {
   white-space: nowrap;
   font-size: 90%;
--- a/mercurial/transaction.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/transaction.py	Fri Mar 13 17:55:04 2015 -0500
@@ -83,7 +83,7 @@
 
 class transaction(object):
     def __init__(self, report, opener, vfsmap, journalname, undoname=None,
-                 after=None, createmode=None):
+                 after=None, createmode=None, validator=None):
         """Begin a new transaction
 
         Begins a new transaction that allows rolling back writes in the event of
@@ -107,6 +107,12 @@
         self.journal = journalname
         self.undoname = undoname
         self._queue = []
+        # A callback to validate transaction content before closing it.
+        # should raise exception is anything is wrong.
+        # target user is repository hooks.
+        if validator is None:
+            validator = lambda tr: None
+        self.validator = validator
         # a dict of arguments to be passed to hooks
         self.hookargs = {}
         self.file = opener.open(self.journal, "w")
@@ -378,6 +384,7 @@
     def close(self):
         '''commit the transaction'''
         if self.count == 1:
+            self.validator(self)  # will raise exception if needed
             self._generatefiles()
             categories = sorted(self._finalizecallback)
             for cat in categories:
--- a/mercurial/ui.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/ui.py	Fri Mar 13 17:55:04 2015 -0500
@@ -531,10 +531,14 @@
         if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
             return loc
 
-        path = self.config('paths', loc)
-        if not path and default is not None:
-            path = self.config('paths', default)
-        return path or loc
+        p = self.paths.getpath(loc, default=default)
+        if p:
+            return p.loc
+        return loc
+
+    @util.propertycache
+    def paths(self):
+        return paths(self)
 
     def pushbuffer(self, error=False):
         """install a buffer to capture standard output of the ui object
@@ -923,3 +927,48 @@
         ui.write(ui.label(s, 'label')).
         '''
         return msg
+
+class paths(dict):
+    """Represents a collection of paths and their configs.
+
+    Data is initially derived from ui instances and the config files they have
+    loaded.
+    """
+    def __init__(self, ui):
+        dict.__init__(self)
+
+        for name, loc in ui.configitems('paths'):
+            # No location is the same as not existing.
+            if not loc:
+                continue
+            self[name] = path(name, rawloc=loc)
+
+    def getpath(self, name, default=None):
+        """Return a ``path`` for the specified name, falling back to a default.
+
+        Returns the first of ``name`` or ``default`` that is present, or None
+        if neither is present.
+        """
+        try:
+            return self[name]
+        except KeyError:
+            if default is not None:
+                try:
+                    return self[default]
+                except KeyError:
+                    pass
+
+        return None
+
+class path(object):
+    """Represents an individual path and its configuration."""
+
+    def __init__(self, name, rawloc=None):
+        """Construct a path from its config options.
+
+        ``name`` is the symbolic name of the path.
+        ``rawloc`` is the raw location, as defined in the config.
+        """
+        self.name = name
+        # We'll do more intelligent things with rawloc in the future.
+        self.loc = rawloc
--- a/mercurial/unionrepo.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/unionrepo.py	Fri Mar 13 17:55:04 2015 -0500
@@ -160,8 +160,11 @@
     def baserevdiff(self, rev1, rev2):
         return filelog.filelog.revdiff(self, rev1, rev2)
 
-    def _file(self, f):
-        self._repo.file(f)
+    def iscensored(self, rev):
+        """Check if a revision is censored."""
+        if rev <= self.repotiprev:
+            return filelog.filelog.iscensored(self, rev)
+        return self.revlog2.iscensored(rev)
 
 class unionpeer(localrepo.localpeer):
     def canpush(self):
--- a/mercurial/util.h	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/util.h	Fri Mar 13 17:55:04 2015 -0500
@@ -172,6 +172,22 @@
 		(d[3]));
 }
 
+static inline int16_t getbeint16(const char *c)
+{
+	const unsigned char *d = (const unsigned char *)c;
+
+	return ((d[0] << 8) |
+		(d[1]));
+}
+
+static inline uint16_t getbeuint16(const char *c)
+{
+	const unsigned char *d = (const unsigned char *)c;
+
+	return ((d[0] << 8) |
+		(d[1]));
+}
+
 static inline void putbe32(uint32_t x, char *c)
 {
 	c[0] = (x >> 24) & 0xff;
@@ -180,4 +196,17 @@
 	c[3] = (x) & 0xff;
 }
 
+static inline double getbefloat64(const char *c)
+{
+	const unsigned char *d = (const unsigned char *)c;
+	double ret;
+	int i;
+	uint64_t t = 0;
+	for (i = 0; i < 8; i++) {
+		t = (t<<8) + d[i];
+	}
+	memcpy(&ret, &t, sizeof(t));
+	return ret;
+}
+
 #endif /* _HG_UTIL_H_ */
--- a/mercurial/util.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/util.py	Fri Mar 13 17:55:04 2015 -0500
@@ -359,8 +359,10 @@
     def __iter__(self):
         return self._list.__iter__()
     def update(self, src):
-        for k in src:
-            self[k] = src[k]
+        if isinstance(src, dict):
+            src = src.iteritems()
+        for k, v in src:
+            self[k] = v
     def clear(self):
         dict.clear(self)
         self._list = []
@@ -1352,11 +1354,11 @@
         formats = defaultdateformats
     date = date.strip()
 
-    if date == _('now'):
+    if date == 'now' or date == _('now'):
         return makedate()
-    if date == _('today'):
+    if date == 'today' or date == _('today'):
         date = datetime.date.today().strftime('%b %d')
-    elif date == _('yesterday'):
+    elif date == 'yesterday' or date == _('yesterday'):
         date = (datetime.date.today() -
                 datetime.timedelta(days=1)).strftime('%b %d')
 
--- a/mercurial/windows.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/mercurial/windows.py	Fri Mar 13 17:55:04 2015 -0500
@@ -26,14 +26,22 @@
 unlink = win32.unlink
 
 umask = 0022
+_SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
 
-# wrap osutil.posixfile to provide friendlier exceptions
 def posixfile(name, mode='r', buffering=-1):
+    '''Open a file with even more POSIX-like semantics'''
     try:
-        return osutil.posixfile(name, mode, buffering)
+        fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
+
+        # The position when opening in append mode is implementation defined, so
+        # make it consistent with other platforms, which position at EOF.
+        if 'a' in mode:
+            fp.seek(0, _SEEK_END)
+
+        return fp
     except WindowsError, err:
+        # convert to a friendlier exception
         raise IOError(err.errno, '%s: %s' % (name, err.strerror))
-posixfile.__doc__ = osutil.posixfile.__doc__
 
 class winstdout(object):
     '''stdout on windows misbehaves if sent through a pipe'''
--- a/setup.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/setup.py	Fri Mar 13 17:55:04 2015 -0500
@@ -63,6 +63,8 @@
         raise SystemExit(
             "Couldn't import standard bz2 (incomplete Python install).")
 
+ispypy = "PyPy" in sys.version
+
 import os, stat, subprocess, time
 import re
 import shutil
@@ -276,7 +278,7 @@
 
 
 class hgdist(Distribution):
-    pure = 0
+    pure = ispypy
 
     global_options = Distribution.global_options + \
                      [('pure', None, "use pure (slow) Python "
@@ -491,6 +493,7 @@
     Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
               depends=common_depends),
     Extension('mercurial.parsers', ['mercurial/dirs.c',
+                                    'mercurial/manifest.c',
                                     'mercurial/parsers.c',
                                     'mercurial/pathencode.c'],
               depends=common_depends),
@@ -555,7 +558,7 @@
 if py2exeloaded:
     extra['console'] = [
         {'script':'hg',
-         'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
+         'copyright':'Copyright (C) 2005-2015 Matt Mackall and others',
          'product_version':version}]
     # sub command of 'build' because 'py2exe' does not handle sub_commands
     build.sub_commands.insert(0, ('build_hgextindex', None))
--- a/tests/hghave.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/hghave.py	Fri Mar 13 17:55:04 2015 -0500
@@ -320,6 +320,11 @@
     except ImportError:
         return False
 
+@check("defaultcacerts", "can verify SSL certs by system's CA certs store")
+def has_defaultcacerts():
+    from mercurial import sslutil
+    return sslutil._defaultcacerts() != '!'
+
 @check("windows", "Windows")
 def has_windows():
     return os.name == 'nt'
--- a/tests/run-tests.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/run-tests.py	Fri Mar 13 17:55:04 2015 -0500
@@ -1863,6 +1863,17 @@
                   'prefix': self._installdir, 'libdir': self._pythondir,
                   'bindir': self._bindir,
                   'nohome': nohome, 'logfile': installerrs})
+
+        # setuptools requires install directories to exist.
+        def makedirs(p):
+            try:
+                os.makedirs(p)
+            except OSError, e:
+                if e.errno != errno.EEXIST:
+                    raise
+        makedirs(self._pythondir)
+        makedirs(self._bindir)
+
         vlog("# Running", cmd)
         if os.system(cmd) == 0:
             if not self.options.verbose:
@@ -1870,7 +1881,7 @@
         else:
             f = open(installerrs, 'rb')
             for line in f:
-                print line
+                sys.stdout.write(line)
             f.close()
             sys.exit(1)
         os.chdir(self._testdir)
--- a/tests/test-addremove.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-addremove.t	Fri Mar 13 17:55:04 2015 -0500
@@ -30,12 +30,12 @@
   adding foo
   $ hg forget foo
 #if windows
-  $ hg -v addremove nonexistant
-  nonexistant: The system cannot find the file specified
+  $ hg -v addremove nonexistent
+  nonexistent: The system cannot find the file specified
   [1]
 #else
-  $ hg -v addremove nonexistant
-  nonexistant: No such file or directory
+  $ hg -v addremove nonexistent
+  nonexistent: No such file or directory
   [1]
 #endif
   $ cd ..
@@ -88,13 +88,13 @@
 
   $ rm c
 #if windows
-  $ hg ci -A -m "c" nonexistant
-  nonexistant: The system cannot find the file specified
+  $ hg ci -A -m "c" nonexistent
+  nonexistent: The system cannot find the file specified
   abort: failed to mark all new/missing files as added/removed
   [255]
 #else
-  $ hg ci -A -m "c" nonexistant
-  nonexistant: No such file or directory
+  $ hg ci -A -m "c" nonexistent
+  nonexistent: No such file or directory
   abort: failed to mark all new/missing files as added/removed
   [255]
 #endif
--- a/tests/test-alias.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-alias.t	Fri Mar 13 17:55:04 2015 -0500
@@ -360,9 +360,11 @@
   sub
   $ hg --cwd .. subalias > /dev/null
   hg: unknown command 'subalias'
+  (did you mean one of idalias?)
   [255]
   $ hg -R .. subalias > /dev/null
   hg: unknown command 'subalias'
+  (did you mean one of idalias?)
   [255]
 
 
@@ -370,12 +372,18 @@
 
   $ hg mainalias > /dev/null
   hg: unknown command 'mainalias'
+  (did you mean one of idalias?)
   [255]
   $ hg -R .. mainalias
   main
   $ hg --cwd .. mainalias
   main
 
+typos get useful suggestions
+  $ hg --cwd .. manalias
+  hg: unknown command 'manalias'
+  (did you mean one of idalias, mainalias, manifest?)
+  [255]
 
 shell aliases with escaped $ chars
 
--- a/tests/test-bundle-type.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-bundle-type.t	Fri Mar 13 17:55:04 2015 -0500
@@ -87,6 +87,7 @@
   $ hg init tgarbage
   $ cd tgarbage
   $ hg pull ../bgarbage
+  pulling from ../bgarbage
   abort: ../bgarbage: not a Mercurial bundle
   [255]
   $ cd ..
--- a/tests/test-bundle2-exchange.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-bundle2-exchange.t	Fri Mar 13 17:55:04 2015 -0500
@@ -25,10 +25,9 @@
   > [phases]
   > publish=False
   > [hooks]
-  > changegroup = sh -c  "HG_LOCAL= python \"$TESTDIR/printenv.py\" changegroup"
-  > b2x-pretransactionclose.tip = hg log -r tip -T "pre-close-tip:{node|short} {phase} {bookmarks}\n"
-  > b2x-transactionclose.tip = hg log -r tip -T "postclose-tip:{node|short} {phase} {bookmarks}\n"
-  > b2x-transactionclose.env = sh -c  "HG_LOCAL= python \"$TESTDIR/printenv.py\" b2x-transactionclose"
+  > pretxnclose.tip = hg log -r tip -T "pre-close-tip:{node|short} {phase} {bookmarks}\n"
+  > txnclose.tip = hg log -r tip -T "postclose-tip:{node|short} {phase} {bookmarks}\n"
+  > txnclose.env = sh -c  "HG_LOCAL= python \"$TESTDIR/printenv.py\" txnclose"
   > pushkey= sh "$TESTTMP/bundle2-pushkey-hook.sh"
   > EOF
 
@@ -39,13 +38,19 @@
   $ touch a
   $ hg add a
   $ hg commit -m 'a'
+  pre-close-tip:3903775176ed draft 
+  postclose-tip:3903775176ed draft 
+  txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=commit
 
   $ hg unbundle $TESTDIR/bundles/rebase.hg
   adding changesets
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+3 heads)
-  changegroup hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_SOURCE=unbundle HG_URL=bundle:*/rebase.hg (glob)
+  pre-close-tip:02de42196ebe draft 
+  postclose-tip:02de42196ebe draft 
+  txnclose hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNNAME=unbundle
+  bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ cd ..
@@ -56,11 +61,20 @@
 Add more obsolescence information
 
   $ hg -R main debugobsolete -d '0 0' 1111111111111111111111111111111111111111 `getmainid 9520eea781bc`
+  pre-close-tip:02de42196ebe draft 
+  postclose-tip:02de42196ebe draft 
+  txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
   $ hg -R main debugobsolete -d '0 0' 2222222222222222222222222222222222222222 `getmainid 24b6387c8c8c`
+  pre-close-tip:02de42196ebe draft 
+  postclose-tip:02de42196ebe draft 
+  txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
 
 clone --pull
 
   $ hg -R main phase --public cd010b8cd998
+  pre-close-tip:000000000000 public 
+  postclose-tip:02de42196ebe draft 
+  txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=phase
   $ hg clone main other --pull --rev 9520eea781bc
   adding changesets
   adding manifests
@@ -69,8 +83,8 @@
   1 new obsolescence markers
   pre-close-tip:9520eea781bc draft 
   postclose-tip:9520eea781bc draft 
-  b2x-transactionclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
-  changegroup hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+  txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNNAME=pull
+  file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R other log -G
@@ -84,6 +98,9 @@
 pull
 
   $ hg -R main phase --public 9520eea781bc
+  pre-close-tip:000000000000 public 
+  postclose-tip:02de42196ebe draft 
+  txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=phase
   $ hg -R other pull -r 24b6387c8c8c
   pulling from $TESTTMP/main (glob)
   searching for changes
@@ -94,8 +111,8 @@
   1 new obsolescence markers
   pre-close-tip:24b6387c8c8c draft 
   postclose-tip:24b6387c8c8c draft 
-  b2x-transactionclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
-  changegroup hook: HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+  txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNNAME=pull
+  file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R other log -G
   o  2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com>  F
@@ -111,12 +128,16 @@
 pull empty (with phase movement)
 
   $ hg -R main phase --public 24b6387c8c8c
+  pre-close-tip:000000000000 public 
+  postclose-tip:02de42196ebe draft 
+  txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=phase
   $ hg -R other pull -r 24b6387c8c8c
   pulling from $TESTTMP/main (glob)
   no changes found
   pre-close-tip:000000000000 public 
   postclose-tip:24b6387c8c8c public 
-  b2x-transactionclose hook: HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+  txnclose hook: HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNNAME=pull
+  file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
   $ hg -R other log -G
   o  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
   |
@@ -135,7 +156,8 @@
   no changes found
   pre-close-tip:24b6387c8c8c public 
   postclose-tip:24b6387c8c8c public 
-  b2x-transactionclose hook: HG_NEW_OBSMARKERS=0 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+  txnclose hook: HG_NEW_OBSMARKERS=0 HG_SOURCE=pull HG_TXNNAME=pull
+  file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
   $ hg -R other log -G
   o  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
   |
@@ -151,14 +173,29 @@
 
   $ hg -R main bookmark --rev eea13746799a book_eea1
   $ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a`
+  pre-close-tip:02de42196ebe draft 
+  postclose-tip:02de42196ebe draft 
+  txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
   $ hg -R main bookmark --rev 02de42196ebe book_02de
   $ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe`
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
   $ hg -R main bookmark --rev 42ccdea3bb16 book_42cc
   $ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16`
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
   $ hg -R main bookmark --rev 5fddd98957c8 book_5fdd
   $ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8`
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
   $ hg -R main bookmark --rev 32af7686d403 book_32af
   $ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403`
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNNAME=debugobsolete
 
   $ hg -R other bookmark --rev cd010b8cd998 book_eea1
   $ hg -R other bookmark --rev cd010b8cd998 book_02de
@@ -167,6 +204,9 @@
   $ hg -R other bookmark --rev cd010b8cd998 book_32af
 
   $ hg -R main phase --public eea13746799a
+  pre-close-tip:000000000000 public 
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=phase
 
 push
   $ hg -R main push other --rev eea13746799a --bookmark book_eea1
@@ -180,8 +220,7 @@
   lock:  free
   wlock: free
   postclose-tip:eea13746799a public book_eea1
-  b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_URL=push
-  changegroup hook: HG_BUNDLE2-EXP=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_SOURCE=push HG_URL=push
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_TXNNAME=unbundle HG_URL=push
   remote: adding changesets
   remote: adding manifests
   remote: adding file changes
@@ -190,7 +229,8 @@
   updating bookmark book_eea1
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
-  b2x-transactionclose hook: HG_SOURCE=push-response HG_URL=file:$TESTTMP/other
+  txnclose hook: HG_SOURCE=push-response HG_TXNNAME=push-response
+  file:/*/$TESTTMP/other HG_URL=file:$TESTTMP/other (glob)
   $ hg -R other log -G
   o    3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
   |\
@@ -218,8 +258,8 @@
   updating bookmark book_02de
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
-  b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=ssh://user@dummy/main
-  changegroup hook: HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_SOURCE=pull HG_URL=ssh://user@dummy/main
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNNAME=pull
+  ssh://user@dummy/main HG_URL=ssh://user@dummy/main
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R other debugobsolete
   1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
@@ -243,8 +283,8 @@
   updating bookmark book_42cc
   pre-close-tip:42ccdea3bb16 draft book_42cc
   postclose-tip:42ccdea3bb16 draft book_42cc
-  b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=http://localhost:$HGPORT/
-  changegroup hook: HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_SOURCE=pull HG_URL=http://localhost:$HGPORT/
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNNAME=pull
+  http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ cat main-error.log
   $ hg -R other debugobsolete
@@ -270,11 +310,11 @@
   remote: lock:  free
   remote: wlock: free
   remote: postclose-tip:5fddd98957c8 draft book_5fdd
-  remote: b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1
-  remote: changegroup hook: HG_BUNDLE2-EXP=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1
+  remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_TXNNAME=unbundle HG_URL=remote:ssh:127.0.0.1
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
-  b2x-transactionclose hook: HG_SOURCE=push-response HG_URL=ssh://user@dummy/other
+  txnclose hook: HG_SOURCE=push-response HG_TXNNAME=push-response
+  ssh://user@dummy/other HG_URL=ssh://user@dummy/other
   $ hg -R other log -G
   o  6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
   |
@@ -304,6 +344,9 @@
   $ cat other.pid >> $DAEMON_PIDS
 
   $ hg -R main phase --public 32af7686d403
+  pre-close-tip:000000000000 public 
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=phase
   $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403 --bookmark book_32af
   pushing to http://localhost:$HGPORT2/
   searching for changes
@@ -315,7 +358,8 @@
   updating bookmark book_32af
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
-  b2x-transactionclose hook: HG_SOURCE=push-response HG_URL=http://localhost:$HGPORT2/
+  txnclose hook: HG_SOURCE=push-response HG_TXNNAME=push-response
+  http://localhost:$HGPORT2/ HG_URL=http://localhost:$HGPORT2/
   $ cat other-error.log
 
 Check final content.
@@ -400,6 +444,9 @@
   $ echo 'I' > I
   $ hg add I
   $ hg ci -m 'I'
+  pre-close-tip:e7ec4e813ba6 draft 
+  postclose-tip:e7ec4e813ba6 draft 
+  txnclose hook: HG_TXNNAME=commit
   $ hg id
   e7ec4e813ba6 tip
   $ cd ..
@@ -501,7 +548,7 @@
   > [failpush]
   > reason =
   > [hooks]
-  > b2x-pretransactionclose.failpush = false
+  > pretxnclose.failpush = false
   > EOF
 
   $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
@@ -514,13 +561,13 @@
   pre-close-tip:e7ec4e813ba6 draft 
   transaction abort!
   rollback completed
-  abort: b2x-pretransactionclose.failpush hook exited with status 1
+  abort: pretxnclose.failpush hook exited with status 1
   [255]
 
   $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
   pushing to ssh://user@dummy/other
   searching for changes
-  abort: b2x-pretransactionclose.failpush hook exited with status 1
+  abort: pretxnclose.failpush hook exited with status 1
   remote: pre-close-tip:e7ec4e813ba6 draft 
   remote: transaction abort!
   remote: rollback completed
@@ -529,7 +576,7 @@
   $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
   pushing to http://localhost:$HGPORT2/
   searching for changes
-  abort: b2x-pretransactionclose.failpush hook exited with status 1
+  abort: pretxnclose.failpush hook exited with status 1
   [255]
 
 (check that no 'pending' files remain)
--- a/tests/test-churn.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-churn.t	Fri Mar 13 17:55:04 2015 -0500
@@ -171,4 +171,27 @@
   El Ni\xc3\xb1o         1 *************** (esc)
   with space      1 ***************
 
+Test --template argument, with backwards compatiblity
+
+  $ hg churn -t '{author|user}'
+  user1      4 ***************************************************************
+  user3      3 ***********************************************
+  user2      2 ********************************
+  nino       1 ****************
+  with       1 ****************
+             0 
+  user4      0 
+  $ hg churn -T '{author|user}'
+  user1      4 ***************************************************************
+  user3      3 ***********************************************
+  user2      2 ********************************
+  nino       1 ****************
+  with       1 ****************
+             0 
+  user4      0 
+  $ hg churn -t 'alltogether'
+  alltogether     11 *********************************************************
+  $ hg churn -T 'alltogether'
+  alltogether     11 *********************************************************
+
   $ cd ..
--- a/tests/test-command-template.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-command-template.t	Fri Mar 13 17:55:04 2015 -0500
@@ -47,6 +47,9 @@
   fourth (second)
   $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
   second -> fourth
+  $ hg log -T '{rev} {ifcontains("fourth", file_copies, "t", "f")}\n' -r .:7
+  8 t
+  7 f
 
 Quoting for ui.logtemplate
 
@@ -1898,6 +1901,11 @@
   hg: parse error: unknown function 'foo'
   [255]
 
+Pass generator object created by template function to filter
+
+  $ hg log -l 1 --template '{if(author, author)|user}\n'
+  test
+
 Test diff function:
 
   $ hg diff -c 8
@@ -2283,6 +2291,14 @@
   $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
   no
 
+Test get function:
+
+  $ hg log -r 0 --template '{get(extras, "branch")}\n'
+  default
+  $ hg log -r 0 --template '{get(files, "should_fail")}\n'
+  hg: parse error: get() expects a dict as first argument
+  [255]
+
 Test shortest(node) function:
 
   $ echo b > b
@@ -2386,6 +2402,10 @@
   2 bar foo
   1 baz
   0 
+  $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
+  2 t
+  1 f
+  0 f
 
 Test stringify on sub expressions
 
--- a/tests/test-commit-amend.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-commit-amend.t	Fri Mar 13 17:55:04 2015 -0500
@@ -1096,7 +1096,7 @@
   $ hg ci -m add
   $ 
   $ hg debugrename newdirname/newfile.py
-  newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def
+  newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
   $ hg status -C --change .
   A newdirname/newfile.py
   $ hg status -C --rev 1
@@ -1115,7 +1115,7 @@
   $ echo a >> newdirname/commonfile.py
   $ hg ci --amend -m bug
   $ hg debugrename newdirname/newfile.py
-  newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def
+  newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
   $ hg debugindex newdirname/newfile.py
      rev    offset  length   base linkrev nodeid       p1           p2
        0         0      88      0       3 34a4d536c0c0 000000000000 000000000000
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-commit-interactive.t	Fri Mar 13 17:55:04 2015 -0500
@@ -0,0 +1,1434 @@
+Set up a repo
+
+  $ cat <<EOF >> $HGRCPATH
+  > [ui]
+  > interactive = true
+  > [extensions]
+  > record =
+  > EOF
+
+  $ hg init a
+  $ cd a
+
+Select no files
+
+  $ touch empty-rw
+  $ hg add empty-rw
+
+  $ hg commit -i empty-rw<<EOF
+  > n
+  > EOF
+  diff --git a/empty-rw b/empty-rw
+  new file mode 100644
+  examine changes to 'empty-rw'? [Ynesfdaq?] n
+  
+  no changes to record
+
+  $ hg tip -p
+  changeset:   -1:000000000000
+  tag:         tip
+  user:        
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  
+  
+
+Select files but no hunks
+
+  $ hg commit -i  empty-rw<<EOF
+  > y
+  > n
+  > EOF
+  diff --git a/empty-rw b/empty-rw
+  new file mode 100644
+  examine changes to 'empty-rw'? [Ynesfdaq?] y
+  
+  abort: empty commit message
+  [255]
+
+  $ hg tip -p
+  changeset:   -1:000000000000
+  tag:         tip
+  user:        
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  
+  
+
+Record empty file
+
+  $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
+  > y
+  > y
+  > EOF
+  diff --git a/empty-rw b/empty-rw
+  new file mode 100644
+  examine changes to 'empty-rw'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   0:c0708cf4e46e
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     empty
+  
+  
+
+Summary shows we updated to the new cset
+
+  $ hg summary
+  parent: 0:c0708cf4e46e tip
+   empty
+  branch: default
+  commit: (clean)
+  update: (current)
+
+Rename empty file
+
+  $ hg mv empty-rw empty-rename
+  $ hg commit -i -d '1 0' -m rename<<EOF
+  > y
+  > EOF
+  diff --git a/empty-rw b/empty-rename
+  rename from empty-rw
+  rename to empty-rename
+  examine changes to 'empty-rw' and 'empty-rename'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   1:d695e8dcb197
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:01 1970 +0000
+  summary:     rename
+  
+  
+
+Copy empty file
+
+  $ hg cp empty-rename empty-copy
+  $ hg commit -i -d '2 0' -m copy<<EOF
+  > y
+  > EOF
+  diff --git a/empty-rename b/empty-copy
+  copy from empty-rename
+  copy to empty-copy
+  examine changes to 'empty-rename' and 'empty-copy'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   2:1d4b90bea524
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:02 1970 +0000
+  summary:     copy
+  
+  
+
+Delete empty file
+
+  $ hg rm empty-copy
+  $ hg commit -i -d '3 0' -m delete<<EOF
+  > y
+  > EOF
+  diff --git a/empty-copy b/empty-copy
+  deleted file mode 100644
+  examine changes to 'empty-copy'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   3:b39a238f01a1
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:03 1970 +0000
+  summary:     delete
+  
+  
+
+Add binary file
+
+  $ hg bundle --base -2 tip.bundle
+  1 changesets found
+  $ hg add tip.bundle
+  $ hg commit -i -d '4 0' -m binary<<EOF
+  > y
+  > EOF
+  diff --git a/tip.bundle b/tip.bundle
+  new file mode 100644
+  this is a binary file
+  examine changes to 'tip.bundle'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   4:ad816da3711e
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:04 1970 +0000
+  summary:     binary
+  
+  diff -r b39a238f01a1 -r ad816da3711e tip.bundle
+  Binary file tip.bundle has changed
+  
+
+Change binary file
+
+  $ hg bundle --base -2 tip.bundle
+  1 changesets found
+  $ hg commit -i -d '5 0' -m binary-change<<EOF
+  > y
+  > EOF
+  diff --git a/tip.bundle b/tip.bundle
+  this modifies a binary file (all or nothing)
+  examine changes to 'tip.bundle'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   5:dccd6f3eb485
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:05 1970 +0000
+  summary:     binary-change
+  
+  diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
+  Binary file tip.bundle has changed
+  
+
+Rename and change binary file
+
+  $ hg mv tip.bundle top.bundle
+  $ hg bundle --base -2 top.bundle
+  1 changesets found
+  $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
+  > y
+  > EOF
+  diff --git a/tip.bundle b/top.bundle
+  rename from tip.bundle
+  rename to top.bundle
+  this modifies a binary file (all or nothing)
+  examine changes to 'tip.bundle' and 'top.bundle'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   6:7fa44105f5b3
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:06 1970 +0000
+  summary:     binary-change-rename
+  
+  diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
+  Binary file tip.bundle has changed
+  diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
+  Binary file top.bundle has changed
+  
+
+Add plain file
+
+  $ for i in 1 2 3 4 5 6 7 8 9 10; do
+  >     echo $i >> plain
+  > done
+
+  $ hg add plain
+  $ hg commit -i -d '7 0' -m plain plain<<EOF
+  > y
+  > y
+  > EOF
+  diff --git a/plain b/plain
+  new file mode 100644
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,10 @@
+  +1
+  +2
+  +3
+  +4
+  +5
+  +6
+  +7
+  +8
+  +9
+  +10
+  record this change to 'plain'? [Ynesfdaq?] y
+  
+  $ hg tip -p
+  changeset:   7:11fb457c1be4
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:07 1970 +0000
+  summary:     plain
+  
+  diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/plain	Thu Jan 01 00:00:07 1970 +0000
+  @@ -0,0 +1,10 @@
+  +1
+  +2
+  +3
+  +4
+  +5
+  +6
+  +7
+  +8
+  +9
+  +10
+  
+Modify end of plain file with username unset
+
+  $ echo 11 >> plain
+  $ unset HGUSER
+  $ hg commit -i --config ui.username= -d '8 0' -m end plain
+  abort: no username supplied
+  (use "hg config --edit" to set your username)
+  [255]
+
+
+Modify end of plain file, also test that diffopts are accounted for
+
+  $ HGUSER="test"
+  $ export HGUSER
+  $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
+  > y
+  > y
+  > EOF
+  diff --git a/plain b/plain
+  1 hunks, 1 lines changed
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
+  @@ -8,3 +8,4 @@ 7
+   8
+   9
+   10
+  +11
+  record this change to 'plain'? [Ynesfdaq?] y
+  
+
+Modify end of plain file, no EOL
+
+  $ hg tip --template '{node}' >> plain
+  $ hg commit -i -d '9 0' -m noeol plain <<EOF
+  > y
+  > y
+  > EOF
+  diff --git a/plain b/plain
+  1 hunks, 1 lines changed
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
+  @@ -9,3 +9,4 @@
+   9
+   10
+   11
+  +7264f99c5f5ff3261504828afa4fb4d406c3af54
+  \ No newline at end of file
+  record this change to 'plain'? [Ynesfdaq?] y
+  
+
+Modify end of plain file, add EOL
+
+  $ echo >> plain
+  $ echo 1 > plain2
+  $ hg add plain2
+  $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
+  > y
+  > y
+  > y
+  > y
+  > EOF
+  diff --git a/plain b/plain
+  1 hunks, 1 lines changed
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
+  @@ -9,4 +9,4 @@
+   9
+   10
+   11
+  -7264f99c5f5ff3261504828afa4fb4d406c3af54
+  \ No newline at end of file
+  +7264f99c5f5ff3261504828afa4fb4d406c3af54
+  record change 1/2 to 'plain'? [Ynesfdaq?] y
+  
+  diff --git a/plain2 b/plain2
+  new file mode 100644
+  examine changes to 'plain2'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +1
+  record change 2/2 to 'plain2'? [Ynesfdaq?] y
+  
+Modify beginning, trim end, record both, add another file to test
+changes numbering
+
+  $ rm plain
+  $ for i in 2 2 3 4 5 6 7 8 9 10; do
+  >   echo $i >> plain
+  > done
+  $ echo 2 >> plain2
+
+  $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
+  > y
+  > y
+  > y
+  > y
+  > y
+  > EOF
+  diff --git a/plain b/plain
+  2 hunks, 3 lines changed
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
+  @@ -1,4 +1,4 @@
+  -1
+  +2
+   2
+   3
+   4
+  record change 1/3 to 'plain'? [Ynesfdaq?] y
+  
+  @@ -8,5 +8,3 @@
+   8
+   9
+   10
+  -11
+  -7264f99c5f5ff3261504828afa4fb4d406c3af54
+  record change 2/3 to 'plain'? [Ynesfdaq?] y
+  
+  diff --git a/plain2 b/plain2
+  1 hunks, 1 lines changed
+  examine changes to 'plain2'? [Ynesfdaq?] y
+  
+  @@ -1,1 +1,2 @@
+   1
+  +2
+  record change 3/3 to 'plain2'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   11:21df83db12b8
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     begin-and-end
+  
+  diff -r ddb8b281c3ff -r 21df83db12b8 plain
+  --- a/plain	Thu Jan 01 00:00:10 1970 +0000
+  +++ b/plain	Thu Jan 01 00:00:10 1970 +0000
+  @@ -1,4 +1,4 @@
+  -1
+  +2
+   2
+   3
+   4
+  @@ -8,5 +8,3 @@
+   8
+   9
+   10
+  -11
+  -7264f99c5f5ff3261504828afa4fb4d406c3af54
+  diff -r ddb8b281c3ff -r 21df83db12b8 plain2
+  --- a/plain2	Thu Jan 01 00:00:10 1970 +0000
+  +++ b/plain2	Thu Jan 01 00:00:10 1970 +0000
+  @@ -1,1 +1,2 @@
+   1
+  +2
+  
+
+Trim beginning, modify end
+
+  $ rm plain
+  > for i in 4 5 6 7 8 9 10.new; do
+  >   echo $i >> plain
+  > done
+
+Record end
+
+  $ hg commit -i -d '11 0' -m end-only plain <<EOF
+  > y
+  > n
+  > y
+  > EOF
+  diff --git a/plain b/plain
+  2 hunks, 4 lines changed
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
+  @@ -1,9 +1,6 @@
+  -2
+  -2
+  -3
+   4
+   5
+   6
+   7
+   8
+   9
+  record change 1/2 to 'plain'? [Ynesfdaq?] n
+  
+  @@ -4,7 +1,7 @@
+   4
+   5
+   6
+   7
+   8
+   9
+  -10
+  +10.new
+  record change 2/2 to 'plain'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   12:99337501826f
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:11 1970 +0000
+  summary:     end-only
+  
+  diff -r 21df83db12b8 -r 99337501826f plain
+  --- a/plain	Thu Jan 01 00:00:10 1970 +0000
+  +++ b/plain	Thu Jan 01 00:00:11 1970 +0000
+  @@ -7,4 +7,4 @@
+   7
+   8
+   9
+  -10
+  +10.new
+  
+
+Record beginning
+
+  $ hg commit -i -d '12 0' -m begin-only plain <<EOF
+  > y
+  > y
+  > EOF
+  diff --git a/plain b/plain
+  1 hunks, 3 lines changed
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
+  @@ -1,6 +1,3 @@
+  -2
+  -2
+  -3
+   4
+   5
+   6
+  record this change to 'plain'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   13:bbd45465d540
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:12 1970 +0000
+  summary:     begin-only
+  
+  diff -r 99337501826f -r bbd45465d540 plain
+  --- a/plain	Thu Jan 01 00:00:11 1970 +0000
+  +++ b/plain	Thu Jan 01 00:00:12 1970 +0000
+  @@ -1,6 +1,3 @@
+  -2
+  -2
+  -3
+   4
+   5
+   6
+  
+
+Add to beginning, trim from end
+
+  $ rm plain
+  $ for i in 1 2 3 4 5 6 7 8 9; do
+  >  echo $i >> plain
+  > done
+
+Record end
+
+  $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
+  > y
+  > n
+  > y
+  > EOF
+  diff --git a/plain b/plain
+  2 hunks, 4 lines changed
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
+  @@ -1,6 +1,9 @@
+  +1
+  +2
+  +3
+   4
+   5
+   6
+   7
+   8
+   9
+  record change 1/2 to 'plain'? [Ynesfdaq?] n
+  
+  @@ -1,7 +4,6 @@
+   4
+   5
+   6
+   7
+   8
+   9
+  -10.new
+  record change 2/2 to 'plain'? [Ynesfdaq?] y
+  
+
+Add to beginning, middle, end
+
+  $ rm plain
+  $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
+  >   echo $i >> plain
+  > done
+
+Record beginning, middle, and test that format-breaking diffopts are ignored
+
+  $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
+  > y
+  > y
+  > y
+  > n
+  > EOF
+  diff --git a/plain b/plain
+  3 hunks, 7 lines changed
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
+  @@ -1,2 +1,5 @@
+  +1
+  +2
+  +3
+   4
+   5
+  record change 1/3 to 'plain'? [Ynesfdaq?] y
+  
+  @@ -1,6 +4,8 @@
+   4
+   5
+  +5.new
+  +5.reallynew
+   6
+   7
+   8
+   9
+  record change 2/3 to 'plain'? [Ynesfdaq?] y
+  
+  @@ -3,4 +8,6 @@
+   6
+   7
+   8
+   9
+  +10
+  +11
+  record change 3/3 to 'plain'? [Ynesfdaq?] n
+  
+
+  $ hg tip -p
+  changeset:   15:f34a7937ec33
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:14 1970 +0000
+  summary:     middle-only
+  
+  diff -r 82c065d0b850 -r f34a7937ec33 plain
+  --- a/plain	Thu Jan 01 00:00:13 1970 +0000
+  +++ b/plain	Thu Jan 01 00:00:14 1970 +0000
+  @@ -1,5 +1,10 @@
+  +1
+  +2
+  +3
+   4
+   5
+  +5.new
+  +5.reallynew
+   6
+   7
+   8
+  
+
+Record end
+
+  $ hg commit -i -d '15 0' -m end-only plain <<EOF
+  > y
+  > y
+  > EOF
+  diff --git a/plain b/plain
+  1 hunks, 2 lines changed
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
+  @@ -9,3 +9,5 @@
+   7
+   8
+   9
+  +10
+  +11
+  record this change to 'plain'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   16:f9900b71a04c
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:15 1970 +0000
+  summary:     end-only
+  
+  diff -r f34a7937ec33 -r f9900b71a04c plain
+  --- a/plain	Thu Jan 01 00:00:14 1970 +0000
+  +++ b/plain	Thu Jan 01 00:00:15 1970 +0000
+  @@ -9,3 +9,5 @@
+   7
+   8
+   9
+  +10
+  +11
+  
+
+  $ mkdir subdir
+  $ cd subdir
+  $ echo a > a
+  $ hg ci -d '16 0' -Amsubdir
+  adding subdir/a
+
+  $ echo a >> a
+  $ hg commit -i -d '16 0' -m subdir-change a <<EOF
+  > y
+  > y
+  > EOF
+  diff --git a/subdir/a b/subdir/a
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/a'? [Ynesfdaq?] y
+  
+  @@ -1,1 +1,2 @@
+   a
+  +a
+  record this change to 'subdir/a'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   18:61be427a9deb
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:16 1970 +0000
+  summary:     subdir-change
+  
+  diff -r a7ffae4d61cb -r 61be427a9deb subdir/a
+  --- a/subdir/a	Thu Jan 01 00:00:16 1970 +0000
+  +++ b/subdir/a	Thu Jan 01 00:00:16 1970 +0000
+  @@ -1,1 +1,2 @@
+   a
+  +a
+  
+
+  $ echo a > f1
+  $ echo b > f2
+  $ hg add f1 f2
+
+  $ hg ci -mz -d '17 0'
+
+  $ echo a >> f1
+  $ echo b >> f2
+
+Help, quit
+
+  $ hg commit -i <<EOF
+  > ?
+  > q
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] ?
+  
+  y - yes, record this change
+  n - no, skip this change
+  e - edit this change manually
+  s - skip remaining changes to this file
+  f - record remaining changes to this file
+  d - done, skip remaining changes and files
+  a - record all changes to all remaining files
+  q - quit, recording no changes
+  ? - ? (display help)
+  examine changes to 'subdir/f1'? [Ynesfdaq?] q
+  
+  abort: user quit
+  [255]
+
+Skip
+
+  $ hg commit -i <<EOF
+  > s
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] s
+  
+  diff --git a/subdir/f2 b/subdir/f2
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
+  [255]
+
+No
+
+  $ hg commit -i <<EOF
+  > n
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] n
+  
+  diff --git a/subdir/f2 b/subdir/f2
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
+  [255]
+
+f, quit
+
+  $ hg commit -i <<EOF
+  > f
+  > q
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] f
+  
+  diff --git a/subdir/f2 b/subdir/f2
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f2'? [Ynesfdaq?] q
+  
+  abort: user quit
+  [255]
+
+s, all
+
+  $ hg commit -i -d '18 0' -mx <<EOF
+  > s
+  > a
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] s
+  
+  diff --git a/subdir/f2 b/subdir/f2
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f2'? [Ynesfdaq?] a
+  
+
+  $ hg tip -p
+  changeset:   20:b3df3dda369a
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:18 1970 +0000
+  summary:     x
+  
+  diff -r 6e02d6c9906d -r b3df3dda369a subdir/f2
+  --- a/subdir/f2	Thu Jan 01 00:00:17 1970 +0000
+  +++ b/subdir/f2	Thu Jan 01 00:00:18 1970 +0000
+  @@ -1,1 +1,2 @@
+   b
+  +b
+  
+
+f
+
+  $ hg commit -i -d '19 0' -my <<EOF
+  > f
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] f
+  
+
+  $ hg tip -p
+  changeset:   21:38ec577f126b
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:19 1970 +0000
+  summary:     y
+  
+  diff -r b3df3dda369a -r 38ec577f126b subdir/f1
+  --- a/subdir/f1	Thu Jan 01 00:00:18 1970 +0000
+  +++ b/subdir/f1	Thu Jan 01 00:00:19 1970 +0000
+  @@ -1,1 +1,2 @@
+   a
+  +a
+  
+
+#if execbit
+
+Preserve chmod +x
+
+  $ chmod +x f1
+  $ echo a >> f1
+  $ hg commit -i -d '20 0' -mz <<EOF
+  > y
+  > y
+  > y
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  old mode 100644
+  new mode 100755
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
+  @@ -1,2 +1,3 @@
+   a
+   a
+  +a
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
+
+  $ hg tip --config diff.git=True -p
+  changeset:   22:3261adceb075
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:20 1970 +0000
+  summary:     z
+  
+  diff --git a/subdir/f1 b/subdir/f1
+  old mode 100644
+  new mode 100755
+  --- a/subdir/f1
+  +++ b/subdir/f1
+  @@ -1,2 +1,3 @@
+   a
+   a
+  +a
+  
+
+Preserve execute permission on original
+
+  $ echo b >> f1
+  $ hg commit -i -d '21 0' -maa <<EOF
+  > y
+  > y
+  > y
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,4 @@
+   a
+   a
+   a
+  +b
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
+
+  $ hg tip --config diff.git=True -p
+  changeset:   23:b429867550db
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:21 1970 +0000
+  summary:     aa
+  
+  diff --git a/subdir/f1 b/subdir/f1
+  --- a/subdir/f1
+  +++ b/subdir/f1
+  @@ -1,3 +1,4 @@
+   a
+   a
+   a
+  +b
+  
+
+Preserve chmod -x
+
+  $ chmod -x f1
+  $ echo c >> f1
+  $ hg commit -i -d '22 0' -mab <<EOF
+  > y
+  > y
+  > y
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  old mode 100755
+  new mode 100644
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
+  @@ -2,3 +2,4 @@
+   a
+   a
+   b
+  +c
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
+
+  $ hg tip --config diff.git=True -p
+  changeset:   24:0b082130c20a
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:22 1970 +0000
+  summary:     ab
+  
+  diff --git a/subdir/f1 b/subdir/f1
+  old mode 100755
+  new mode 100644
+  --- a/subdir/f1
+  +++ b/subdir/f1
+  @@ -2,3 +2,4 @@
+   a
+   a
+   b
+  +c
+  
+
+#else
+
+Slightly bogus tests to get almost same repo structure as when x bit is used
+- but with different hashes.
+
+Mock "Preserve chmod +x"
+
+  $ echo a >> f1
+  $ hg commit -i -d '20 0' -mz <<EOF
+  > y
+  > y
+  > y
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
+  @@ -1,2 +1,3 @@
+   a
+   a
+  +a
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
+
+  $ hg tip --config diff.git=True -p
+  changeset:   22:0d463bd428f5
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:20 1970 +0000
+  summary:     z
+  
+  diff --git a/subdir/f1 b/subdir/f1
+  --- a/subdir/f1
+  +++ b/subdir/f1
+  @@ -1,2 +1,3 @@
+   a
+   a
+  +a
+  
+
+Mock "Preserve execute permission on original"
+
+  $ echo b >> f1
+  $ hg commit -i -d '21 0' -maa <<EOF
+  > y
+  > y
+  > y
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,4 @@
+   a
+   a
+   a
+  +b
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
+
+  $ hg tip --config diff.git=True -p
+  changeset:   23:0eab41a3e524
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:21 1970 +0000
+  summary:     aa
+  
+  diff --git a/subdir/f1 b/subdir/f1
+  --- a/subdir/f1
+  +++ b/subdir/f1
+  @@ -1,3 +1,4 @@
+   a
+   a
+   a
+  +b
+  
+
+Mock "Preserve chmod -x"
+
+  $ chmod -x f1
+  $ echo c >> f1
+  $ hg commit -i -d '22 0' -mab <<EOF
+  > y
+  > y
+  > y
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
+  @@ -2,3 +2,4 @@
+   a
+   a
+   b
+  +c
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
+
+  $ hg tip --config diff.git=True -p
+  changeset:   24:f4f718f27b7c
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:22 1970 +0000
+  summary:     ab
+  
+  diff --git a/subdir/f1 b/subdir/f1
+  --- a/subdir/f1
+  +++ b/subdir/f1
+  @@ -2,3 +2,4 @@
+   a
+   a
+   b
+  +c
+  
+
+#endif
+
+  $ cd ..
+
+
+Abort early when a merge is in progress
+
+  $ hg up 4
+  1 files updated, 0 files merged, 6 files removed, 0 files unresolved
+
+  $ touch iwillmergethat
+  $ hg add iwillmergethat
+
+  $ hg branch thatbranch
+  marked working directory as branch thatbranch
+  (branches are permanent and global, did you want a bookmark?)
+
+  $ hg ci -m'new head'
+
+  $ hg up default
+  6 files updated, 0 files merged, 2 files removed, 0 files unresolved
+
+  $ hg merge thatbranch
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg commit -i -m'will abort'
+  abort: cannot partially commit a merge (use "hg commit" instead)
+  [255]
+
+  $ hg up -C
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+Editing patch (and ignoring trailing text)
+
+  $ cat > editor.sh << '__EOF__'
+  > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
+  > trailing\nditto' "$1" > tmp
+  > mv tmp "$1"
+  > __EOF__
+  $ cat > editedfile << '__EOF__'
+  > This is the first line
+  > This is the second line
+  > This is the third line
+  > __EOF__
+  $ hg add editedfile
+  $ hg commit -medit-patch-1
+  $ cat > editedfile << '__EOF__'
+  > This line has changed
+  > This change will be committed
+  > This is the third line
+  > __EOF__
+  $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
+  > y
+  > e
+  > EOF
+  diff --git a/editedfile b/editedfile
+  1 hunks, 2 lines changed
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,3 @@
+  -This is the first line
+  -This is the second line
+  +This line has changed
+  +This change will be committed
+   This is the third line
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
+  $ cat editedfile
+  This line has changed
+  This change will be committed
+  This is the third line
+  $ hg cat -r tip editedfile
+  This is the first line
+  This change will be committed
+  This is the third line
+  $ hg revert editedfile
+
+Trying to edit patch for whole file
+
+  $ echo "This is the fourth line" >> editedfile
+  $ hg commit -i <<EOF
+  > e
+  > q
+  > EOF
+  diff --git a/editedfile b/editedfile
+  1 hunks, 1 lines changed
+  examine changes to 'editedfile'? [Ynesfdaq?] e
+  
+  cannot edit patch for whole file
+  examine changes to 'editedfile'? [Ynesfdaq?] q
+  
+  abort: user quit
+  [255]
+  $ hg revert editedfile
+
+Removing changes from patch
+
+  $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
+  $ mv tmp editedfile
+  $ echo "This line has been added" >> editedfile
+  $ cat > editor.sh << '__EOF__'
+  > sed -e 's/^[-+]/ /' "$1" > tmp
+  > mv tmp "$1"
+  > __EOF__
+  $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
+  > y
+  > e
+  > EOF
+  diff --git a/editedfile b/editedfile
+  1 hunks, 3 lines changed
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,3 @@
+  -This is the first line
+  -This change will be committed
+  -This is the third line
+  +This change will not be committed
+  +This is the second line
+  +This line has been added
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
+  no changes to record
+  $ cat editedfile
+  This change will not be committed
+  This is the second line
+  This line has been added
+  $ hg cat -r tip editedfile
+  This is the first line
+  This change will be committed
+  This is the third line
+  $ hg revert editedfile
+
+Invalid patch
+
+  $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
+  $ mv tmp editedfile
+  $ echo "This line has been added" >> editedfile
+  $ cat > editor.sh << '__EOF__'
+  > sed s/This/That/ "$1" > tmp
+  > mv tmp "$1"
+  > __EOF__
+  $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
+  > y
+  > e
+  > EOF
+  diff --git a/editedfile b/editedfile
+  1 hunks, 3 lines changed
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,3 @@
+  -This is the first line
+  -This change will be committed
+  -This is the third line
+  +This change will not be committed
+  +This is the second line
+  +This line has been added
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
+  patching file editedfile
+  Hunk #1 FAILED at 0
+  1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
+  abort: patch failed to apply
+  [255]
+  $ cat editedfile
+  This change will not be committed
+  This is the second line
+  This line has been added
+  $ hg cat -r tip editedfile
+  This is the first line
+  This change will be committed
+  This is the third line
+  $ cat editedfile.rej
+  --- editedfile
+  +++ editedfile
+  @@ -1,3 +1,3 @@
+  -That is the first line
+  -That change will be committed
+  -That is the third line
+  +That change will not be committed
+  +That is the second line
+  +That line has been added
+
+Malformed patch - error handling
+
+  $ cat > editor.sh << '__EOF__'
+  > sed -e '/^@/p' "$1" > tmp
+  > mv tmp "$1"
+  > __EOF__
+  $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
+  > y
+  > e
+  > EOF
+  diff --git a/editedfile b/editedfile
+  1 hunks, 3 lines changed
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,3 @@
+  -This is the first line
+  -This change will be committed
+  -This is the third line
+  +This change will not be committed
+  +This is the second line
+  +This line has been added
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
+  abort: error parsing patch: unhandled transition: range -> range
+  [255]
+
+random text in random positions is still an error
+
+  $ cat > editor.sh << '__EOF__'
+  > sed -e '/^@/i\
+  > other' "$1" > tmp
+  > mv tmp "$1"
+  > __EOF__
+  $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
+  > y
+  > e
+  > EOF
+  diff --git a/editedfile b/editedfile
+  1 hunks, 3 lines changed
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,3 @@
+  -This is the first line
+  -This change will be committed
+  -This is the third line
+  +This change will not be committed
+  +This is the second line
+  +This line has been added
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
+  abort: error parsing patch: unhandled transition: file -> other
+  [255]
+
+  $ hg up -C
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+With win32text
+
+  $ echo '[extensions]' >> .hg/hgrc
+  $ echo 'win32text = ' >> .hg/hgrc
+  $ echo '[decode]' >> .hg/hgrc
+  $ echo '** = cleverdecode:' >> .hg/hgrc
+  $ echo '[encode]' >> .hg/hgrc
+  $ echo '** = cleverencode:' >> .hg/hgrc
+  $ echo '[patch]' >> .hg/hgrc
+  $ echo 'eol = crlf' >> .hg/hgrc
+
+Ignore win32text deprecation warning for now:
+
+  $ echo '[win32text]' >> .hg/hgrc
+  $ echo 'warn = no' >> .hg/hgrc
+
+  $ echo d >> subdir/f1
+  $ hg commit -i -d '24 0' -mw1 <<EOF
+  > y
+  > y
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
+  @@ -3,3 +3,4 @@
+   a
+   b
+   c
+  +d
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
+
+  $ hg tip -p
+  changeset:   28:* (glob)
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:24 1970 +0000
+  summary:     w1
+  
+  diff -r ???????????? -r ???????????? subdir/f1 (glob)
+  --- a/subdir/f1	Thu Jan 01 00:00:23 1970 +0000
+  +++ b/subdir/f1	Thu Jan 01 00:00:24 1970 +0000
+  @@ -3,3 +3,4 @@
+   a
+   b
+   c
+  +d
+  
+
+
+Test --user when ui.username not set
+  $ unset HGUSER
+  $ echo e >> subdir/f1
+  $ hg commit -i  --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
+  > y
+  > y
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  1 hunks, 1 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
+  @@ -4,3 +4,4 @@
+   b
+   c
+   d
+  +e
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
+  $ hg log --template '{author}\n' -l 1
+  xyz
+  $ HGUSER="test"
+  $ export HGUSER
+
+
+Editing patch of newly added file
+
+  $ cat > editor.sh << '__EOF__'
+  > cat "$1"  | sed "s/first/very/g"  > tt
+  > mv tt  "$1"
+  > __EOF__
+  $ cat > newfile << '__EOF__'
+  > This is the first line
+  > This is the second line
+  > This is the third line
+  > __EOF__
+  $ hg add newfile
+  $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
+  > y
+  > e
+  > EOF
+  diff --git a/newfile b/newfile
+  new file mode 100644
+  examine changes to 'newfile'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,3 @@
+  +This is the first line
+  +This is the second line
+  +This is the third line
+  record this change to 'newfile'? [Ynesfdaq?] e
+  
+  $ hg cat -r tip newfile
+  This is the very line
+  This is the second line
+  This is the third line
+
+  $ cat newfile
+  This is the first line
+  This is the second line
+  This is the third line
+
+  $ cd ..
--- a/tests/test-completion.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-completion.t	Fri Mar 13 17:55:04 2015 -0500
@@ -202,7 +202,7 @@
   add: include, exclude, subrepos, dry-run
   annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude, template
   clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
-  commit: addremove, close-branch, amend, secret, edit, include, exclude, message, logfile, date, user, subrepos
+  commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
   diff: rev, change, text, git, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos
   export: output, switch-parent, rev, text, git, nodates
   forget: include, exclude
@@ -268,7 +268,7 @@
   heads: rev, topo, active, closed, style, template
   help: extension, command, keyword
   identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure
-  import: strip, base, edit, force, no-commit, bypass, partial, exact, import-branch, message, logfile, date, user, similarity
+  import: strip, base, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity
   incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
   locate: rev, print0, fullpath, include, exclude
   manifest: rev, all, template
@@ -278,7 +278,7 @@
   phase: public, draft, secret, force, rev
   recover: 
   rename: after, force, include, exclude, dry-run
-  resolve: all, list, mark, unmark, no-status, tool, include, exclude
+  resolve: all, list, mark, unmark, no-status, tool, include, exclude, template
   revert: all, date, rev, no-backup, include, exclude, dry-run
   rollback: dry-run, force
   root: 
--- a/tests/test-context.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-context.py	Fri Mar 13 17:55:04 2015 -0500
@@ -51,7 +51,7 @@
 for d in ctxb.diff(ctxa, git=True):
     print d
 
-# test safeness and correctness of "cxt.status()"
+# test safeness and correctness of "ctx.status()"
 print '= checking context.status():'
 
 # ancestor "wcctx ~ 2"
--- a/tests/test-convert-datesort.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-convert-datesort.t	Fri Mar 13 17:55:04 2015 -0500
@@ -85,9 +85,9 @@
   $ hg -R t-datesort log -G --template '{rev} "{desc}"\n'
   o    12 "c1"
   |\
-  | o  11 "b2x"
+  | _  11 "b2x"
   | |
-  | | o  10 "a7x"
+  | | _  10 "a7x"
   | | |
   o | |  9 "c0"
   | | |
@@ -136,9 +136,9 @@
   $ hg -R t-sourcesort log -G --template '{rev} "{desc}"\n'
   o    12 "c1"
   |\
-  | o  11 "b2x"
+  | _  11 "b2x"
   | |
-  | | o  10 "a7x"
+  | | _  10 "a7x"
   | | |
   o | |  9 "c0"
   | | |
@@ -189,11 +189,11 @@
   |\
   | o  11 "c0"
   | |
-  o |  10 "b2x"
+  _ |  10 "b2x"
   | |
   o |  9 "b1"
   | |
-  | | o  8 "a7x"
+  | | _  8 "a7x"
   | | |
   | | o  7 "a6"
   | | |
--- a/tests/test-convert-filemap.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-convert-filemap.t	Fri Mar 13 17:55:04 2015 -0500
@@ -387,15 +387,15 @@
   1 addb
   0 closedefault
   $ glog -R branchpruning-hg1
-  o  5 "closedefault" files:
+  _  5 "closedefault" files:
   |
   o  4 "addb" files: b
   |
-  | o  3 "closeempty" files:
+  | _  3 "closeempty" files:
   | |
   | o  2 "emptybranch" files:
   |/
-  | o  1 "closefoo" files:
+  | _  1 "closefoo" files:
   |/
   o  0 "adda" files: a
   
@@ -422,7 +422,7 @@
   1 closeempty
   0 closedefault
   $ glog -R branchpruning-hg2
-  o  1 "closedefault" files:
+  _  1 "closedefault" files:
   |
   o  0 "addb" files: b
   
--- a/tests/test-doctest.py	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-doctest.py	Fri Mar 13 17:55:04 2015 -0500
@@ -19,6 +19,7 @@
 testmod('mercurial.hgweb.hgwebdir_mod')
 testmod('mercurial.match')
 testmod('mercurial.minirst')
+testmod('mercurial.patch')
 testmod('mercurial.pathutil')
 testmod('mercurial.revset')
 testmod('mercurial.store')
--- a/tests/test-extension.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-extension.t	Fri Mar 13 17:55:04 2015 -0500
@@ -946,6 +946,9 @@
 Declare the version as supporting this hg version, show regular bts link:
   $ hgver=`$PYTHON -c 'from mercurial import util; print util.version().split("+")[0]'`
   $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
+  $ if [ -z "$hgver" ]; then
+  >   echo "unable to fetch a mercurial version. Make sure __version__ is correct";
+  > fi
   $ rm -f throw.pyc throw.pyo
   $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
   ** unknown exception encountered, please report by visiting
@@ -1140,3 +1143,27 @@
   C sub3/3
 
   $ cd ..
+
+Test synopsis and docstring extending
+
+  $ hg init exthelp
+  $ cat > exthelp.py <<EOF
+  > from mercurial import commands, extensions
+  > def exbookmarks(orig, *args, **opts):
+  >     return orig(*args, **opts)
+  > def uisetup(ui):
+  >     synopsis = ' GREPME [--foo] [-x]'
+  >     docstring = '''
+  >     GREPME make sure that this is in the help!
+  >     '''
+  >     extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
+  >                            synopsis, docstring)
+  > EOF
+  $ abspath=`pwd`/exthelp.py
+  $ echo '[extensions]' >> $HGRCPATH
+  $ echo "exthelp = $abspath" >> $HGRCPATH
+  $ cd exthelp
+  $ hg help bookmarks | grep GREPME
+  hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
+      GREPME make sure that this is in the help!
+
--- a/tests/test-gendoc.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-gendoc.t	Fri Mar 13 17:55:04 2015 -0500
@@ -1,4 +1,5 @@
 #require docutils
+#require gettext
 
 Test document extraction
 
--- a/tests/test-glog.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-glog.t	Fri Mar 13 17:55:04 2015 -0500
@@ -1541,6 +1541,9 @@
   $ testlog --follow
   []
   []
+  $ testlog -rnull
+  ['null']
+  []
   $ echo a > a
   $ echo aa > aa
   $ echo f > f
@@ -1764,6 +1767,13 @@
   nodetag 1
   nodetag 0
 
+Test --follow null parent
+
+  $ hg up -q null
+  $ testlog -f
+  []
+  []
+
 Test --follow-first
 
   $ hg up -q 3
@@ -2192,13 +2202,6 @@
       (func
         ('symbol', 'rev')
         ('symbol', '6'))))
-  --- log.nodes	* (glob)
-  +++ glog.nodes	* (glob)
-  @@ -1,3 +1,3 @@
-  -nodetag 6
-   nodetag 8
-   nodetag 7
-  +nodetag 6
 
 Test --follow-first and forward --rev
 
@@ -2240,6 +2243,14 @@
         ('symbol', 'rev')
         ('symbol', '6'))))
 
+Test --follow with --rev of graphlog extension
+
+  $ hg --config extensions.graphlog= glog -qfr1
+  o  1:216d4c92cf98
+  |
+  o  0:f8035bb17114
+  
+
 Test subdir
 
   $ hg up -q 3
@@ -2354,4 +2365,14 @@
      date:        Thu Jan 01 00:00:00 1970 +0000
   
 
+should not draw line down to null due to the magic of fullreposet
+
+  $ hg log -G -r 'all()' | tail -6
+  |
+  o  changeset:   0:f8035bb17114
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add a
+  
+
   $ cd ..
--- a/tests/test-grep.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-grep.t	Fri Mar 13 17:55:04 2015 -0500
@@ -82,6 +82,10 @@
   port:1:2:+:eggs:export
   port:0:1:+:spam:import
 
+  $ hg up -q null
+  $ hg grep -f port
+  [1]
+
   $ cd ..
   $ hg init t2
   $ cd t2
--- a/tests/test-help.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-help.t	Fri Mar 13 17:55:04 2015 -0500
@@ -411,7 +411,7 @@
   Mercurial Distributed SCM (version *) (glob)
   (see http://mercurial.selenic.com for more information)
   
-  Copyright (C) 2005-2014 Matt Mackall and others
+  Copyright (C) 2005-2015 Matt Mackall and others
   This is free software; see the source for copying conditions. There is NO
   warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
@@ -1101,6 +1101,125 @@
   abort: help section not found
   [255]
 
+Test dynamic list of merge tools only shows up once
+  $ hg help merge-tools
+  Merge Tools
+  """""""""""
+  
+      To merge files Mercurial uses merge tools.
+  
+      A merge tool combines two different versions of a file into a merged file.
+      Merge tools are given the two files and the greatest common ancestor of
+      the two file versions, so they can determine the changes made on both
+      branches.
+  
+      Merge tools are used both for "hg resolve", "hg merge", "hg update", "hg
+      backout" and in several extensions.
+  
+      Usually, the merge tool tries to automatically reconcile the files by
+      combining all non-overlapping changes that occurred separately in the two
+      different evolutions of the same initial base file. Furthermore, some
+      interactive merge programs make it easier to manually resolve conflicting
+      merges, either in a graphical way, or by inserting some conflict markers.
+      Mercurial does not include any interactive merge programs but relies on
+      external tools for that.
+  
+      Available merge tools
+      =====================
+  
+      External merge tools and their properties are configured in the merge-
+      tools configuration section - see hgrc(5) - but they can often just be
+      named by their executable.
+  
+      A merge tool is generally usable if its executable can be found on the
+      system and if it can handle the merge. The executable is found if it is an
+      absolute or relative executable path or the name of an application in the
+      executable search path. The tool is assumed to be able to handle the merge
+      if it can handle symlinks if the file is a symlink, if it can handle
+      binary files if the file is binary, and if a GUI is available if the tool
+      requires a GUI.
+  
+      There are some internal merge tools which can be used. The internal merge
+      tools are:
+  
+      ":dump"
+        Creates three versions of the files to merge, containing the contents of
+        local, other and base. These files can then be used to perform a merge
+        manually. If the file to be merged is named "a.txt", these files will
+        accordingly be named "a.txt.local", "a.txt.other" and "a.txt.base" and
+        they will be placed in the same directory as "a.txt".
+  
+      ":fail"
+        Rather than attempting to merge files that were modified on both
+        branches, it marks them as unresolved. The resolve command must be used
+        to resolve these conflicts.
+  
+      ":local"
+        Uses the local version of files as the merged version.
+  
+      ":merge"
+        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. Markers will have two sections, one for each side
+        of merge.
+  
+      ":merge3"
+        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.
+  
+      ":other"
+        Uses the other version of files as the merged version.
+  
+      ":prompt"
+        Asks the user which of the local or the other version to keep as the
+        merged version.
+  
+      ":tagmerge"
+        Uses the internal tag merge algorithm (experimental).
+  
+      Internal tools are always available and do not require a GUI but will by
+      default not handle symlinks or binary files.
+  
+      Choosing a merge tool
+      =====================
+  
+      Mercurial uses these rules when deciding which merge tool to use:
+  
+      1. If a tool has been specified with the --tool option to merge or
+         resolve, it is used.  If it is the name of a tool in the merge-tools
+         configuration, its configuration is used. Otherwise the specified tool
+         must be executable by the shell.
+      2. If the "HGMERGE" environment variable is present, its value is used and
+         must be executable by the shell.
+      3. If the filename of the file to be merged matches any of the patterns in
+         the merge-patterns configuration section, the first usable merge tool
+         corresponding to a matching pattern is used. Here, binary capabilities
+         of the merge tool are not considered.
+      4. If ui.merge is set it will be considered next. If the value is not the
+         name of a configured tool, the specified value is used and must be
+         executable by the shell. Otherwise the named tool is used if it is
+         usable.
+      5. If any usable merge tools are present in the merge-tools configuration
+         section, the one with the highest priority is used.
+      6. If a program named "hgmerge" can be found on the system, it is used -
+         but it will by default not be used for symlinks and binary files.
+      7. If the file to be merged is not binary and is not a symlink, then
+         internal ":merge" is used.
+      8. The merge of the file fails and must be resolved before commit.
+  
+      Note:
+         After selecting a merge program, Mercurial will by default attempt to
+         merge the files using a simple merge algorithm first. Only if it
+         doesn't succeed because of conflicting changes Mercurial will actually
+         execute the merge program. Whether to use the simple merge algorithm
+         first can be controlled by the premerge setting of the merge tool.
+         Premerge is enabled by default unless the file is binary or a symlink.
+  
+      See the merge-tools and ui sections of hgrc(5) for details on the
+      configuration of merge tools.
+
 Test usage of section marks in help documents
 
   $ cd "$TESTDIR"/../doc
--- a/tests/test-hgrc.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgrc.t	Fri Mar 13 17:55:04 2015 -0500
@@ -71,7 +71,7 @@
   Mercurial Distributed SCM (version *) (glob)
   (see http://mercurial.selenic.com for more information)
   
-  Copyright (C) 2005-2014 Matt Mackall and others
+  Copyright (C) 2005-2015 Matt Mackall and others
   This is free software; see the source for copying conditions. There is NO
   warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   $ unset FAKEPATH
--- a/tests/test-hgweb-commands.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-commands.t	Fri Mar 13 17:55:04 2015 -0500
@@ -726,7 +726,6 @@
   <ul>
    <li><a href="/help">help</a></li>
   </ul>
-  <p></p>
   <div class="atom-logo">
   <a href="/atom-log" title="subscribe to atom feed">
   <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -752,11 +751,13 @@
   </div>
   
   <table class="bigtable">
+  <thead>
    <tr>
     <th class="age">age</th>
     <th class="author">author</th>
     <th class="description">description</th>
    </tr>
+  </thead>
   <tbody class="stripes2">
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
@@ -873,7 +874,8 @@
   </tr>
   <tr>
    <th class="date">date</th>
-   <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
+   <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+  </tr>
   <tr>
    <th class="author">parents</th>
    <td class="author"></td>
@@ -894,8 +896,7 @@
       <a id="diffstatexpand" href="javascript:toggleDiffstat()">[<tt>+</tt>]</a>
       <div id="diffstatdetails" style="display:none;">
         <a href="javascript:toggleDiffstat()">[<tt>-</tt>]</a>
-        <p></p>
-        <table class="stripes2">  <tr>
+        <table class="diffstat-table stripes2">  <tr>
       <td class="diffstat-file"><a href="#l1.1">da/foo</a></td>
       <td class="diffstat-total" align="right">1</td>
       <td class="diffstat-graph">
@@ -1012,11 +1013,13 @@
   </div>
   
   <table class="bigtable">
+  <thead>
    <tr>
     <th class="age">age</th>
     <th class="author">author</th>
     <th class="description">description</th>
    </tr>
+  </thead>
   <tbody class="stripes2">
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
--- a/tests/test-hgweb-descend-empties.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-descend-empties.t	Fri Mar 13 17:55:04 2015 -0500
@@ -81,11 +81,13 @@
   </form>
   
   <table class="bigtable">
+  <thead>
   <tr>
     <th class="name">name</th>
     <th class="size">size</th>
     <th class="permissions">permissions</th>
   </tr>
+  </thead>
   <tbody class="stripes2">
   <tr class="fileline">
     <td class="name"><a href="/file/9087c84a0f5d/">[up]</a></td>
--- a/tests/test-hgweb-diffs.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-diffs.t	Fri Mar 13 17:55:04 2015 -0500
@@ -97,7 +97,8 @@
   </tr>
   <tr>
    <th class="date">date</th>
-   <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
+   <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+  </tr>
   <tr>
    <th class="author">parents</th>
    <td class="author"></td>
@@ -118,8 +119,7 @@
       <a id="diffstatexpand" href="javascript:toggleDiffstat()">[<tt>+</tt>]</a>
       <div id="diffstatdetails" style="display:none;">
         <a href="javascript:toggleDiffstat()">[<tt>-</tt>]</a>
-        <p></p>
-        <table class="stripes2">  <tr>
+        <table class="diffstat-table stripes2">  <tr>
       <td class="diffstat-file"><a href="#l1.1">a</a></td>
       <td class="diffstat-total" align="right">1</td>
       <td class="diffstat-graph">
@@ -369,7 +369,8 @@
   </tr>
   <tr>
    <th class="date">date</th>
-   <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
+   <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+  </tr>
   <tr>
    <th class="author">parents</th>
    <td class="author"></td>
@@ -390,8 +391,7 @@
       <a id="diffstatexpand" href="javascript:toggleDiffstat()">[<tt>+</tt>]</a>
       <div id="diffstatdetails" style="display:none;">
         <a href="javascript:toggleDiffstat()">[<tt>-</tt>]</a>
-        <p></p>
-        <table class="stripes2">  <tr>
+        <table class="diffstat-table stripes2">  <tr>
       <td class="diffstat-file"><a href="#l1.1">a</a></td>
       <td class="diffstat-total" align="right">1</td>
       <td class="diffstat-graph">
--- a/tests/test-hgweb-empty.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-empty.t	Fri Mar 13 17:55:04 2015 -0500
@@ -48,7 +48,6 @@
   <ul>
    <li><a href="/help">help</a></li>
   </ul>
-  <p></p>
   <div class="atom-logo">
   <a href="/atom-log" title="subscribe to atom feed">
   <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -74,11 +73,13 @@
   </div>
   
   <table class="bigtable">
+  <thead>
    <tr>
     <th class="age">age</th>
     <th class="author">author</th>
     <th class="description">description</th>
    </tr>
+  </thead>
   <tbody class="stripes2">
   
   </tbody>
@@ -158,7 +159,6 @@
   <ul>
    <li><a href="/help">help</a></li>
   </ul>
-  <p></p>
   <div class="atom-logo">
   <a href="/atom-log" title="subscribe to atom feed">
   <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -184,11 +184,13 @@
   </div>
   
   <table class="bigtable">
+  <thead>
    <tr>
     <th class="age">age</th>
     <th class="author">author</th>
     <th class="description">description</th>
    </tr>
+  </thead>
   <tbody class="stripes2">
   
   </tbody>
@@ -264,7 +266,6 @@
   <ul>
    <li><a href="/help">help</a></li>
   </ul>
-  <p></p>
   <div class="atom-logo">
   <a href="/atom-log" title="subscribe to atom feed">
   <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -431,11 +432,13 @@
   </form>
   
   <table class="bigtable">
+  <thead>
   <tr>
     <th class="name">name</th>
     <th class="size">size</th>
     <th class="permissions">permissions</th>
   </tr>
+  </thead>
   <tbody class="stripes2">
   <tr class="fileline">
     <td class="name"><a href="/file/000000000000/">[up]</a></td>
--- a/tests/test-hgweb-filelog.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-filelog.t	Fri Mar 13 17:55:04 2015 -0500
@@ -156,7 +156,6 @@
   <ul>
   <li><a href="/help">help</a></li>
   </ul>
-  <p></p>
   <div class="atom-logo">
   <a href="/atom-log/01de2d66a28d/a" title="subscribe to atom feed">
   <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -181,11 +180,13 @@
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
   
   <table class="bigtable">
+  <thead>
    <tr>
     <th class="age">age</th>
     <th class="author">author</th>
     <th class="description">description</th>
    </tr>
+  </thead>
   <tbody class="stripes2">
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
@@ -266,7 +267,6 @@
   <ul>
   <li><a href="/help">help</a></li>
   </ul>
-  <p></p>
   <div class="atom-logo">
   <a href="/atom-log/01de2d66a28d/a" title="subscribe to atom feed">
   <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -291,11 +291,13 @@
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
   
   <table class="bigtable">
+  <thead>
    <tr>
     <th class="age">age</th>
     <th class="author">author</th>
     <th class="description">description</th>
    </tr>
+  </thead>
   <tbody class="stripes2">
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
@@ -376,7 +378,6 @@
   <ul>
   <li><a href="/help">help</a></li>
   </ul>
-  <p></p>
   <div class="atom-logo">
   <a href="/atom-log/5ed941583260/a" title="subscribe to atom feed">
   <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -401,11 +402,13 @@
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
   
   <table class="bigtable">
+  <thead>
    <tr>
     <th class="age">age</th>
     <th class="author">author</th>
     <th class="description">description</th>
    </tr>
+  </thead>
   <tbody class="stripes2">
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
@@ -481,7 +484,6 @@
   <ul>
   <li><a href="/help">help</a></li>
   </ul>
-  <p></p>
   <div class="atom-logo">
   <a href="/atom-log/5ed941583260/a" title="subscribe to atom feed">
   <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
@@ -506,11 +508,13 @@
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
   
   <table class="bigtable">
+  <thead>
    <tr>
     <th class="age">age</th>
     <th class="author">author</th>
     <th class="description">description</th>
    </tr>
+  </thead>
   <tbody class="stripes2">
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
--- a/tests/test-hgweb-removed.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb-removed.t	Fri Mar 13 17:55:04 2015 -0500
@@ -78,7 +78,8 @@
   </tr>
   <tr>
    <th class="date">date</th>
-   <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
+   <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+  </tr>
   <tr>
    <th class="author">parents</th>
    <td class="author"><a href="/rev/cb9a9f314b8b">cb9a9f314b8b</a> </td>
@@ -99,8 +100,7 @@
       <a id="diffstatexpand" href="javascript:toggleDiffstat()">[<tt>+</tt>]</a>
       <div id="diffstatdetails" style="display:none;">
         <a href="javascript:toggleDiffstat()">[<tt>-</tt>]</a>
-        <p></p>
-        <table class="stripes2">  <tr>
+        <table class="diffstat-table stripes2">  <tr>
       <td class="diffstat-file"><a href="#l1.1">a</a></td>
       <td class="diffstat-total" align="right">1</td>
       <td class="diffstat-graph">
--- a/tests/test-hgweb.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgweb.t	Fri Mar 13 17:55:04 2015 -0500
@@ -272,11 +272,13 @@
   </form>
   
   <table class="bigtable">
+  <thead>
   <tr>
     <th class="name">name</th>
     <th class="size">size</th>
     <th class="permissions">permissions</th>
   </tr>
+  </thead>
   <tbody class="stripes2">
   <tr class="fileline">
     <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
--- a/tests/test-hgwebdir.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hgwebdir.t	Fri Mar 13 17:55:04 2015 -0500
@@ -201,6 +201,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   
   <table class="bigtable">
+      <thead>
       <tr>
           <th><a href="?sort=name">Name</a></th>
           <th><a href="?sort=description">Description</a></th>
@@ -209,6 +210,7 @@
           <th>&nbsp;</th>
           <th>&nbsp;</th>
       </tr>
+      </thead>
       <tbody class="stripes2">
       
   <tr>
@@ -699,6 +701,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> &gt; <a href="/t">t</a> </h2>
   
   <table class="bigtable">
+      <thead>
       <tr>
           <th><a href="?sort=name">Name</a></th>
           <th><a href="?sort=description">Description</a></th>
@@ -707,6 +710,7 @@
           <th>&nbsp;</th>
           <th>&nbsp;</th>
       </tr>
+      </thead>
       <tbody class="stripes2">
       
   <tr>
@@ -1128,6 +1132,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   
   <table class="bigtable">
+      <thead>
       <tr>
           <th><a href="?sort=name">Name</a></th>
           <th><a href="?sort=description">Description</a></th>
@@ -1136,6 +1141,7 @@
           <th>&nbsp;</th>
           <th>&nbsp;</th>
       </tr>
+      </thead>
       <tbody class="stripes2">
       
       </tbody>
--- a/tests/test-highlight.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-highlight.t	Fri Mar 13 17:55:04 2015 -0500
@@ -268,10 +268,12 @@
   
   <div class="overflow">
   <table class="bigtable">
+  <thead>
   <tr>
    <th class="annotate">rev</th>
    <th class="line">&nbsp;&nbsp;line source</th>
   </tr>
+  </thead>
   <tbody class="stripes2">
     
   <tr id="l1">
--- a/tests/test-histedit-arguments.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-histedit-arguments.t	Fri Mar 13 17:55:04 2015 -0500
@@ -103,6 +103,15 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg up --quiet
 
+Test config specified default
+-----------------------------
+
+  $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF
+  > pick c8e68270e35a 3 four
+  > pick 08d98a8350f3 4 five
+  > EOF
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
 Run on a revision not descendants of the initial parent
 --------------------------------------------------------------------
 
--- a/tests/test-histedit-drop.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-histedit-drop.t	Fri Mar 13 17:55:04 2015 -0500
@@ -96,7 +96,6 @@
 Check histedit_source
 
   $ hg log --debug --rev f518305ce889
-  invalid branchheads cache (visible): tip differs
   changeset:   4:f518305ce889c07cb5bd05522176d75590ef3324
   tag:         tip
   phase:       draft
--- a/tests/test-histedit-edit.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-histedit-edit.t	Fri Mar 13 17:55:04 2015 -0500
@@ -3,13 +3,14 @@
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > histedit=
+  > strip=
   > EOF
 
   $ initrepo ()
   > {
   >     hg init r
   >     cd r
-  >     for x in a b c d e f ; do
+  >     for x in a b c d e f g; do
   >         echo $x > $x
   >         hg add $x
   >         hg ci -m $x
@@ -20,10 +21,15 @@
 
 log before edit
   $ hg log --graph
-  @  changeset:   5:652413bf663e
+  @  changeset:   6:3c6a8ed2ebe8
   |  tag:         tip
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     g
+  |
+  o  changeset:   5:652413bf663e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     f
   |
   o  changeset:   4:e860deea161a
@@ -58,11 +64,19 @@
   > pick 055a42cdd887 d
   > edit e860deea161a e
   > pick 652413bf663e f
+  > pick 3c6a8ed2ebe8 g
   > EOF
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
   Make changes as needed, you may commit or record as needed now.
   When you are finished, run hg histedit --continue to resume.
 
+edit the plan
+  $ hg histedit --edit-plan --commands - 2>&1 << EOF
+  > edit e860deea161a e
+  > pick 652413bf663e f
+  > drop 3c6a8ed2ebe8 g
+  > EOF
+
 Go at a random point and try to continue
 
   $ hg id -n
@@ -72,10 +86,22 @@
   (use 'hg histedit --continue' or 'hg histedit --abort')
   [255]
 
+Try to delete necessary commit
+  $ hg strip -r 652413bf663e
+  abort: histedit in progress, can't strip 363532343133
+  [255]
+
 commit, then edit the revision
   $ hg ci -m 'wat'
   created new head
   $ echo a > e
+
+qnew should fail while we're in the middle of the edit step
+
+  $ hg --config extensions.mq= qnew please-fail
+  abort: histedit in progress
+  (use 'hg histedit --continue' or 'hg histedit --abort')
+  [255]
   $ HGEDITOR='echo foobaz > ' 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
--- a/tests/test-hook.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-hook.t	Fri Mar 13 17:55:04 2015 -0500
@@ -12,13 +12,19 @@
   > pre-identify = python "$TESTDIR/printenv.py" pre-identify 1
   > pre-cat = python "$TESTDIR/printenv.py" pre-cat
   > post-cat = python "$TESTDIR/printenv.py" post-cat
+  > pretxnopen = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnopen"
+  > pretxnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnclose"
+  > txnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnclose"
   > EOF
   $ echo a > a
   $ hg add a
   $ hg commit -m a
   precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
+  pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
   0:cb9a9f314b8b
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
+  txnclose hook: HG_PHASES_MOVED=1 HG_TXNNAME=commit
   commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
   commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
 
@@ -42,8 +48,11 @@
   $ echo b >> a
   $ hg commit -m a1 -d "1 0"
   precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
+  pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
   1:ab228980c14d
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
+  txnclose hook: HG_TXNNAME=commit
   commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
   commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
   $ hg update -C 0
@@ -52,8 +61,11 @@
   $ hg add b
   $ hg commit -m b -d '1 0'
   precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
+  pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
   2:ee9deb46ab31
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
+  txnclose hook: HG_TXNNAME=commit
   commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
   commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
   created new head
@@ -62,8 +74,11 @@
   (branch merge, don't forget to commit)
   $ hg commit -m merge -d '2 0'
   precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
+  pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
   3:07f3376c1e65
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
+  txnclose hook: HG_TXNNAME=commit
   commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
   commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
 
@@ -103,9 +118,12 @@
   $ hg tag -d '3 0' a
   pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
   precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
+  pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
   4:539e4b31b6dc
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=commit
   tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
+  txnclose hook: HG_TXNNAME=commit
   commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
   commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
   $ hg tag -l la
@@ -137,6 +155,7 @@
   4:539e4b31b6dc
   $ hg commit -m 'fail' -d '4 0'
   precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
+  pretxnopen hook: HG_TXNNAME=commit
   pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
   5:6f611f8018c1
   5:6f611f8018c1
@@ -198,6 +217,9 @@
   pushing to ../a
   searching for changes
   no changes found
+  pretxnopen hook: HG_TXNNAME=bookmarks
+  pretxnclose hook: HG_PENDING=$TESTTMP/a HG_XNNAME=bookmarks
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNNAME=bookmarks
   pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
   exporting bookmark foo
   [1]
--- a/tests/test-https.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-https.t	Fri Mar 13 17:55:04 2015 -0500
@@ -119,12 +119,12 @@
 Apple's OpenSSL. This trick do not work with plain OpenSSL.
 
   $ DISABLEOSXDUMMYCERT=
-#if osx
+#if defaultcacerts
   $ hg clone https://localhost:$HGPORT/ copy-pull
   abort: error: *certificate verify failed* (glob)
   [255]
 
-  $ DISABLEOSXDUMMYCERT="--config=web.cacerts="
+  $ DISABLEOSXDUMMYCERT="--config=web.cacerts=!"
 #endif
 
 clone via pull
@@ -156,8 +156,8 @@
   $ echo '[hooks]' >> .hg/hgrc
   $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
   $ hg pull $DISABLEOSXDUMMYCERT
+  pulling from https://localhost:$HGPORT/
   warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
-  pulling from https://localhost:$HGPORT/
   searching for changes
   adding changesets
   adding manifests
@@ -188,28 +188,30 @@
   searching for changes
   no changes found
   $ P=`pwd` hg -R copy-pull pull --insecure
+  pulling from https://localhost:$HGPORT/
   warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
-  pulling from https://localhost:$HGPORT/
   searching for changes
   no changes found
 
 cacert mismatch
 
   $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
+  pulling from https://127.0.0.1:$HGPORT/
   abort: 127.0.0.1 certificate error: certificate is for localhost
   (configure hostfingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca or use --insecure to connect insecurely)
   [255]
   $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
+  pulling from https://127.0.0.1:$HGPORT/
   warning: 127.0.0.1 certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
-  pulling from https://127.0.0.1:$HGPORT/
   searching for changes
   no changes found
   $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
+  pulling from https://localhost:$HGPORT/
   abort: error: *certificate verify failed* (glob)
   [255]
   $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
+  pulling from https://localhost:$HGPORT/
   warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
-  pulling from https://localhost:$HGPORT/
   searching for changes
   no changes found
 
@@ -218,6 +220,7 @@
   $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
   $ cat hg1.pid >> $DAEMON_PIDS
   $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
+  pulling from https://localhost:$HGPORT1/
   abort: error: *certificate verify failed* (glob)
   [255]
 
@@ -226,6 +229,7 @@
   $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
   $ cat hg2.pid >> $DAEMON_PIDS
   $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
+  pulling from https://localhost:$HGPORT2/
   abort: error: *certificate verify failed* (glob)
   [255]
 
@@ -236,7 +240,7 @@
   $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
 
 - works without cacerts
-  $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
+  $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=!
   5fed3813f7f5
 
 - fails when cert doesn't match hostname (port is ignored)
@@ -267,8 +271,8 @@
 Test unvalidated https through proxy
 
   $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
+  pulling from https://localhost:$HGPORT/
   warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
-  pulling from https://localhost:$HGPORT/
   searching for changes
   no changes found
 
@@ -286,8 +290,10 @@
 Test https with cert problems through proxy
 
   $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
+  pulling from https://localhost:$HGPORT/
   abort: error: *certificate verify failed* (glob)
   [255]
   $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
+  pulling from https://localhost:$HGPORT2/
   abort: error: *certificate verify failed* (glob)
   [255]
--- a/tests/test-import-bypass.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-import-bypass.t	Fri Mar 13 17:55:04 2015 -0500
@@ -104,6 +104,86 @@
   $ hg rollback
   repository tip rolled back to revision 1 (undo import)
 
+Test --strip with --bypass
+
+  $ mkdir -p dir/dir2
+  $ echo bb > dir/dir2/b
+  $ echo cc > dir/dir2/c
+  $ echo d > dir/d
+  $ hg ci -Am 'addabcd'
+  adding dir/d
+  adding dir/dir2/b
+  adding dir/dir2/c
+  $ shortlog
+  @  2:d805bc8236b6 test 0 0 - default - addabcd
+  |
+  | o  1:4e322f7ce8e3 test 0 0 - foo - changea
+  |/
+  o  0:07f494440405 test 0 0 - default - adda
+  
+  $ hg import --bypass --strip 2 --prefix dir/ - <<EOF
+  > # HG changeset patch
+  > # User test
+  > # Date 0 0
+  > # Branch foo
+  > changeabcd
+  > 
+  > diff --git a/foo/a b/foo/a
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/foo/a
+  > @@ -0,0 +1 @@
+  > +a
+  > diff --git a/foo/dir2/b b/foo/dir2/b2
+  > rename from foo/dir2/b
+  > rename to foo/dir2/b2
+  > diff --git a/foo/dir2/c b/foo/dir2/c
+  > --- a/foo/dir2/c
+  > +++ b/foo/dir2/c
+  > @@ -0,0 +1 @@
+  > +cc
+  > diff --git a/foo/d b/foo/d
+  > deleted file mode 100644
+  > --- a/foo/d
+  > +++ /dev/null
+  > @@ -1,1 +0,0 @@
+  > -d
+  > EOF
+  applying patch from stdin
+
+  $ shortlog
+  o  3:5bd46886ca3e test 0 0 - default - changeabcd
+  |
+  @  2:d805bc8236b6 test 0 0 - default - addabcd
+  |
+  | o  1:4e322f7ce8e3 test 0 0 - foo - changea
+  |/
+  o  0:07f494440405 test 0 0 - default - adda
+  
+  $ hg diff --change 3 --git
+  diff --git a/dir/a b/dir/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/dir/a
+  @@ -0,0 +1,1 @@
+  +a
+  diff --git a/dir/d b/dir/d
+  deleted file mode 100644
+  --- a/dir/d
+  +++ /dev/null
+  @@ -1,1 +0,0 @@
+  -d
+  diff --git a/dir/dir2/b b/dir/dir2/b2
+  rename from dir/dir2/b
+  rename to dir/dir2/b2
+  diff --git a/dir/dir2/c b/dir/dir2/c
+  --- a/dir/dir2/c
+  +++ b/dir/dir2/c
+  @@ -1,1 +1,2 @@
+   cc
+  +cc
+  $ hg -q --config extensions.strip= strip .
+
 Test unsupported combinations
 
   $ hg import --bypass --no-commit ../test.diff
@@ -112,6 +192,9 @@
   $ hg import --bypass --similarity 50 ../test.diff
   abort: cannot use --similarity with --bypass
   [255]
+  $ hg import --exact --prefix dir/ ../test.diff
+  abort: cannot use --exact with --prefix
+  [255]
 
 Test commit editor
 (this also tests that editor is invoked, if the patch doesn't contain
--- a/tests/test-import-git.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-import-git.t	Fri Mar 13 17:55:04 2015 -0500
@@ -612,12 +612,64 @@
     a
   R a
 
-Renames, similarity and git diff
+Prefix with strip, renames, creates etc
 
   $ hg revert -aC
   undeleting a
   forgetting b
   $ rm b
+  $ mkdir -p dir/dir2
+  $ echo b > dir/dir2/b
+  $ echo c > dir/dir2/c
+  $ echo d > dir/d
+  $ hg ci -Am addbcd
+  adding dir/d
+  adding dir/dir2/b
+  adding dir/dir2/c
+(test that prefixes are relative to the root)
+  $ mkdir tmpdir
+  $ cd tmpdir
+  $ hg import --no-commit -p2 --prefix dir/ - <<EOF
+  > diff --git a/foo/a b/foo/a
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/foo/a
+  > @@ -0,0 +1 @@
+  > +a
+  > diff --git a/foo/dir2/b b/foo/dir2/b2
+  > rename from foo/dir2/b
+  > rename to foo/dir2/b2
+  > diff --git a/foo/dir2/c b/foo/dir2/c
+  > --- a/foo/dir2/c
+  > +++ b/foo/dir2/c
+  > @@ -0,0 +1 @@
+  > +cc
+  > diff --git a/foo/d b/foo/d
+  > deleted file mode 100644
+  > --- a/foo/d
+  > +++ /dev/null
+  > @@ -1,1 +0,0 @@
+  > -d
+  > EOF
+  applying patch from stdin
+  $ hg st --copies
+  M dir/dir2/c
+  A dir/a
+  A dir/dir2/b2
+    dir/dir2/b
+  R dir/d
+  R dir/dir2/b
+  $ cd ..
+
+Renames, similarity and git diff
+
+  $ hg revert -aC
+  forgetting dir/a (glob)
+  undeleting dir/d (glob)
+  undeleting dir/dir2/b (glob)
+  forgetting dir/dir2/b2 (glob)
+  reverting dir/dir2/c (glob)
+  $ rm dir/a dir/dir2/b2
   $ hg import --similarity 90 --no-commit - <<EOF
   > diff --git a/a b/b
   > rename from a
--- a/tests/test-import.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-import.t	Fri Mar 13 17:55:04 2015 -0500
@@ -670,6 +670,25 @@
   $ hg status
   $ cat a
   bb
+
+test --prefix
+
+  $ mkdir -p dir/dir2
+  $ echo b > dir/dir2/b
+  $ hg ci -Am b
+  adding dir/dir2/b
+  $ hg import -p2 --prefix dir - << EOF
+  > foobar
+  > --- drop1/drop2/dir2/b
+  > +++ drop1/drop2/dir2/b
+  > @@ -1,1 +1,1 @@
+  > -b
+  > +cc
+  > EOF
+  applying patch from stdin
+  $ hg status
+  $ cat dir/dir2/b
+  cc
   $ cd ..
 
 
--- a/tests/test-keyword.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-keyword.t	Fri Mar 13 17:55:04 2015 -0500
@@ -473,18 +473,24 @@
 
   $ hg -v record -l msg -d '12 2' r<<EOF
   > y
+  > y
   > EOF
   diff --git a/r b/r
   new file mode 100644
   examine changes to 'r'? [Ynesfdaq?] y
   
+  @@ -0,0 +1,1 @@
+  +$Id$
+  record this change to 'r'? [Ynesfdaq?] y
+  
+  resolving manifests
+  patching file r
   committing files:
   r
   committing manifest
   committing changelog
   committed changeset 3:82a2f715724d
   overwriting r expanding keywords
- - status call required for dirstate.normallookup() check
   $ hg status r
   $ hg --verbose rollback
   repository tip rolled back to revision 2 (undo commit)
@@ -501,11 +507,18 @@
   $ hg add i
   $ hg --verbose record -d '13 1' -m recignored<<EOF
   > y
+  > y
   > EOF
   diff --git a/i b/i
   new file mode 100644
   examine changes to 'i'? [Ynesfdaq?] y
   
+  @@ -0,0 +1,1 @@
+  +$Id$
+  record this change to 'i'? [Ynesfdaq?] y
+  
+  resolving manifests
+  patching file i
   committing files:
   i
   committing manifest
--- a/tests/test-largefiles-cache.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-largefiles-cache.t	Fri Mar 13 17:55:04 2015 -0500
@@ -136,7 +136,7 @@
 #endif
 
 Test issue 4053 (remove --after on a deleted, uncommitted file shouldn't say
-it is missing, but a remove on a nonexistant unknown file still should.  Same
+it is missing, but a remove on a nonexistent unknown file still should.  Same
 for a forget.)
 
   $ cd src
--- a/tests/test-largefiles-misc.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-largefiles-misc.t	Fri Mar 13 17:55:04 2015 -0500
@@ -362,6 +362,14 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg status -S
 
+  $ hg forget -v subrepo/large.txt
+  removing subrepo/large.txt (glob)
+
+Test reverting a forgotten file
+  $ hg revert -R subrepo subrepo/large.txt
+  $ hg status -SA subrepo/large.txt
+  C subrepo/large.txt
+
   $ hg rm -v subrepo/large.txt
   removing subrepo/large.txt (glob)
   $ hg revert -R subrepo subrepo/large.txt
@@ -443,6 +451,10 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     anotherlarge
   
+  $ hg --debug log -T '{rev}: {desc}\n' ../sub/anotherlarge
+  updated patterns: ['../.hglf/sub/../sub/anotherlarge', '../sub/anotherlarge']
+  1: anotherlarge
+
   $ hg log -G anotherlarge
   @  changeset:   1:9627a577c5e9
   |  tag:         tip
@@ -450,6 +462,30 @@
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     anotherlarge
   |
+
+  $ hg log glob:another*
+  changeset:   1:9627a577c5e9
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     anotherlarge
+  
+  $ hg --debug log -T '{rev}: {desc}\n' -G glob:another*
+  updated patterns: ['glob:../.hglf/sub/another*', 'glob:another*']
+  @  1: anotherlarge
+  |
+
+#if no-msys
+  $ hg --debug log -T '{rev}: {desc}\n' 'glob:../.hglf/sub/another*' # no-msys
+  updated patterns: ['glob:../.hglf/sub/another*']
+  1: anotherlarge
+
+  $ hg --debug log -G -T '{rev}: {desc}\n' 'glob:../.hglf/sub/another*' # no-msys
+  updated patterns: ['glob:../.hglf/sub/another*']
+  @  1: anotherlarge
+  |
+#endif
+
   $ echo more >> anotherlarge
   $ hg st .
   M anotherlarge
@@ -460,8 +496,33 @@
   ? sub/anotherlarge.orig
   $ cd ..
 
+Test glob logging from the root dir
+  $ hg log glob:**another*
+  changeset:   1:9627a577c5e9
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     anotherlarge
+  
+  $ hg log -G glob:**another*
+  @  changeset:   1:9627a577c5e9
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     anotherlarge
+  |
+
   $ cd ..
 
+Log from outer space
+  $ hg --debug log -R addrm2 -T '{rev}: {desc}\n' 'addrm2/sub/anotherlarge'
+  updated patterns: ['addrm2/.hglf/sub/anotherlarge', 'addrm2/sub/anotherlarge']
+  1: anotherlarge
+  $ hg --debug log -R addrm2 -T '{rev}: {desc}\n' 'addrm2/.hglf/sub/anotherlarge'
+  updated patterns: ['addrm2/.hglf/sub/anotherlarge']
+  1: anotherlarge
+
+
 Check error message while exchange
 =========================================================
 
--- a/tests/test-largefiles.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-largefiles.t	Fri Mar 13 17:55:04 2015 -0500
@@ -1183,12 +1183,12 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
-  0 largefiles cached
   rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
   Invoking status precommit hook
   M sub/normal4
   M sub2/large6
   saved backup bundle to $TESTTMP/d/.hg/strip-backup/f574fb32bb45-dd1d9f80-backup.hg (glob)
+  0 largefiles cached
   $ [ -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
   $ hg log --template '{rev}:{node|short}  {desc|firstline}\n'
   9:598410d3eb9a  modify normal file largefile in repo d
--- a/tests/test-log.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-log.t	Fri Mar 13 17:55:04 2015 -0500
@@ -46,18 +46,31 @@
   $ hg ci -me -d '5 0'
 
 Make sure largefiles doesn't interfere with logging a regular file
-  $ hg log a --config extensions.largefiles=
-  changeset:   0:9161b9aeaf16
-  user:        test
-  date:        Thu Jan 01 00:00:01 1970 +0000
-  summary:     a
-  
+  $ hg --debug log a -T '{rev}: {desc}\n' --config extensions.largefiles=
+  updated patterns: ['.hglf/a', 'a']
+  0: a
   $ hg log a
   changeset:   0:9161b9aeaf16
   user:        test
   date:        Thu Jan 01 00:00:01 1970 +0000
   summary:     a
   
+  $ hg log glob:a*
+  changeset:   3:2ca5ba701980
+  user:        test
+  date:        Thu Jan 01 00:00:04 1970 +0000
+  summary:     d
+  
+  changeset:   0:9161b9aeaf16
+  user:        test
+  date:        Thu Jan 01 00:00:01 1970 +0000
+  summary:     a
+  
+  $ hg --debug log glob:a* -T '{rev}: {desc}\n' --config extensions.largefiles=
+  updated patterns: ['glob:.hglf/a*', 'glob:a*']
+  3: d
+  0: a
+
 log on directory
 
   $ hg log dir
@@ -631,7 +644,7 @@
   
 
 
-log -f -r 1:tip
+log -f -r '1 + 4'
 
   $ hg up -C 0
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -639,25 +652,24 @@
   $ hg ci -Amb2 -d '1 0'
   adding b2
   created new head
-  $ hg log -f -r 1:tip
+  $ hg log -f -r '1 + 4'
+  changeset:   4:ddb82e70d1a1
+  tag:         tip
+  parent:      0:67e992f2c4f3
+  user:        test
+  date:        Thu Jan 01 00:00:01 1970 +0000
+  summary:     b2
+  
   changeset:   1:3d5bf5654eda
   user:        test
   date:        Thu Jan 01 00:00:01 1970 +0000
   summary:     r1
   
-  changeset:   2:60c670bf5b30
+  changeset:   0:67e992f2c4f3
   user:        test
   date:        Thu Jan 01 00:00:01 1970 +0000
-  summary:     r2
+  summary:     base
   
-  changeset:   3:e62f78d544b4
-  parent:      1:3d5bf5654eda
-  user:        test
-  date:        Thu Jan 01 00:00:01 1970 +0000
-  summary:     b1
-  
-
-
 log -f -r null
 
   $ hg log -f -r null
@@ -672,10 +684,17 @@
   
 
 
+log -f with null parent
+
+  $ hg up -C null
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg log -f
+
+
 log -r .  with two parents
 
   $ hg up -C 3
-  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg merge tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
@@ -1339,6 +1358,11 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     add foo, related
   
+  changeset:   2:c4c64aedf0f7
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add unrelated old foo
+  
   $ cd ..
 
 Issue2383: hg log showing _less_ differences than hg diff
@@ -1652,7 +1676,7 @@
   |
   o  a
   
-Ensure that largefiles doesn't intefere with following a normal file
+Ensure that largefiles doesn't interfere with following a normal file
   $ hg  --config extensions.largefiles= log -f d -T '{desc}' -G
   @  c
   |
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-manifest.py	Fri Mar 13 17:55:04 2015 -0500
@@ -0,0 +1,232 @@
+import binascii
+import unittest
+import itertools
+
+import silenttestrunner
+
+from mercurial import manifest as manifestmod
+
+HASH_1 = '1' * 40
+HASH_2 = 'f' * 40
+HASH_3 = '1234567890abcdef0987654321deadbeef0fcafe'
+A_SHORT_MANIFEST = (
+    'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
+    'foo\0%(hash1)s%(flag1)s\n'
+    ) % {'hash1': HASH_1,
+         'flag1': '',
+         'hash2': HASH_2,
+         'flag2': 'l',
+         }
+
+HUGE_MANIFEST_ENTRIES = 200001
+
+A_HUGE_MANIFEST = ''.join(sorted(
+    'file%d\0%s%s\n' % (i, h, f) for i, h, f in
+    itertools.izip(xrange(200001),
+                   itertools.cycle((HASH_1, HASH_2)),
+                   itertools.cycle(('', 'x', 'l')))))
+
+class testmanifest(unittest.TestCase):
+
+    def assertIn(self, thing, container, msg=None):
+        # assertIn new in 2.7, use it if available, otherwise polyfill
+        sup = getattr(unittest.TestCase, 'assertIn', False)
+        if sup:
+            return sup(self, thing, container, msg=msg)
+        if not msg:
+            msg = 'Expected %r in %r' % (thing, container)
+        self.assert_(thing in container, msg)
+
+    def testEmptyManifest(self):
+        m = manifestmod._lazymanifest('')
+        self.assertEqual(0, len(m))
+        self.assertEqual([], list(m.iterentries()))
+
+    def testManifest(self):
+        m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+        want = [
+            ('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
+            ('foo', binascii.unhexlify(HASH_1), ''),
+            ]
+        self.assertEqual(len(want), len(m))
+        self.assertEqual(want, list(m.iterentries()))
+        self.assertEqual((binascii.unhexlify(HASH_1), ''), m['foo'])
+        self.assertRaises(KeyError, lambda : m['wat'])
+        self.assertEqual((binascii.unhexlify(HASH_2), 'l'),
+                         m['bar/baz/qux.py'])
+
+    def testSetItem(self):
+        want = binascii.unhexlify(HASH_1), ''
+
+        m = manifestmod._lazymanifest('')
+        m['a'] = want
+        self.assertIn('a', m)
+        self.assertEqual(want, m['a'])
+        self.assertEqual('a\0' + HASH_1 + '\n', m.text())
+
+        m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+        m['a'] = want
+        self.assertEqual(want, m['a'])
+        self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
+                         m.text())
+        m2 = m.copy()
+        del m
+        del m2 # make sure we don't double free() anything
+
+    def testCompaction(self):
+        unhex = binascii.unhexlify
+        h1, h2 = unhex(HASH_1), unhex(HASH_2)
+        m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+        m['alpha'] = h1, ''
+        m['beta'] = h2, ''
+        del m['foo']
+        want = 'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
+            HASH_1, HASH_2, HASH_2)
+        self.assertEqual(want, m.text())
+        self.assertEqual(3, len(m))
+        self.assertEqual((h1, ''), m['alpha'])
+        self.assertEqual((h2, ''), m['beta'])
+        self.assertRaises(KeyError, lambda : m['foo'])
+        w = [('alpha', h1, ''), ('bar/baz/qux.py', h2, 'l'), ('beta', h2, '')]
+        self.assertEqual(w, list(m.iterentries()))
+
+    def testSetGetNodeSuffix(self):
+        clean = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+        m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+        h, f = m['foo']
+        want = h + 'a', f
+        # Merge code wants to set 21-byte fake hashes at times
+        m['foo'] = want
+        self.assertEqual(want, m['foo'])
+        self.assertEqual([('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
+                          ('foo', binascii.unhexlify(HASH_1) + 'a', '')],
+                         list(m.iterentries()))
+        # Sometimes it even tries a 22-byte fake hash, but we can
+        # return 21 and it'll work out
+        m['foo'] = want[0] + '+', f
+        self.assertEqual(want, m['foo'])
+        # make sure the suffix survives a copy
+        m2 = m.filtercopy(lambda x: x == 'foo')
+        self.assertEqual(want, m2['foo'])
+        self.assertEqual(1, len(m2))
+        self.assertEqual(('foo\0%s\n' % HASH_1), m2.text())
+        m2 = m.copy()
+        self.assertEqual(want, m2['foo'])
+        # suffix with iteration
+        self.assertEqual([('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
+                          ('foo', want[0], '')], list(m.iterentries()))
+        # shows up in diff
+        self.assertEqual({'foo': (want, (h, ''))}, m.diff(clean))
+        self.assertEqual({'foo': ((h, ''), want)}, clean.diff(m))
+
+    def testFilterCopyException(self):
+        m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+        def filt(path):
+            if path == 'foo':
+                assert False
+            return True
+        self.assertRaises(AssertionError, m.filtercopy, filt)
+
+    def testRemoveItem(self):
+        m = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+        del m['foo']
+        self.assertRaises(KeyError, lambda : m['foo'])
+        self.assertEqual(1, len(m))
+        self.assertEqual(1, len(list(m)))
+        # now restore and make sure everything works right
+        m['foo'] = 'a' * 20, ''
+        self.assertEqual(2, len(m))
+        self.assertEqual(2, len(list(m)))
+
+    def testManifestDiff(self):
+        MISSING = (None, '')
+        addl = 'z-only-in-left\0' + HASH_1 + '\n'
+        addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
+        left = manifestmod._lazymanifest(
+            A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
+        right = manifestmod._lazymanifest(A_SHORT_MANIFEST + addr)
+        want = {
+            'foo': ((binascii.unhexlify(HASH_3), 'x'),
+                    (binascii.unhexlify(HASH_1), '')),
+            'z-only-in-left': ((binascii.unhexlify(HASH_1), ''), MISSING),
+            'z-only-in-right': (MISSING, (binascii.unhexlify(HASH_2), 'x')),
+            }
+        self.assertEqual(want, left.diff(right))
+
+        want = {
+            'bar/baz/qux.py': (MISSING, (binascii.unhexlify(HASH_2), 'l')),
+            'foo': (MISSING, (binascii.unhexlify(HASH_3), 'x')),
+            'z-only-in-left': (MISSING, (binascii.unhexlify(HASH_1), '')),
+            }
+        self.assertEqual(want, manifestmod._lazymanifest('').diff(left))
+
+        want = {
+            'bar/baz/qux.py': ((binascii.unhexlify(HASH_2), 'l'), MISSING),
+            'foo': ((binascii.unhexlify(HASH_3), 'x'), MISSING),
+            'z-only-in-left': ((binascii.unhexlify(HASH_1), ''), MISSING),
+            }
+        self.assertEqual(want, left.diff(manifestmod._lazymanifest('')))
+        copy = right.copy()
+        del copy['z-only-in-right']
+        del right['foo']
+        want = {
+            'foo': (MISSING, (binascii.unhexlify(HASH_1), '')),
+            'z-only-in-right': ((binascii.unhexlify(HASH_2), 'x'), MISSING),
+            }
+        self.assertEqual(want, right.diff(copy))
+
+        short = manifestmod._lazymanifest(A_SHORT_MANIFEST)
+        pruned = short.copy()
+        del pruned['foo']
+        want = {
+            'foo': ((binascii.unhexlify(HASH_1), ''), MISSING),
+            }
+        self.assertEqual(want, short.diff(pruned))
+        want = {
+            'foo': (MISSING, (binascii.unhexlify(HASH_1), '')),
+            }
+        self.assertEqual(want, pruned.diff(short))
+        want = {
+            'bar/baz/qux.py': None,
+            'foo': (MISSING, (binascii.unhexlify(HASH_1), '')),
+            }
+        self.assertEqual(want, pruned.diff(short, True))
+
+    def testReversedLines(self):
+        backwards = ''.join(
+            l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
+        try:
+            manifestmod._lazymanifest(backwards)
+            self.fail('Should have raised ValueError')
+        except ValueError, v:
+            self.assertIn('Manifest lines not in sorted order.', str(v))
+
+    def testNoTerminalNewline(self):
+        try:
+            manifestmod._lazymanifest(A_SHORT_MANIFEST + 'wat')
+            self.fail('Should have raised ValueError')
+        except ValueError, v:
+            self.assertIn('Manifest did not end in a newline.', str(v))
+
+    def testNoNewLineAtAll(self):
+        try:
+            manifestmod._lazymanifest('wat')
+            self.fail('Should have raised ValueError')
+        except ValueError, v:
+            self.assertIn('Manifest did not end in a newline.', str(v))
+
+    def testHugeManifest(self):
+        m = manifestmod._lazymanifest(A_HUGE_MANIFEST)
+        self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
+        self.assertEqual(len(m), len(list(m)))
+
+    def testIntersectFiles(self):
+        m = manifestmod.manifestdict(A_HUGE_MANIFEST)
+        m2 = m.intersectfiles(['file1', 'file200', 'file300'])
+        w = ('file1\0%sx\n'
+             'file200\0%sl\n'
+             'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
+        self.assertEqual(w, m2.text())
+
+if __name__ == '__main__':
+    silenttestrunner.main(__name__)
--- a/tests/test-merge-tools.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-merge-tools.t	Fri Mar 13 17:55:04 2015 -0500
@@ -603,7 +603,8 @@
   true.priority=1
   true.executable=cat
   # hg update -C 1
-  $ hg debugsetparent 0
+  $ hg update -q 0
+  $ hg revert -q -r 1 .
   $ hg update -r 2
   merging f
   revision 1
@@ -628,7 +629,8 @@
   true.priority=1
   true.executable=cat
   # hg update -C 1
-  $ hg debugsetparent 0
+  $ hg update -q 0
+  $ hg revert -q -r 1 .
   $ hg update -r 2 --tool false
   merging f
   merging f failed!
--- a/tests/test-mq-subrepo.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-mq-subrepo.t	Fri Mar 13 17:55:04 2015 -0500
@@ -295,6 +295,11 @@
   new file mode 100644
   examine changes to '.hgsub'? [Ynesfdaq?] y
   
+  @@ -0,0 +1,1 @@
+  +sub = sub
+  record this change to '.hgsub'? [Ynesfdaq?] y
+  
+  warning: subrepo spec file .hgsub not found
   abort: uncommitted changes in subrepository sub
   [255]
   % update substate when adding .hgsub w/clean updated subrepo
@@ -304,10 +309,14 @@
   new file mode 100644
   examine changes to '.hgsub'? [Ynesfdaq?] y
   
+  @@ -0,0 +1,1 @@
+  +sub = sub
+  record this change to '.hgsub'? [Ynesfdaq?] y
+  
+  warning: subrepo spec file .hgsub not found
   path sub
    source   sub
    revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
-
   $ testmod qrecord --config ui.interactive=1 -m1 1.diff <<EOF
   > y
   > y
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete-tag-cache.t	Fri Mar 13 17:55:04 2015 -0500
@@ -0,0 +1,70 @@
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > rebase=
+  > 
+  > [experimental]
+  > evolution = createmarkers
+  > EOF
+
+Create a repo with some tags
+
+  $ hg init repo
+  $ cd repo
+  $ echo initial > foo
+  $ hg -q commit -A -m initial
+  $ hg tag -m 'test tag' test1
+  $ echo first > first
+  $ hg -q commit -A -m first
+  $ hg tag -m 'test2 tag' test2
+  $ hg -q up -r 0
+  $ echo newhead > newhead
+  $ hg commit -A -m newhead
+  adding newhead
+  created new head
+
+Trigger tags cache population by doing something that accesses tags info
+
+  $ hg log -G -T '{rev}:{node|short} {tags} {desc}\n'
+  @  4:042eb6bfcc49 tip newhead
+  |
+  | o  3:c3cb30f2d2cd  test2 tag
+  | |
+  | o  2:d75775ffbc6b test2 first
+  | |
+  | o  1:5f97d42da03f  test tag
+  |/
+  o  0:55482a6fb4b1 test1 initial
+  
+
+  $ cat .hg/cache/tags
+  4 042eb6bfcc4909bad84a1cbf6eb1ddf0ab587d41
+  3 c3cb30f2d2cd0aae008cc91a07876e3c5131fd22 b3bce87817fe7ac9dca2834366c1d7534c095cf1
+  
+  55482a6fb4b1881fa8f746fd52cf6f096bb21c89 test1
+  d75775ffbc6bca1794d300f5571272879bd280da test2
+
+Create some hidden changesets via a rebase and trigger tags cache
+repopulation
+
+  $ hg -q rebase -s 1 -d 4
+  $ hg log -G -T '{rev}:{node|short} {tags} {desc}\n'
+  o  7:eb610439e10e tip test2 tag
+  |
+  o  6:7b4af00c3c83  first
+  |
+  o  5:43ac2a539b3c  test tag
+  |
+  @  4:042eb6bfcc49  newhead
+  |
+  o  0:55482a6fb4b1 test1 initial
+  
+
+.hgtags filenodes for hidden heads should be visible (issue4550)
+(currently broken)
+
+  $ cat .hg/cache/tags
+  7 eb610439e10e0c6b296f97b59624c2e24fc59e30 b3bce87817fe7ac9dca2834366c1d7534c095cf1
+  
+  55482a6fb4b1881fa8f746fd52cf6f096bb21c89 test1
+  d75775ffbc6bca1794d300f5571272879bd280da test2
+
--- a/tests/test-obsolete.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-obsolete.t	Fri Mar 13 17:55:04 2015 -0500
@@ -11,7 +11,7 @@
   >    hg ci -m "add $1"
   > }
   $ getid() {
-  >    hg id --debug --hidden -ir "desc('$1')"
+  >    hg log -T "{node}\n" --hidden -r "desc('$1')"
   > }
 
   $ cat > debugkeys.py <<EOF
@@ -187,6 +187,8 @@
   [255]
   $ hg debugrevspec 'rev(6)'
   $ hg debugrevspec 'rev(4)'
+  $ hg debugrevspec 'null'
+  -1
 
 Check that public changeset are not accounted as obsolete:
 
@@ -621,7 +623,7 @@
 
 check filelog view
 
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'log/'`hg id --debug --id`/'babar'
+  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'log/'`hg log -r . -T "{node}"`/'babar'
   200 Script output follows
 
   $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/68'
@@ -753,3 +755,43 @@
   $ hg tags
   visible                            0:193e9254ce7e
   tip                                0:193e9254ce7e
+
+#if serve
+
+Test issue 4506
+
+  $ cd ..
+  $ hg init repo-issue4506
+  $ cd repo-issue4506
+  $ echo "0" > foo
+  $ hg add foo
+  $ hg ci -m "content-0"
+
+  $ hg up null
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo "1" > bar
+  $ hg add bar
+  $ hg ci -m "content-1"
+  created new head
+  $ hg up 0
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg graft 1
+  grafting 1:1c9eddb02162 "content-1" (tip)
+
+  $ hg debugobsolete `hg log -r1 -T'{node}'` `hg log -r2 -T'{node}'`
+
+  $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
+  $ cat hg.pid >> $DAEMON_PIDS
+
+  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/1'
+  404 Not Found
+  [1]
+  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'file/tip/bar'
+  200 Script output follows
+  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'annotate/tip/bar'
+  200 Script output follows
+
+  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+
+#endif
+
--- a/tests/test-pull.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-pull.t	Fri Mar 13 17:55:04 2015 -0500
@@ -76,7 +76,7 @@
   abort: file:// URLs can only refer to localhost
   [255]
 
-  $ hg pull -q file:../test
+  $ hg pull -q file:../test  # no-msys
 
 It's tricky to make file:// URLs working on every platform with
 regular shell commands.
--- a/tests/test-rebase-named-branches.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-rebase-named-branches.t	Fri Mar 13 17:55:04 2015 -0500
@@ -352,7 +352,7 @@
   $ hg ci -qm 'c2 closed' --close
   $ hg up -qr 2
   $ hg tglog
-  o  4: 'c2 closed' c
+  _  4: 'c2 closed' c
   |
   o  3: 'b1' b
   |
@@ -366,7 +366,7 @@
   nothing to rebase - working directory parent is also destination
   [1]
   $ hg tglog
-  o  4: 'c2 closed' c
+  _  4: 'c2 closed' c
   |
   o  3: 'b1' b
   |
--- a/tests/test-rebase-pull.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-rebase-pull.t	Fri Mar 13 17:55:04 2015 -0500
@@ -165,5 +165,47 @@
   |
   o  0: 'C1'
   
+pull --rebase works with bundle2 turned on
 
-
+  $ cd ../a
+  $ echo R4 > R4
+  $ hg ci -Am R4
+  adding R4
+  $ hg tglog
+  @  5: 'R4'
+  |
+  o  4: 'R3'
+  |
+  o  3: 'R2'
+  |
+  o  2: 'R1'
+  |
+  o  1: 'C2'
+  |
+  o  0: 'C1'
+  
+  $ cd ../c
+  $ hg pull --rebase --config experimental.bundle2-exp=True --config experimental.strip-bundle2-version=02
+  pulling from $TESTTMP/a (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  rebasing 5:518d153c0ba3 "L1"
+  saved backup bundle to $TESTTMP/c/.hg/strip-backup/518d153c0ba3-73407f14-backup.hg (glob)
+  $ hg tglog
+  @  6: 'L1'
+  |
+  o  5: 'R4'
+  |
+  o  4: 'R3'
+  |
+  o  3: 'R2'
+  |
+  o  2: 'R1'
+  |
+  o  1: 'C2'
+  |
+  o  0: 'C1'
+  
--- a/tests/test-record.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-record.t	Fri Mar 13 17:55:04 2015 -0500
@@ -235,7 +235,19 @@
   new file mode 100644
   examine changes to 'plain'? [Ynesfdaq?] y
   
-
+  @@ -0,0 +1,10 @@
+  +1
+  +2
+  +3
+  +4
+  +5
+  +6
+  +7
+  +8
+  +9
+  +10
+  record this change to 'plain'? [Ynesfdaq?] y
+  
   $ hg tip -p
   changeset:   7:11fb457c1be4
   tag:         tip
@@ -317,6 +329,7 @@
   > y
   > y
   > y
+  > y
   > EOF
   diff --git a/plain b/plain
   1 hunks, 1 lines changed
@@ -335,7 +348,10 @@
   new file mode 100644
   examine changes to 'plain2'? [Ynesfdaq?] y
   
-
+  @@ -0,0 +1,1 @@
+  +1
+  record change 2/2 to 'plain2'? [Ynesfdaq?] y
+  
 Modify beginning, trim end, record both, add another file to test
 changes numbering
 
@@ -1353,6 +1369,8 @@
    c
   +d
   
+
+
 Test --user when ui.username not set
   $ unset HGUSER
   $ echo e >> subdir/f1
@@ -1376,4 +1394,41 @@
   $ HGUSER="test"
   $ export HGUSER
 
+
+Editing patch of newly added file
+
+  $ cat > editor.sh << '__EOF__'
+  > cat "$1"  | sed "s/first/very/g"  > tt
+  > mv tt  "$1"
+  > __EOF__
+  $ cat > newfile << '__EOF__'
+  > This is the first line
+  > This is the second line
+  > This is the third line
+  > __EOF__
+  $ hg add newfile
+  $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg record -d '23 0' -medit-patch-new <<EOF
+  > y
+  > e
+  > EOF
+  diff --git a/newfile b/newfile
+  new file mode 100644
+  examine changes to 'newfile'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,3 @@
+  +This is the first line
+  +This is the second line
+  +This is the third line
+  record this change to 'newfile'? [Ynesfdaq?] e
+  
+  $ hg cat -r tip newfile
+  This is the very line
+  This is the second line
+  This is the third line
+
+  $ cat newfile
+  This is the first line
+  This is the second line
+  This is the third line
+
   $ cd ..
--- a/tests/test-rename.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-rename.t	Fri Mar 13 17:55:04 2015 -0500
@@ -620,10 +620,16 @@
   $ hg rename d1/d11/a1 .hg
   abort: path contains illegal component: .hg/a1 (glob)
   [255]
+  $ hg --config extensions.largefiles= rename d1/d11/a1 .hg
+  abort: path contains illegal component: .hg/a1 (glob)
+  [255]
   $ hg status -C
   $ hg rename d1/d11/a1 ..
   abort: ../a1 not under root '$TESTTMP' (glob)
   [255]
+  $ hg --config extensions.largefiles= rename d1/d11/a1 ..
+  abort: ../a1 not under root '$TESTTMP' (glob)
+  [255]
   $ hg status -C
 
   $ mv d1/d11/a1 .hg
--- a/tests/test-resolve.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-resolve.t	Fri Mar 13 17:55:04 2015 -0500
@@ -43,10 +43,15 @@
   U file1
   U file2
 
-resolving an unknown path should emit a warning
+  $ hg resolve -l --no-status
+  file1
+  file2
+
+resolving an unknown path should emit a warning, but not for -l
 
   $ hg resolve -m does-not-exist
   arguments do not match paths that need resolving
+  $ hg resolve -l does-not-exist
 
 resolve the failure
 
@@ -59,6 +64,18 @@
   R file1
   U file2
 
+  $ hg resolve -l -Tjson
+  [
+   {
+    "path": "file1",
+    "status": "R"
+   },
+   {
+    "path": "file2",
+    "status": "U"
+   }
+  ]
+
 resolve -m without paths should mark all resolved
 
   $ hg resolve -m
@@ -69,6 +86,10 @@
 
   $ hg resolve -l
 
+  $ hg resolve -l -Tjson
+  [
+  ]
+
 resolve --all should abort when no merge in progress
 
   $ hg resolve --all
--- a/tests/test-revset.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-revset.t	Fri Mar 13 17:55:04 2015 -0500
@@ -478,8 +478,36 @@
   [255]
 
 Test null revision
+  $ log '(null)'
+  -1
+  $ log '(null:0)'
+  -1
+  0
+  $ log '(0:null)'
+  0
+  -1
+  $ log 'null::0'
+  -1
+  0
+  $ log 'null:tip - 0:'
+  -1
+  $ log 'null: and null::' | head -1
+  -1
+  $ log 'null: or 0:' | head -2
+  -1
+  0
   $ log 'ancestors(null)'
   -1
+  $ log 'reverse(null:)' | tail -2
+  0
+  -1
+  $ log 'first(null:)'
+  -1
+  $ log 'min(null:)'
+  -1
+  $ log 'tip:null and all()' | tail -2
+  1
+  0
 
   $ log 'outgoing()'
   8
@@ -860,6 +888,23 @@
   hg: parse error: ^ expects a number 0, 1, or 2
   [255]
 
+Bogus function gets suggestions
+  $ log 'add()'
+  hg: parse error: unknown identifier: add
+  (did you mean 'adds'?)
+  [255]
+  $ log 'added()'
+  hg: parse error: unknown identifier: added
+  (did you mean 'adds'?)
+  [255]
+  $ log 'remo()'
+  hg: parse error: unknown identifier: remo
+  (did you mean one of remote, removes?)
+  [255]
+  $ log 'babar()'
+  hg: parse error: unknown identifier: babar
+  [255]
+
 multiple revspecs
 
   $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
@@ -1011,12 +1056,12 @@
     (range
       ('symbol', '2')
       ('symbol', '5')))
-  abort: failed to parse the definition of revset alias "injectparamasstring2": not a function: _aliasarg
+  abort: failed to parse the definition of revset alias "injectparamasstring2": unknown identifier: _aliasarg
   [255]
   $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
   ('symbol', 'tip')
   warning: failed to parse the definition of revset alias "anotherbadone": at 7: not a prefix: end
-  warning: failed to parse the definition of revset alias "injectparamasstring2": not a function: _aliasarg
+  warning: failed to parse the definition of revset alias "injectparamasstring2": unknown identifier: _aliasarg
   9
   >>> data = file('.hg/hgrc', 'rb').read()
   >>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
@@ -1029,6 +1074,19 @@
   ('symbol', 'tip')
   warning: failed to parse the declaration of revset alias "bad name": at 4: invalid token
   9
+  $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
+  $ try 'strictreplacing("foo", tip)'
+  (func
+    ('symbol', 'strictreplacing')
+    (list
+      ('string', 'foo')
+      ('symbol', 'tip')))
+  (or
+    ('symbol', 'tip')
+    (func
+      ('symbol', 'desc')
+      ('string', '$1')))
+  9
 
   $ try 'd(2:5)'
   (func
@@ -1114,6 +1172,62 @@
   3
   2
 
+issue4553: check that revset aliases override existing hash prefix
+
+  $ hg log -qr e
+  6:e0cc66ef77e8
+
+  $ hg log -qr e --config revsetalias.e="all()"
+  0:2785f51eece5
+  1:d75937da8da0
+  2:5ed5505e9f1c
+  3:8528aa5637f2
+  4:2326846efdab
+  5:904fa392b941
+  6:e0cc66ef77e8
+  7:013af1973af4
+  8:d5d0dcbdc4d9
+  9:24286f4ae135
+
+  $ hg log -qr e: --config revsetalias.e="0"
+  0:2785f51eece5
+  1:d75937da8da0
+  2:5ed5505e9f1c
+  3:8528aa5637f2
+  4:2326846efdab
+  5:904fa392b941
+  6:e0cc66ef77e8
+  7:013af1973af4
+  8:d5d0dcbdc4d9
+  9:24286f4ae135
+
+  $ hg log -qr :e --config revsetalias.e="9"
+  0:2785f51eece5
+  1:d75937da8da0
+  2:5ed5505e9f1c
+  3:8528aa5637f2
+  4:2326846efdab
+  5:904fa392b941
+  6:e0cc66ef77e8
+  7:013af1973af4
+  8:d5d0dcbdc4d9
+  9:24286f4ae135
+
+  $ hg log -qr e:
+  6:e0cc66ef77e8
+  7:013af1973af4
+  8:d5d0dcbdc4d9
+  9:24286f4ae135
+
+  $ hg log -qr :e
+  0:2785f51eece5
+  1:d75937da8da0
+  2:5ed5505e9f1c
+  3:8528aa5637f2
+  4:2326846efdab
+  5:904fa392b941
+  6:e0cc66ef77e8
+
 issue2549 - correct optimizations
 
   $ log 'limit(1 or 2 or 3, 2) and not 2'
--- a/tests/test-shelve.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-shelve.t	Fri Mar 13 17:55:04 2015 -0500
@@ -81,11 +81,11 @@
 ensure that our shelved changes exist
 
   $ hg shelve -l
-  default-01      (*)    changes to '[mq]: second.patch' (glob)
-  default         (*)    changes to '[mq]: second.patch' (glob)
+  default-01      (*)* changes to '[mq]: second.patch' (glob)
+  default         (*)* changes to '[mq]: second.patch' (glob)
 
   $ hg shelve -l -p default
-  default         (*)    changes to '[mq]: second.patch' (glob)
+  default         (*)* changes to '[mq]: second.patch' (glob)
   
   diff --git a/a/a b/a/a
   --- a/a/a
--- a/tests/test-ssh.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-ssh.t	Fri Mar 13 17:55:04 2015 -0500
@@ -116,6 +116,14 @@
   searching for changes
   no changes found
 
+pull from wrong ssh URL
+
+  $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
+  pulling from ssh://user@dummy/doesnotexist
+  remote: abort: there is no Mercurial repository here (.hg not found)!
+  abort: no suitable response from remote hg!
+  [255]
+
 local change
 
   $ echo bleah > foo
@@ -446,6 +454,7 @@
   Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R local serve --stdio
   Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
--- a/tests/test-status-color.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-status-color.t	Fri Mar 13 17:55:04 2015 -0500
@@ -338,8 +338,8 @@
 hg resolve with one unresolved, one resolved:
 
   $ hg resolve --color=always -l
-  \x1b[0;31;1mU a\x1b[0m (esc)
-  \x1b[0;32;1mR b\x1b[0m (esc)
+  \x1b[0;31;1mU \x1b[0m\x1b[0;31;1ma\x1b[0m (esc)
+  \x1b[0;32;1mR \x1b[0m\x1b[0;32;1mb\x1b[0m (esc)
 
 color coding of error message with current availability of curses
 
--- a/tests/test-strip.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-strip.t	Fri Mar 13 17:55:04 2015 -0500
@@ -220,8 +220,69 @@
   Stream params: {}
   b2x:changegroup -- "{'version': '02'}"
       264128213d290d868c54642d13aeaa3675551a78
+  $ hg incoming .hg/strip-backup/*
+  comparing with .hg/strip-backup/264128213d29-0b39d6bf-backup.hg
+  searching for changes
+  changeset:   4:264128213d29
+  tag:         tip
+  parent:      1:ef3a871183d7
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     c
+  
   $ restore
-
+  $ hg up -C 4
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg --config experimental.bundle2-exp=True --config experimental.strip-bundle2-version=02 --traceback strip 4
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/test/.hg/strip-backup/264128213d29-0b39d6bf-backup.hg (glob)
+  $ hg parents
+  changeset:   1:ef3a871183d7
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     b
+  
+  $ hg debugbundle .hg/strip-backup/*
+  Stream params: {}
+  b2x:changegroup -- "{'version': '02'}"
+      264128213d290d868c54642d13aeaa3675551a78
+  $ hg pull .hg/strip-backup/*
+  pulling from .hg/strip-backup/264128213d29-0b39d6bf-backup.hg
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files (+1 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ rm .hg/strip-backup/*
+  $ hg log --graph
+  o  changeset:   4:264128213d29
+  |  tag:         tip
+  |  parent:      1:ef3a871183d7
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     c
+  |
+  | o  changeset:   3:443431ffac4f
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     e
+  | |
+  | o  changeset:   2:65bd5f99a4a3
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     d
+  |
+  @  changeset:   1:ef3a871183d7
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     b
+  |
+  o  changeset:   0:9ab35a2d17cb
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     a
+  
   $ hg up -C 2
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg merge 4
--- a/tests/test-subrepo-deep-nested-change.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-subrepo-deep-nested-change.t	Fri Mar 13 17:55:04 2015 -0500
@@ -46,12 +46,29 @@
 
 Clone main
 
-  $ hg clone main cloned
+  $ hg --config extensions.largefiles= clone main cloned
   updating to branch default
   cloning subrepo sub1 from $TESTTMP/sub1
   cloning subrepo sub1/sub2 from $TESTTMP/sub2 (glob)
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
+Largefiles is NOT enabled in the clone if the source repo doesn't require it
+  $ cat cloned/.hg/hgrc
+  # example repository config (see "hg help config" for more info)
+  [paths]
+  default = $TESTTMP/main (glob)
+  
+  # path aliases to other clones of this repo in URLs or filesystem paths
+  # (see "hg help config.paths" for more info)
+  #
+  # default-push = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-fork      = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-clone     = /home/jdoe/jdoes-clone
+  
+  [ui]
+  # name and email (local to this repository, optional), e.g.
+  # username = Jane Doe <jdoe@example.com>
+
 Checking cloned repo ids
 
   $ printf "cloned " ; hg id -R cloned
@@ -319,6 +336,31 @@
   ../archive_lf/sub1/sub2/large.bin
   $ rm -rf ../archive_lf
 
+The local repo enables largefiles if a largefiles repo is cloned
+  $ hg showconfig extensions
+  abort: repository requires features unknown to this Mercurial: largefiles!
+  (see http://mercurial.selenic.com/wiki/MissingRequirement for more information)
+  [255]
+  $ hg --config extensions.largefiles= clone -qU . ../lfclone
+  $ cat ../lfclone/.hg/hgrc
+  # example repository config (see "hg help config" for more info)
+  [paths]
+  default = $TESTTMP/cloned (glob)
+  
+  # path aliases to other clones of this repo in URLs or filesystem paths
+  # (see "hg help config.paths" for more info)
+  #
+  # default-push = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-fork      = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-clone     = /home/jdoe/jdoes-clone
+  
+  [ui]
+  # name and email (local to this repository, optional), e.g.
+  # username = Jane Doe <jdoe@example.com>
+  
+  [extensions]
+  largefiles=
+
 Find an exact match to a standin (should archive nothing)
   $ hg --config extensions.largefiles= archive -S -I 'sub/sub2/.hglf/large.bin' ../archive_lf
   $ find ../archive_lf 2> /dev/null | sort
@@ -377,4 +419,24 @@
   A a.dat
   A a.txt
 
+  $ hg ci -m "add a.*"
+  $ hg mv a.dat b.dat
+  $ hg mv foo/bar/abc foo/bar/def
+  $ hg status -C
+  A b.dat
+    a.dat
+  A foo/bar/def
+    foo/bar/abc
+  R a.dat
+  R foo/bar/abc
+
+  $ hg ci -m "move large and normal"
+  $ hg status -C --rev '.^' --rev .
+  A b.dat
+    a.dat
+  A foo/bar/def
+    foo/bar/abc
+  R a.dat
+  R foo/bar/abc
+
   $ cd ..
--- a/tests/test-subrepo-git.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-subrepo-git.t	Fri Mar 13 17:55:04 2015 -0500
@@ -134,6 +134,7 @@
   $ hg status --subrepos
   ? s/f
   $ hg add .
+  adding f
   $ git add f
   $ cd ..
 
@@ -174,6 +175,8 @@
   pulling subrepo s from $TESTTMP/gitroot
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
+  $ hg st --subrepos s
+  A s/f
   $ cat s/f
   f
   $ cat s/g
@@ -802,4 +805,235 @@
   $ hg status --subrepos
   ? s/barfoo
 
+show file at specific revision
+  $ cat > s/foobar << EOF
+  > woop    woop
+  > fooo bar
+  > EOF
+  $ hg commit --subrepos -m "updated foobar"
+  committing subrepository s
+  $ cat > s/foobar << EOF
+  > current foobar
+  > (should not be visible using hg cat)
+  > EOF
+
+  $ hg cat -r . s/foobar
+  woop    woop
+  fooo bar (no-eol)
+  $ hg cat -r "parents(.)" s/foobar > catparents
+
+  $ mkdir -p tmp/s
+
+  $ hg cat -r "parents(.)" --output tmp/%% s/foobar
+  $ diff tmp/% catparents
+
+  $ hg cat -r "parents(.)" --output tmp/%s s/foobar
+  $ diff tmp/foobar catparents
+
+  $ hg cat -r "parents(.)" --output tmp/%d/otherfoobar s/foobar
+  $ diff tmp/s/otherfoobar catparents
+
+  $ hg cat -r "parents(.)" --output tmp/%p s/foobar
+  $ diff tmp/s/foobar catparents
+
+  $ hg cat -r "parents(.)" --output tmp/%H s/foobar
+  $ diff tmp/255ee8cf690ec86e99b1e80147ea93ece117cd9d catparents
+
+  $ hg cat -r "parents(.)" --output tmp/%R s/foobar
+  $ diff tmp/10 catparents
+
+  $ hg cat -r "parents(.)" --output tmp/%h s/foobar
+  $ diff tmp/255ee8cf690e catparents
+
+  $ rm tmp/10
+  $ hg cat -r "parents(.)" --output tmp/%r s/foobar
+  $ diff tmp/10 catparents
+
+  $ mkdir tmp/tc
+  $ hg cat -r "parents(.)" --output tmp/%b/foobar s/foobar
+  $ diff tmp/tc/foobar catparents
+
+cleanup
+  $ rm -r tmp
+  $ rm catparents
+
+add git files, using either files or patterns
+  $ echo "hsss! hsssssssh!" > s/snake.python
+  $ echo "ccc" > s/c.c
+  $ echo "cpp" > s/cpp.cpp
+
+  $ hg add s/snake.python s/c.c s/cpp.cpp
+  $ hg st --subrepos s
+  M s/foobar
+  A s/c.c
+  A s/cpp.cpp
+  A s/snake.python
+  ? s/barfoo
+  $ hg revert s
+  reverting subrepo ../gitroot
+
+  $ hg add --subrepos "glob:**.python"
+  adding s/snake.python (glob)
+  $ hg st --subrepos s
+  A s/snake.python
+  ? s/barfoo
+  ? s/c.c
+  ? s/cpp.cpp
+  ? s/foobar.orig
+  $ hg revert s
+  reverting subrepo ../gitroot
+
+  $ hg add --subrepos s
+  adding s/barfoo (glob)
+  adding s/c.c (glob)
+  adding s/cpp.cpp (glob)
+  adding s/foobar.orig (glob)
+  adding s/snake.python (glob)
+  $ hg st --subrepos s
+  A s/barfoo
+  A s/c.c
+  A s/cpp.cpp
+  A s/foobar.orig
+  A s/snake.python
+  $ hg revert s
+  reverting subrepo ../gitroot
+make sure everything is reverted correctly
+  $ hg st --subrepos s
+  ? s/barfoo
+  ? s/c.c
+  ? s/cpp.cpp
+  ? s/foobar.orig
+  ? s/snake.python
+
+  $ hg add --subrepos --exclude "path:s/c.c"
+  adding s/barfoo (glob)
+  adding s/cpp.cpp (glob)
+  adding s/foobar.orig (glob)
+  adding s/snake.python (glob)
+  $ hg st --subrepos s
+  A s/barfoo
+  A s/cpp.cpp
+  A s/foobar.orig
+  A s/snake.python
+  ? s/c.c
+  $ hg revert --all -q
+
+.hgignore should not have influence in subrepos
+  $ cat > .hgignore << EOF
+  > syntax: glob
+  > *.python
+  > EOF
+  $ hg add .hgignore
+  $ hg add --subrepos "glob:**.python" s/barfoo
+  adding s/snake.python (glob)
+  $ hg st --subrepos s
+  A s/barfoo
+  A s/snake.python
+  ? s/c.c
+  ? s/cpp.cpp
+  ? s/foobar.orig
+  $ hg revert --all -q
+
+.gitignore should have influence,
+except for explicitly added files (no patterns)
+  $ cat > s/.gitignore << EOF
+  > *.python
+  > EOF
+  $ hg add s/.gitignore
+  $ hg st --subrepos s
+  A s/.gitignore
+  ? s/barfoo
+  ? s/c.c
+  ? s/cpp.cpp
+  ? s/foobar.orig
+  $ hg st --subrepos s --all
+  A s/.gitignore
+  ? s/barfoo
+  ? s/c.c
+  ? s/cpp.cpp
+  ? s/foobar.orig
+  I s/snake.python
+  C s/f
+  C s/foobar
+  C s/g
+  $ hg add --subrepos "glob:**.python"
+  $ hg st --subrepos s
+  A s/.gitignore
+  ? s/barfoo
+  ? s/c.c
+  ? s/cpp.cpp
+  ? s/foobar.orig
+  $ hg add --subrepos s/snake.python
+  $ hg st --subrepos s
+  A s/.gitignore
+  A s/snake.python
+  ? s/barfoo
+  ? s/c.c
+  ? s/cpp.cpp
+  ? s/foobar.orig
+
+correctly do a dry run
+  $ hg add --subrepos s --dry-run
+  adding s/barfoo (glob)
+  adding s/c.c (glob)
+  adding s/cpp.cpp (glob)
+  adding s/foobar.orig (glob)
+  $ hg st --subrepos s
+  A s/.gitignore
+  A s/snake.python
+  ? s/barfoo
+  ? s/c.c
+  ? s/cpp.cpp
+  ? s/foobar.orig
+
+error given when adding an already tracked file
+  $ hg add s/.gitignore
+  s/.gitignore already tracked!
+  [1]
+  $ hg add s/g
+  s/g already tracked!
+  [1]
+
+removed files can be re-added
+removing files using 'rm' or 'git rm' has the same effect,
+since we ignore the staging area
+  $ hg ci --subrepos -m 'snake'
+  committing subrepository s
+  $ cd s
+  $ rm snake.python
+(remove leftover .hg so Mercurial doesn't look for a root here)
+  $ rm -r .hg
+  $ hg status --subrepos --all .
+  R snake.python
+  ? barfoo
+  ? c.c
+  ? cpp.cpp
+  ? foobar.orig
+  C .gitignore
+  C f
+  C foobar
+  C g
+  $ git rm snake.python
+  rm 'snake.python'
+  $ hg status --subrepos --all .
+  R snake.python
+  ? barfoo
+  ? c.c
+  ? cpp.cpp
+  ? foobar.orig
+  C .gitignore
+  C f
+  C foobar
+  C g
+  $ touch snake.python
   $ cd ..
+  $ hg add s/snake.python
+  $ hg status -S
+  M s/snake.python
+  ? .hgignore
+  ? s/barfoo
+  ? s/c.c
+  ? s/cpp.cpp
+  ? s/foobar.orig
+
+  $ cd ..
--- a/tests/test-subrepo.t	Fri Mar 13 21:18:59 2015 +0900
+++ b/tests/test-subrepo.t	Fri Mar 13 17:55:04 2015 -0500
@@ -50,9 +50,16 @@
 Revert subrepo and test subrepo fileset keyword:
 
   $ echo b > s/a
+  $ hg revert --dry-run "set:subrepo('glob:s*')"
+  reverting subrepo s
+  reverting s/a (glob)
+  $ cat s/a
+  b
   $ hg revert "set:subrepo('glob:s*')"
   reverting subrepo s
   reverting s/a (glob)
+  $ cat s/a
+  a
   $ rm s/a.orig
 
 Revert subrepo with no backup. The "reverting s/a" line is gone since