changeset 17064:168cc52ad7c2

histedit: new extension for interactive history editing
author Augie Fackler <raf@durin42.com>
date Wed, 27 Jun 2012 17:52:54 -0500
parents 3fbc6e3abdbd
children 949e241b5573
files hgext/histedit.py tests/histedit-helpers.sh tests/missing-comment.hg tests/test-histedit-bookmark-motion.t tests/test-histedit-commute tests/test-histedit-commute.out tests/test-histedit-drop tests/test-histedit-drop.out tests/test-histedit-edit tests/test-histedit-edit.out tests/test-histedit-fold tests/test-histedit-fold-non-commute tests/test-histedit-fold-non-commute.out tests/test-histedit-fold.out tests/test-histedit-no-change tests/test-histedit-no-change.out tests/test-histedit-non-commute tests/test-histedit-non-commute-abort tests/test-histedit-non-commute-abort.out tests/test-histedit-non-commute.out tests/test-histedit-outgoing tests/test-histedit-outgoing.out tests/test-histedit-revspec.t
diffstat 23 files changed, 2470 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/histedit.py	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,564 @@
+# histedit.py - interactive history editing for mercurial
+#
+# Copyright 2009 Augie Fackler <raf@durin42.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""Interactive history editing.
+
+Inspired by git rebase --interactive.
+"""
+from inspect import getargspec
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+import tempfile
+import os
+
+from mercurial import bookmarks
+from mercurial import cmdutil
+from mercurial import discovery
+from mercurial import error
+from mercurial import hg
+from mercurial import node
+from mercurial import patch
+from mercurial import repair
+from mercurial import scmutil
+from mercurial import url
+from mercurial import util
+from mercurial.i18n import _
+
+
+editcomment = """
+
+# Edit history between %s and %s
+#
+# Commands:
+#  p, pick = use commit
+#  e, edit = use commit, but stop for amending
+#  f, fold = use commit, but fold into previous commit (combines N and N-1)
+#  d, drop = remove commit from history
+#  m, mess = edit message without changing commit content
+#
+"""
+
+def between(repo, old, new, keep):
+    revs = [old, ]
+    current = old
+    while current != new:
+        ctx = repo[current]
+        if not keep and len(ctx.children()) > 1:
+            raise util.Abort(_('cannot edit history that would orphan nodes'))
+        if len(ctx.parents()) != 1 and ctx.parents()[1] != node.nullid:
+            raise util.Abort(_("can't edit history with merges"))
+        if not ctx.children():
+            current = new
+        else:
+            current = ctx.children()[0].node()
+            revs.append(current)
+    if len(repo[current].children()) and not keep:
+        raise util.Abort(_('cannot edit history that would orphan nodes'))
+    return revs
+
+
+def pick(ui, repo, ctx, ha, opts):
+    oldctx = repo[ha]
+    if oldctx.parents()[0] == ctx:
+        ui.debug('node %s unchanged\n' % ha)
+        return oldctx, [], [], []
+    hg.update(repo, ctx.node())
+    fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
+    fp = os.fdopen(fd, 'w')
+    diffopts = patch.diffopts(ui, opts)
+    diffopts.git = True
+    diffopts.ignorews = False
+    diffopts.ignorewsamount = False
+    diffopts.ignoreblanklines = False
+    gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
+    for chunk in gen:
+        fp.write(chunk)
+    fp.close()
+    try:
+        files = set()
+        try:
+            patch.patch(ui, repo, patchfile, files=files, eolmode=None)
+            if not files:
+                ui.warn(_('%s: empty changeset')
+                             % node.hex(ha))
+                return ctx, [], [], []
+        finally:
+            os.unlink(patchfile)
+    except Exception, inst:
+        raise util.Abort(_('Fix up the change and run '
+                           'hg histedit --continue'))
+    n = repo.commit(text=oldctx.description(), user=oldctx.user(), date=oldctx.date(),
+                    extra=oldctx.extra())
+    return repo[n], [n, ], [oldctx.node(), ], []
+
+
+def edit(ui, repo, ctx, ha, opts):
+    oldctx = repo[ha]
+    hg.update(repo, ctx.node())
+    fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
+    fp = os.fdopen(fd, 'w')
+    diffopts = patch.diffopts(ui, opts)
+    diffopts.git = True
+    diffopts.ignorews = False
+    diffopts.ignorewsamount = False
+    diffopts.ignoreblanklines = False
+    gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
+    for chunk in gen:
+        fp.write(chunk)
+    fp.close()
+    try:
+        files = set()
+        try:
+            patch.patch(ui, repo, patchfile, files=files, eolmode=None)
+        finally:
+            os.unlink(patchfile)
+    except Exception, inst:
+        pass
+    raise util.Abort(_('Make changes as needed, you may commit or record as '
+                       'needed now.\nWhen you are finished, run hg'
+                       ' histedit --continue to resume.'))
+
+def fold(ui, repo, ctx, ha, opts):
+    oldctx = repo[ha]
+    hg.update(repo, ctx.node())
+    fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
+    fp = os.fdopen(fd, 'w')
+    diffopts = patch.diffopts(ui, opts)
+    diffopts.git = True
+    diffopts.ignorews = False
+    diffopts.ignorewsamount = False
+    diffopts.ignoreblanklines = False
+    gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
+    for chunk in gen:
+        fp.write(chunk)
+    fp.close()
+    try:
+        files = set()
+        try:
+            patch.patch(ui, repo, patchfile, files=files, eolmode=None)
+            if not files:
+                ui.warn(_('%s: empty changeset')
+                             % node.hex(ha))
+                return ctx, [], [], []
+        finally:
+            os.unlink(patchfile)
+    except Exception, inst:
+        raise util.Abort(_('Fix up the change and run '
+                           'hg histedit --continue'))
+    n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(), date=oldctx.date(),
+                    extra=oldctx.extra())
+    return finishfold(ui, repo, ctx, oldctx, n, opts, [])
+
+def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
+    parent = ctx.parents()[0].node()
+    hg.update(repo, parent)
+    fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
+    fp = os.fdopen(fd, 'w')
+    diffopts = patch.diffopts(ui, opts)
+    diffopts.git = True
+    diffopts.ignorews = False
+    diffopts.ignorewsamount = False
+    diffopts.ignoreblanklines = False
+    gen = patch.diff(repo, parent, newnode, opts=diffopts)
+    for chunk in gen:
+        fp.write(chunk)
+    fp.close()
+    files = set()
+    try:
+        patch.patch(ui, repo, patchfile, files=files, eolmode=None)
+    finally:
+        os.unlink(patchfile)
+    newmessage = '\n***\n'.join(
+        [ctx.description(), ] +
+        [repo[r].description() for r in internalchanges] +
+        [oldctx.description(), ])
+    # If the changesets are from the same author, keep it.
+    if ctx.user() == oldctx.user():
+        username = ctx.user()
+    else:
+        username = ui.username()
+    newmessage = ui.edit(newmessage, username)
+    n = repo.commit(text=newmessage, user=username, date=max(ctx.date(), oldctx.date()),
+                    extra=oldctx.extra())
+    return repo[n], [n, ], [oldctx.node(), ctx.node() ], [newnode, ]
+
+def drop(ui, repo, ctx, ha, opts):
+    return ctx, [], [repo[ha].node(), ], []
+
+
+def message(ui, repo, ctx, ha, opts):
+    oldctx = repo[ha]
+    hg.update(repo, ctx.node())
+    fd, patchfile = tempfile.mkstemp(prefix='hg-histedit-')
+    fp = os.fdopen(fd, 'w')
+    diffopts = patch.diffopts(ui, opts)
+    diffopts.git = True
+    diffopts.ignorews = False
+    diffopts.ignorewsamount = False
+    diffopts.ignoreblanklines = False
+    gen = patch.diff(repo, oldctx.parents()[0].node(), ha, opts=diffopts)
+    for chunk in gen:
+        fp.write(chunk)
+    fp.close()
+    try:
+        files = set()
+        try:
+            patch.patch(ui, repo, patchfile, files=files, eolmode=None)
+        finally:
+            os.unlink(patchfile)
+    except Exception, inst:
+        raise util.Abort(_('Fix up the change and run '
+                           'hg histedit --continue'))
+    message = oldctx.description()
+    message = ui.edit(message, ui.username())
+    new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
+                      extra=oldctx.extra())
+    newctx = repo[new]
+    if oldctx.node() != newctx.node():
+        return newctx, [new], [oldctx.node()], []
+    # We didn't make an edit, so just indicate no replaced nodes
+    return newctx, [new], [], []
+
+
+def makedesc(c):
+    summary = ''
+    if c.description():
+        summary = c.description().splitlines()[0]
+    line = 'pick %s %d %s' % (c.hex()[:12], c.rev(), summary)
+    return line[:80]  # trim to 80 chars so it's not stupidly wide in my editor
+
+actiontable = {'p': pick,
+               'pick': pick,
+               'e': edit,
+               'edit': edit,
+               'f': fold,
+               'fold': fold,
+               'd': drop,
+               'drop': drop,
+               'm': message,
+               'mess': message,
+               }
+def histedit(ui, repo, *parent, **opts):
+    """hg histedit <parent>
+    """
+    # TODO only abort if we try and histedit mq patches, not just
+    # blanket if mq patches are applied somewhere
+    mq = getattr(repo, 'mq', None)
+    if mq and mq.applied:
+        raise util.Abort(_('source has mq patches applied'))
+
+    parent = list(parent) + opts.get('rev', [])
+    if opts.get('outgoing'):
+        if len(parent) > 1:
+            raise util.Abort(_('only one repo argument allowed with --outgoing'))
+        elif parent:
+            parent = parent[0]
+
+        dest = ui.expandpath(parent or 'default-push', parent or 'default')
+        dest, revs = hg.parseurl(dest, None)[:2]
+        ui.status(_('comparing with %s\n') % util.hidepassword(dest))
+
+        revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
+        other = hg.repository(hg.remoteui(repo, opts), dest)
+
+        if revs:
+            revs = [repo.lookup(rev) for rev in revs]
+
+        parent = discovery.findcommonoutgoing(
+            repo, other, [], force=opts.get('force')).missing[0:1]
+    else:
+        if opts.get('force'):
+            raise util.Abort(_('--force only allowed with --outgoing'))
+
+    if opts.get('continue', False):
+        if len(parent) != 0:
+            raise util.Abort(_('no arguments allowed with --continue'))
+        (parentctxnode, created, replaced,
+         tmpnodes, existing, rules, keep, tip, replacemap ) = readstate(repo)
+        currentparent, wantnull = repo.dirstate.parents()
+        parentctx = repo[parentctxnode]
+        # discover any nodes the user has added in the interim
+        newchildren = [c for c in parentctx.children()
+                       if c.node() not in existing]
+        action, currentnode = rules.pop(0)
+        while newchildren:
+            if action in ['f', 'fold', ]:
+                tmpnodes.extend([n.node() for n in newchildren])
+            else:
+                created.extend([n.node() for n in newchildren])
+            newchildren = filter(lambda x: x.node() not in existing,
+                                 reduce(lambda x, y: x + y,
+                                        map(lambda r: r.children(),
+                                            newchildren)))
+        m, a, r, d = repo.status()[:4]
+        oldctx = repo[currentnode]
+        message = oldctx.description()
+        if action in ('e', 'edit', 'm', 'mess'):
+            message = ui.edit(message, ui.username())
+        elif action in ('f', 'fold', ):
+            message = 'fold-temp-revision %s' % currentnode
+        new = None
+        if m or a or r or d:
+            new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
+                              extra=oldctx.extra())
+
+        if action in ('f', 'fold'):
+            if new:
+                tmpnodes.append(new)
+            else:
+                new = newchildren[-1]
+            (parentctx, created_,
+             replaced_, tmpnodes_, ) = finishfold(ui, repo,
+                                                  parentctx, oldctx, new,
+                                                  opts, newchildren)
+            replaced.extend(replaced_)
+            created.extend(created_)
+            tmpnodes.extend(tmpnodes_)
+        elif action not in ('d', 'drop'):
+            if new != oldctx.node():
+                replaced.append(oldctx.node())
+            if new:
+                if new != oldctx.node():
+                    created.append(new)
+                parentctx = repo[new]
+
+    elif opts.get('abort', False):
+        if len(parent) != 0:
+            raise util.Abort(_('no arguments allowed with --abort'))
+        (parentctxnode, created, replaced, tmpnodes,
+         existing, rules, keep, tip, replacemap) = readstate(repo)
+        ui.debug('restore wc to old tip %s\n' % node.hex(tip))
+        hg.clean(repo, tip)
+        ui.debug('should strip created nodes %s\n' %
+                 ', '.join([node.hex(n)[:12] for n in created]))
+        ui.debug('should strip temp nodes %s\n' %
+                 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
+        for nodes in (created, tmpnodes, ):
+            for n in reversed(nodes):
+                try:
+                    repair.strip(ui, repo, n)
+                except error.LookupError:
+                    pass
+        os.unlink(os.path.join(repo.path, 'histedit-state'))
+        return
+    else:
+        cmdutil.bailifchanged(repo)
+        if os.path.exists(os.path.join(repo.path, 'histedit-state')):
+            raise util.Abort(_('history edit already in progress, try '
+                               '--continue or --abort'))
+
+        tip, empty = repo.dirstate.parents()
+
+
+        if len(parent) != 1:
+            raise util.Abort(_('histedit requires exactly one parent revision'))
+        parent = scmutil.revsingle(repo, parent[0]).node()
+
+        keep = opts.get('keep', False)
+        revs = between(repo, parent, tip, keep)
+
+        ctxs = [repo[r] for r in revs]
+        existing = [r.node() for r in ctxs]
+        rules = opts.get('commands', '')
+        if not rules:
+            rules = '\n'.join([makedesc(c) for c in ctxs])
+            rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12], )
+            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()
+        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[0] == '#']
+        rules = verifyrules(rules, repo, ctxs)
+
+        parentctx = repo[parent].parents()[0]
+        keep = opts.get('keep', False)
+        replaced = []
+        replacemap = {}
+        tmpnodes = []
+        created = []
+
+
+    while rules:
+        writestate(repo, parentctx.node(), created, replaced, tmpnodes, existing,
+                   rules, keep, tip, replacemap)
+        action, ha = rules.pop(0)
+        (parentctx, created_,
+         replaced_, tmpnodes_, ) = actiontable[action](ui, repo,
+                                                       parentctx, ha,
+                                                       opts)
+
+        hexshort = lambda x: node.hex(x)[:12]
+
+        if replaced_:
+            clen, rlen = len(created_), len(replaced_)
+            if clen == rlen == 1:
+                ui.debug('histedit: exact replacement of %s with %s\n' % (
+                    hexshort(replaced_[0]), hexshort(created_[0])))
+
+                replacemap[replaced_[0]] = created_[0]
+            elif clen > rlen:
+                assert rlen == 1, ('unexpected replacement of '
+                                   '%d changes with %d changes' % (rlen, clen))
+                # made more changesets than we're replacing
+                # TODO synthesize patch names for created patches
+                replacemap[replaced_[0]] = created_[-1]
+                ui.debug('histedit: created many, assuming %s replaced by %s' % (
+                    hexshort(replaced_[0]), hexshort(created_[-1])))
+            elif rlen > clen:
+                if not created_:
+                    # This must be a drop. Try and put our metadata on
+                    # the parent change.
+                    assert rlen == 1
+                    r = replaced_[0]
+                    ui.debug('histedit: %s seems replaced with nothing, '
+                            'finding a parent\n' % (hexshort(r)))
+                    pctx = repo[r].parents()[0]
+                    if pctx.node() in replacemap:
+                        ui.debug('histedit: parent is already replaced\n')
+                        replacemap[r] = replacemap[pctx.node()]
+                    else:
+                        replacemap[r] = pctx.node()
+                    ui.debug('histedit: %s best replaced by %s\n' % (
+                        hexshort(r), hexshort(replacemap[r])))
+                else:
+                    assert len(created_) == 1
+                    for r in replaced_:
+                        ui.debug('histedit: %s replaced by %s\n' % (
+                            hexshort(r), hexshort(created_[0])))
+                        replacemap[r] = created_[0]
+            else:
+                assert False, (
+                    'Unhandled case in replacement mapping! '
+                    'replacing %d changes with %d changes' % (rlen, clen))
+        created.extend(created_)
+        replaced.extend(replaced_)
+        tmpnodes.extend(tmpnodes_)
+
+    hg.update(repo, parentctx.node())
+
+    if not keep:
+        if replacemap:
+            ui.note('histedit: Should update metadata for the following '
+                    'changes:\n')
+
+            def copybms(old, new):
+                if old in tmpnodes or old in created:
+                    # can't have any metadata we'd want to update
+                    return
+                while new in replacemap:
+                    new = replacemap[new]
+                ui.note('histedit:  %s to %s\n' % (hexshort(old), hexshort(new)))
+                octx = repo[old]
+                marks = octx.bookmarks()
+                if marks:
+                    ui.note('histedit:     moving bookmarks %s\n' %
+                            ', '.join(marks))
+                    for mark in marks:
+                        repo._bookmarks[mark] = new
+                    bookmarks.write(repo)
+
+            # We assume that bookmarks on the tip should remain
+            # tipmost, but bookmarks on non-tip changesets should go
+            # to their most reasonable successor. As a result, find
+            # the old tip and new tip and copy those bookmarks first,
+            # then do the rest of the bookmark copies.
+            oldtip = sorted(replacemap.keys(), key=repo.changelog.rev)[-1]
+            newtip = sorted(replacemap.values(), key=repo.changelog.rev)[-1]
+            copybms(oldtip, newtip)
+
+            for old, new in replacemap.iteritems():
+                copybms(old, new)
+                # TODO update mq state
+
+        ui.debug('should strip replaced nodes %s\n' %
+                 ', '.join([node.hex(n)[:12] for n in replaced]))
+        for n in sorted(replaced, key=lambda x: repo[x].rev()):
+            try:
+                repair.strip(ui, repo, n)
+            except error.LookupError:
+                pass
+
+    ui.debug('should strip temp nodes %s\n' %
+             ', '.join([node.hex(n)[:12] for n in tmpnodes]))
+    for n in reversed(tmpnodes):
+        try:
+            repair.strip(ui, repo, n)
+        except error.LookupError:
+            pass
+    os.unlink(os.path.join(repo.path, 'histedit-state'))
+    if os.path.exists(repo.sjoin('undo')):
+        os.unlink(repo.sjoin('undo'))
+
+
+def writestate(repo, parentctxnode, created, replaced,
+               tmpnodes, existing, rules, keep, oldtip, replacemap):
+    fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
+    pickle.dump((parentctxnode, created, replaced,
+                 tmpnodes, existing, rules, keep, oldtip, replacemap),
+                fp)
+    fp.close()
+
+def readstate(repo):
+    """Returns a tuple of (parentnode, created, replaced, tmp, existing, rules,
+                           keep, oldtip, replacemap ).
+    """
+    fp = open(os.path.join(repo.path, 'histedit-state'))
+    return pickle.load(fp)
+
+
+def verifyrules(rules, repo, ctxs):
+    """Verify that there exists exactly one edit rule per given changeset.
+
+    Will abort if there are to many or too few rules, a malformed rule,
+    or a rule on a changeset outside of the user-given range.
+    """
+    parsed = []
+    first = True
+    if len(rules) != len(ctxs):
+        raise util.Abort(_('must specify a rule for each changeset once'))
+    for r in rules:
+        if ' ' not in r:
+            raise util.Abort(_('malformed line "%s"') % r)
+        action, rest = r.split(' ', 1)
+        if ' ' in rest.strip():
+            ha, rest = rest.split(' ', 1)
+        else:
+            ha = r.strip()
+        try:
+            if repo[ha] not in ctxs:
+                raise util.Abort(_('may not use changesets other than the ones listed'))
+        except error.RepoError:
+            raise util.Abort(_('unknown changeset %s listed') % ha)
+        if action not in actiontable:
+            raise util.Abort(_('unknown action "%s"') % action)
+        parsed.append([action, ha])
+    return parsed
+
+
+cmdtable = {
+    "histedit":
+        (histedit,
+         [('', 'commands', '', _('Read history edits from the specified file.')),
+          ('c', 'continue', False, _('continue an edit already in progress')),
+          ('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')),
+          ],
+         __doc__,
+         ),
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/histedit-helpers.sh	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+fixbundle() {
+    grep -v 'saving bundle' | grep -v 'saved backup' | \
+        grep -v added | grep -v adding | \
+        grep -v "unable to find 'e' for patching" | \
+        grep -v "e: No such file or directory"
+}
Binary file tests/missing-comment.hg has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-bookmark-motion.t	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,190 @@
+  $ . "$TESTDIR/histedit-helpers.sh"
+
+  $  cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > graphlog=
+  > histedit=
+  > EOF
+
+  $ hg init r
+  $ cd r
+See if bookmarks are in core. If not, then we don't support bookmark
+motion on this version of hg.
+  $ hg bookmarks || exit 80
+  no bookmarks set
+  $ for x in a b c d e f ; do
+  >     echo $x > $x
+  >     hg add $x
+  >     hg ci -m $x
+  > done
+
+  $ hg book -r 1 will-move-backwards
+  $ hg book -r 2 two
+  $ hg book -r 2 also-two
+  $ hg book -r 3 three
+  $ hg book -r 4 four
+  $ hg book -r tip five
+  $ hg log --graph
+  @  changeset:   5:652413bf663e
+  |  bookmark:    five
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     f
+  |
+  o  changeset:   4:e860deea161a
+  |  bookmark:    four
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     e
+  |
+  o  changeset:   3:055a42cdd887
+  |  bookmark:    three
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     d
+  |
+  o  changeset:   2:177f92b77385
+  |  bookmark:    also-two
+  |  bookmark:    two
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     c
+  |
+  o  changeset:   1:d2ae7f538514
+  |  bookmark:    will-move-backwards
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     b
+  |
+  o  changeset:   0:cb9a9f314b8b
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     a
+  
+  $ HGEDITOR=cat hg histedit 1
+  pick d2ae7f538514 1 b
+  pick 177f92b77385 2 c
+  pick 055a42cdd887 3 d
+  pick e860deea161a 4 e
+  pick 652413bf663e 5 f
+  
+  # Edit history between d2ae7f538514 and 652413bf663e
+  #
+  # Commands:
+  #  p, pick = use commit
+  #  e, edit = use commit, but stop for amending
+  #  f, fold = use commit, but fold into previous commit (combines N and N-1)
+  #  d, drop = remove commit from history
+  #  m, mess = edit message without changing commit content
+  #
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat >> commands.txt <<EOF
+  > pick 177f92b77385 2 c
+  > drop d2ae7f538514 1 b
+  > pick 055a42cdd887 3 d
+  > fold e860deea161a 4 e
+  > pick 652413bf663e 5 f
+  > EOF
+  $ hg histedit 1 --commands commands.txt --verbose | grep histedit
+  histedit: Should update metadata for the following changes:
+  histedit:  055a42cdd887 to ae467701c500
+  histedit:     moving bookmarks three
+  histedit:  652413bf663e to 0efacef7cb48
+  histedit:     moving bookmarks five
+  histedit:  d2ae7f538514 to cb9a9f314b8b
+  histedit:     moving bookmarks will-move-backwards
+  histedit:  e860deea161a to ae467701c500
+  histedit:     moving bookmarks four
+  histedit:  177f92b77385 to d36c0562f908
+  histedit:     moving bookmarks also-two, two
+  saved backup bundle to $TESTTMP/r/.hg/strip-backup/d2ae7f538514-backup.hg
+  saved backup bundle to $TESTTMP/r/.hg/strip-backup/34a9919932c1-backup.hg
+  $ hg log --graph
+  @  changeset:   3:0efacef7cb48
+  |  bookmark:    five
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     f
+  |
+  o  changeset:   2:ae467701c500
+  |  bookmark:    four
+  |  bookmark:    three
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     d
+  |
+  o  changeset:   1:d36c0562f908
+  |  bookmark:    also-two
+  |  bookmark:    two
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     c
+  |
+  o  changeset:   0:cb9a9f314b8b
+     bookmark:    will-move-backwards
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     a
+  
+  $ HGEDITOR=cat hg histedit 1
+  pick d36c0562f908 1 c
+  pick ae467701c500 2 d
+  pick 0efacef7cb48 3 f
+  
+  # Edit history between d36c0562f908 and 0efacef7cb48
+  #
+  # Commands:
+  #  p, pick = use commit
+  #  e, edit = use commit, but stop for amending
+  #  f, fold = use commit, but fold into previous commit (combines N and N-1)
+  #  d, drop = remove commit from history
+  #  m, mess = edit message without changing commit content
+  #
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat > commands.txt << EOF
+  > pick d36c0562f908 1 c
+  > pick 0efacef7cb48 3 f
+  > pick ae467701c500 2 d
+  > EOF
+  $ hg histedit 1 --commands commands.txt --verbose | grep histedit
+  histedit: Should update metadata for the following changes:
+  histedit:  0efacef7cb48 to 1be9c35b4cb2
+  histedit:     moving bookmarks five
+  histedit:  ae467701c500 to 1be9c35b4cb2
+  histedit:     moving bookmarks four, three
+  histedit:  0efacef7cb48 to 7c044e3e33a9
+  saved backup bundle to $TESTTMP/r/.hg/strip-backup/ae467701c500-backup.hg
+
+We expect 'five' to stay at tip, since the tipmost bookmark is most
+likely the useful signal.
+
+  $ hg log --graph
+  @  changeset:   3:1be9c35b4cb2
+  |  bookmark:    five
+  |  bookmark:    four
+  |  bookmark:    three
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     d
+  |
+  o  changeset:   2:7c044e3e33a9
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     f
+  |
+  o  changeset:   1:d36c0562f908
+  |  bookmark:    also-two
+  |  bookmark:    two
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     c
+  |
+  o  changeset:   0:cb9a9f314b8b
+     bookmark:    will-move-backwards
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     a
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-commute	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+. "$TESTDIR/histedit-helpers.sh"
+
+cat >> $HGRCPATH <<EOF
+[extensions]
+graphlog=
+histedit=
+EOF
+
+EDITED=`pwd`/editedhistory
+cat > $EDITED <<EOF
+pick 177f92b77385 c
+pick e860deea161a e
+pick 652413bf663e f
+pick 055a42cdd887 d
+EOF
+initrepo ()
+{
+    hg init r
+    cd r
+    for x in a b c d e f ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+}
+
+initrepo
+
+echo % log before edit
+hg log --graph
+
+echo % show the edit commands offered
+HGEDITOR=cat hg histedit 177f92b77385
+
+echo % edit the history
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+
+echo % rules should end up in .hg/histedit-last-edit.txt:
+cat .hg/histedit-last-edit.txt
+echo '**** end of rules file ****'
+
+echo % log after edit
+hg log --graph
+
+echo % put things back
+
+cat > $EDITED <<EOF
+pick 177f92b77385 c
+pick 853c68da763f d
+pick b069cc29fb22 e
+pick 26f6a030ae82 f
+EOF
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+
+hg log --graph
+
+
+echo % slightly different this time
+
+cat > $EDITED <<EOF
+pick 055a42cdd887 d
+pick 652413bf663e f
+pick e860deea161a e
+pick 177f92b77385 c
+EOF
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+hg log --graph
+
+
+echo % keep prevents stripping dead revs
+cat > $EDITED <<EOF
+pick bfe4a5a76b37 d
+pick c4f52e213402 f
+pick 99a62755c625 c
+pick 7c6fdd608667 e
+EOF
+HGEDITOR="cat $EDITED > " hg histedit bfe4a5a76b37 --keep 2>&1 | fixbundle
+hg log --graph
+
+echo '% try with --rev'
+cat > $EDITED <<EOF
+pick 7c6fdd608667 e
+pick 99a62755c625 c
+EOF
+hg histedit --commands "$EDITED" --rev -2 2>&1 | fixbundle
+hg log --graph
+
+echo % should also work if a commit message is missing
+BUNDLE="$TESTDIR/missing-comment.hg"
+hg init missing
+cd missing
+hg unbundle $BUNDLE
+hg co tip
+hg log --graph
+hg histedit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-commute.out	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,277 @@
+% log before edit
+@  changeset:   5:652413bf663e
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% show the edit commands offered
+pick 177f92b77385 2 c
+pick 055a42cdd887 3 d
+pick e860deea161a 4 e
+pick 652413bf663e 5 f
+
+# Edit history between 177f92b77385 and 652413bf663e
+#
+# Commands:
+#  p, pick = use commit
+#  e, edit = use commit, but stop for amending
+#  f, fold = use commit, but fold into previous commit (combines N and N-1)
+#  d, drop = remove commit from history
+#  m, mess = edit message without changing commit content
+#
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% edit the history
+0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% rules should end up in .hg/histedit-last-edit.txt:
+pick 177f92b77385 c
+pick e860deea161a e
+pick 652413bf663e f
+pick 055a42cdd887 d
+**** end of rules file ****
+% log after edit
+@  changeset:   5:853c68da763f
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   4:26f6a030ae82
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   3:b069cc29fb22
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% put things back
+0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+@  changeset:   5:652413bf663e
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% slightly different this time
+0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+@  changeset:   5:99a62755c625
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   4:7c6fdd608667
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:c4f52e213402
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   2:bfe4a5a76b37
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% keep prevents stripping dead revs
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+@  changeset:   7:99e266581538
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   6:5ad36efb0653
+|  parent:      3:c4f52e213402
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+| o  changeset:   5:99a62755c625
+| |  user:        test
+| |  date:        Thu Jan 01 00:00:00 1970 +0000
+| |  summary:     c
+| |
+| o  changeset:   4:7c6fdd608667
+|/   user:        test
+|    date:        Thu Jan 01 00:00:00 1970 +0000
+|    summary:     e
+|
+o  changeset:   3:c4f52e213402
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   2:bfe4a5a76b37
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% try with --rev
+abort: may not use changesets other than the ones listed
+@  changeset:   7:99e266581538
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   6:5ad36efb0653
+|  parent:      3:c4f52e213402
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+| o  changeset:   5:99a62755c625
+| |  user:        test
+| |  date:        Thu Jan 01 00:00:00 1970 +0000
+| |  summary:     c
+| |
+| o  changeset:   4:7c6fdd608667
+|/   user:        test
+|    date:        Thu Jan 01 00:00:00 1970 +0000
+|    summary:     e
+|
+o  changeset:   3:c4f52e213402
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   2:bfe4a5a76b37
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% should also work if a commit message is missing
+adding changesets
+adding manifests
+adding file changes
+added 3 changesets with 3 changes to 1 files
+(run 'hg update' to get a working copy)
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+@  changeset:   2:bd22688093b3
+|  tag:         tip
+|  user:        Robert Altman <robert.altman@telventDTN.com>
+|  date:        Mon Nov 28 16:40:04 2011 +0000
+|  summary:     Update file.
+|
+o  changeset:   1:3b3e956f9171
+|  user:        Robert Altman <robert.altman@telventDTN.com>
+|  date:        Mon Nov 28 16:37:57 2011 +0000
+|
+o  changeset:   0:141947992243
+   user:        Robert Altman <robert.altman@telventDTN.com>
+   date:        Mon Nov 28 16:35:28 2011 +0000
+   summary:     Checked in text file
+
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-drop	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+. "$TESTDIR/histedit-helpers.sh"
+
+cat >> $HGRCPATH <<EOF
+[extensions]
+graphlog=
+histedit=
+EOF
+
+EDITED=`pwd`/editedhistory
+cat > $EDITED <<EOF
+drop 177f92b77385 c
+pick e860deea161a e
+pick 652413bf663e f
+pick 055a42cdd887 d
+EOF
+initrepo ()
+{
+    hg init r
+    cd r
+    for x in a b c d e f ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+}
+
+initrepo
+
+echo % log before edit
+hg log --graph
+
+echo % edit the history
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+
+echo % log after edit
+hg log --graph
+
+echo % manifest after edit
+hg manifest
+
+echo % EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-drop.out	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,71 @@
+% log before edit
+@  changeset:   5:652413bf663e
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% edit the history
+0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% log after edit
+@  changeset:   4:708943196e52
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   3:75cbdffecadb
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   2:493dc0964412
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% manifest after edit
+a
+b
+d
+e
+f
+% EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-edit	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+. "$TESTDIR/histedit-helpers.sh"
+
+cat >> $HGRCPATH <<EOF
+[extensions]
+graphlog=
+histedit=
+EOF
+
+EDITED=`pwd`/editedhistory
+cat > $EDITED <<EOF
+pick 177f92b77385 c
+pick 055a42cdd887 d
+edit e860deea161a e
+pick 652413bf663e f
+EOF
+initrepo ()
+{
+    hg init r
+    cd r
+    for x in a b c d e f ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+}
+
+initrepo
+
+echo % log before edit
+hg log --graph
+
+echo % edit the history
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+
+echo % edit the revision
+echo a > e
+HGEDITOR='echo "foobaz" > ' hg histedit --continue 2>&1 | fixbundle
+
+hg log --graph
+
+echo '% contents of e:'
+hg cat e
+
+cat > $EDITED <<EOF
+edit c38516e9ed62 f
+EOF
+HGEDITOR="cat $EDITED > " hg histedit tip 2>&1 | fixbundle
+hg status
+HGEDITOR='true' hg histedit --continue
+hg status
+
+echo % log after edit
+hg log --limit 1
+
+echo "% say we'll change the message, but don't."
+cat > ../edit.sh <<EOF
+#!/bin/sh
+cat \$1 | sed s/pick/mess/ > tmp
+mv tmp \$1
+EOF
+chmod +x ../edit.sh
+HGEDITOR="../edit.sh" hg histedit tip 2>&1 | fixbundle
+hg status
+hg log --limit 1
+
+echo % modify the message
+cat > $EDITED <<EOF
+mess c38516e9ed62 f
+EOF
+HGEDITOR="cat $EDITED > " hg histedit tip 2>&1 | fixbundle
+hg status
+hg log --limit 1
+
+echo % rollback should not work after a histedit
+hg rollback
+
+echo % EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-edit.out	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,105 @@
+% log before edit
+@  changeset:   5:652413bf663e
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% edit the history
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+abort: Make changes as needed, you may commit or record as needed now.
+When you are finished, run hg histedit --continue to resume.
+% edit the revision
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+@  changeset:   5:c38516e9ed62
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:1da62d13177d
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     foobaz
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% contents of e:
+a
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+abort: Make changes as needed, you may commit or record as needed now.
+When you are finished, run hg histedit --continue to resume.
+A f
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% log after edit
+changeset:   5:c38516e9ed62
+tag:         tip
+user:        test
+date:        Thu Jan 01 00:00:00 1970 +0000
+summary:     f
+
+% say we'll change the message, but don't.
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+changeset:   5:c38516e9ed62
+tag:         tip
+user:        test
+date:        Thu Jan 01 00:00:00 1970 +0000
+summary:     f
+
+% modify the message
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+changeset:   5:4d6a10bcf3e3
+tag:         tip
+user:        test
+date:        Thu Jan 01 00:00:00 1970 +0000
+summary:     mess c38516e9ed62 f
+
+% rollback should not work after a histedit
+no rollback information available
+% EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-fold	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+. "$TESTDIR/histedit-helpers.sh"
+
+cat >> $HGRCPATH <<EOF
+[extensions]
+graphlog=
+histedit=
+EOF
+
+EDITED=`pwd`/editedhistory
+cat > $EDITED <<EOF
+pick e860deea161a e
+pick 652413bf663e f
+fold 177f92b77385 c
+pick 055a42cdd887 d
+EOF
+initrepo ()
+{
+    hg init r
+    cd r
+    for x in a b c d e f ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+}
+
+initrepo
+
+echo % log before edit
+hg log --graph
+
+echo % edit the history
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+
+echo % log after edit
+hg log --graph
+
+echo % post-fold manifest
+hg manifest
+
+echo % EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-fold-non-commute	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+. "$TESTDIR/histedit-helpers.sh"
+
+cat >> $HGRCPATH <<EOF
+[extensions]
+graphlog=
+histedit=
+EOF
+
+EDITED=`pwd`/editedhistory
+cat > $EDITED <<EOF
+pick 177f92b77385 c
+pick 055a42cdd887 d
+fold bfa474341cc9 does not commute with e
+pick e860deea161a e
+pick 652413bf663e f
+EOF
+initrepo ()
+{
+    hg init r
+    cd r
+    for x in a b c d e f ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+    echo a >> e
+    hg ci -m 'does not commute with e'
+}
+
+initrepo
+
+echo % log before edit
+hg log --graph
+
+echo % edit the history
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+
+echo % fix up
+echo a > e
+hg add e
+cat > cat.py <<EOF
+import sys
+print open(sys.argv[1]).read()
+print
+print
+EOF
+HGEDITOR="python cat.py" hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
+
+echo
+echo % just continue this time
+hg histedit --continue 2>&1 | fixbundle
+
+
+echo % log after edit
+hg log --graph
+
+echo % contents of e
+hg cat e
+
+echo % manifest
+hg manifest
+
+echo % EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-fold-non-commute.out	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,92 @@
+% log before edit
+@  changeset:   6:bfa474341cc9
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     does not commute with e
+|
+o  changeset:   5:652413bf663e
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% edit the history
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+% fix up
+d
+***
+does not commute with e
+
+
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+file e already exists
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+
+% just continue this time
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% log after edit
+@  changeset:   4:f768fd60ca34
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   3:671efe372e33
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% contents of e
+a
+% manifest
+a
+b
+c
+d
+e
+f
+% EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-fold.out	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,74 @@
+% log before edit
+@  changeset:   5:652413bf663e
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% edit the history
+0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% log after edit
+@  changeset:   4:82b0c1ff1777
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   3:150aafb44a91
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     pick e860deea161a e
+|
+o  changeset:   2:493dc0964412
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% post-fold manifest
+a
+b
+c
+d
+e
+f
+% EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-no-change	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,110 @@
+#!/bin/sh
+
+# test for issue #6:
+# editing a changeset without any actual change would corrupt the repository
+
+. "$TESTDIR/histedit-helpers.sh"
+
+cat >> $HGRCPATH <<EOF
+[extensions]
+graphlog=
+histedit=
+EOF
+
+initrepo ()
+{
+    dir="$1"
+    comment="$2"
+
+    if [ -n "${comment}" ]; then
+        echo % ${comment}
+        echo % ${comment} | sed 's:.:-:g'
+    fi
+
+    hg init ${dir}
+    cd ${dir}
+    for x in a b c d e f ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+}
+
+geneditor ()
+{
+    # generate an editor script for selecting changesets to be edited
+
+    choice=$1  # changesets that should be edited (using sed line ranges)
+
+    cat <<EOF | sed 's:^....::'
+    #!/bin/sh
+
+    # editing the rules, replacing 'pick' with 'edit' for the chosen lines
+    sed '${choice}s:^pick:edit:' \$1 > \${1}.tmp
+    mv \${1}.tmp \$1
+
+    # displaying the resulting rules, minus comments and empty lines
+    sed '/^#/d;/^$/d;s:^:| :' \$1 >&2
+EOF
+}
+
+startediting ()
+{
+    # begin an editing session
+
+    choice="$1"  # changesets that should be edited
+    number="$2"  # number of changesets considered (from tip)
+    comment="$3"
+
+    geneditor "${choice}" > edit.sh
+    chmod +x edit.sh
+
+    echo % start editing the history ${comment}
+    HGEDITOR=./edit.sh hg histedit -- -${number} 2>&1 | fixbundle
+}
+
+continueediting ()
+{
+    # continue an edit already in progress
+
+    editor="$1"  # message editor when finalizing editing
+    comment="$2"
+
+    echo % finalize changeset editing ${comment}
+    HGEDITOR=${editor} hg histedit --continue 2>&1 | fixbundle
+}
+
+graphlog ()
+{
+    comment="${1:-log}"
+
+    echo % "${comment}"
+    hg glog --template '{rev} {node} \"{desc|firstline}\"\n'
+}
+
+
+
+initrepo r1 "test editing with no change"
+graphlog "log before editing"
+startediting 2 3 "(not changing anything)" # edit the 2nd of 3 changesets
+continueediting true "(leaving commit message unaltered)"
+
+echo "% check state of working copy"
+hg id
+
+graphlog "log after history editing"
+
+
+cd ..
+initrepo r2 "test editing with no change, then abort"
+graphlog "log before editing"
+startediting 1,2 3 "(not changing anything)" # edit the 1st two of 3 changesets
+continueediting true "(leaving commit message unaltered)"
+graphlog "log after first edit"
+
+echo % abort editing session
+hg histedit --abort 2>&1 | fixbundle
+
+graphlog "log after abort"
+
+echo % EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-no-change.out	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,94 @@
+% test editing with no change
+-----------------------------
+% log before editing
+@  5 652413bf663ef2a641cab26574e46d5f5a64a55a "f"
+|
+o  4 e860deea161a2f77de56603b340ebbb4536308ae "e"
+|
+o  3 055a42cdd88768532f9cf79daa407fc8d138de9b "d"
+|
+o  2 177f92b773850b59254aa5e923436f921b55483b "c"
+|
+o  1 d2ae7f538514cd87c17547b0de4cea71fe1af9fb "b"
+|
+o  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a"
+
+% start editing the history (not changing anything)
+| pick 055a42cdd887 3 d
+| edit e860deea161a 4 e
+| pick 652413bf663e 5 f
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+abort: Make changes as needed, you may commit or record as needed now.
+When you are finished, run hg histedit --continue to resume.
+% finalize changeset editing (leaving commit message unaltered)
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% check state of working copy
+652413bf663e tip
+% log after history editing
+@  5 652413bf663ef2a641cab26574e46d5f5a64a55a "f"
+|
+o  4 e860deea161a2f77de56603b340ebbb4536308ae "e"
+|
+o  3 055a42cdd88768532f9cf79daa407fc8d138de9b "d"
+|
+o  2 177f92b773850b59254aa5e923436f921b55483b "c"
+|
+o  1 d2ae7f538514cd87c17547b0de4cea71fe1af9fb "b"
+|
+o  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a"
+
+% test editing with no change, then abort
+-----------------------------------------
+% log before editing
+@  5 652413bf663ef2a641cab26574e46d5f5a64a55a "f"
+|
+o  4 e860deea161a2f77de56603b340ebbb4536308ae "e"
+|
+o  3 055a42cdd88768532f9cf79daa407fc8d138de9b "d"
+|
+o  2 177f92b773850b59254aa5e923436f921b55483b "c"
+|
+o  1 d2ae7f538514cd87c17547b0de4cea71fe1af9fb "b"
+|
+o  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a"
+
+% start editing the history (not changing anything)
+| edit 055a42cdd887 3 d
+| edit e860deea161a 4 e
+| pick 652413bf663e 5 f
+0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+abort: Make changes as needed, you may commit or record as needed now.
+When you are finished, run hg histedit --continue to resume.
+% finalize changeset editing (leaving commit message unaltered)
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+abort: Make changes as needed, you may commit or record as needed now.
+When you are finished, run hg histedit --continue to resume.
+% log after first edit
+o  5 652413bf663ef2a641cab26574e46d5f5a64a55a "f"
+|
+o  4 e860deea161a2f77de56603b340ebbb4536308ae "e"
+|
+@  3 055a42cdd88768532f9cf79daa407fc8d138de9b "d"
+|
+o  2 177f92b773850b59254aa5e923436f921b55483b "c"
+|
+o  1 d2ae7f538514cd87c17547b0de4cea71fe1af9fb "b"
+|
+o  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a"
+
+% abort editing session
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% log after abort
+@  5 652413bf663ef2a641cab26574e46d5f5a64a55a "f"
+|
+o  4 e860deea161a2f77de56603b340ebbb4536308ae "e"
+|
+o  3 055a42cdd88768532f9cf79daa407fc8d138de9b "d"
+|
+o  2 177f92b773850b59254aa5e923436f921b55483b "c"
+|
+o  1 d2ae7f538514cd87c17547b0de4cea71fe1af9fb "b"
+|
+o  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b "a"
+
+% EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-non-commute	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+. "$TESTDIR/histedit-helpers.sh"
+
+cat >> $HGRCPATH <<EOF
+[extensions]
+graphlog=
+histedit=
+EOF
+
+EDITED=`pwd`/editedhistory
+cat > $EDITED <<EOF
+pick 177f92b77385 c
+pick 055a42cdd887 d
+pick bfa474341cc9 does not commute with e
+pick e860deea161a e
+pick 652413bf663e f
+EOF
+initrepo ()
+{
+    hg init r
+    cd r
+    for x in a b c d e f ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+    echo a >> e
+    hg ci -m 'does not commute with e'
+}
+
+initrepo
+
+echo % log before edit
+hg log --graph
+
+echo % edit the history
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+
+echo % abort the edit
+hg histedit --abort 2>&1 | fixbundle
+
+echo
+echo
+echo % second edit set
+
+hg log --graph
+
+echo % edit the history
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+
+echo % fix up
+echo a > e
+hg add e
+hg histedit --continue 2>&1 | fixbundle
+
+echo
+echo % just continue this time
+hg histedit --continue 2>&1 | fixbundle
+
+echo % log after edit
+hg log --graph
+
+echo % start over
+
+cd ..
+rm -r r
+initrepo
+cat > $EDITED <<EOF
+pick 177f92b77385 c
+pick 055a42cdd887 d
+mess bfa474341cc9 does not commute with e
+pick e860deea161a e
+pick 652413bf663e f
+EOF
+
+echo % edit the history, this time with a fold action
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+
+echo
+echo a > e
+hg add e
+HGEDITOR="cat $EDITED > " hg histedit --continue 2>&1 | fixbundle
+echo % second edit also fails, but just continue
+hg histedit --continue 2>&1 | fixbundle
+
+echo % post message fix
+hg log --graph
+
+echo % EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-non-commute-abort	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+. "$TESTDIR/histedit-helpers.sh"
+
+cat >> $HGRCPATH <<EOF
+[extensions]
+graphlog=
+histedit=
+EOF
+
+EDITED=`pwd`/editedhistory
+cat > $EDITED <<EOF
+pick 177f92b77385 c
+pick 055a42cdd887 d
+pick bfa474341cc9 does not commute with e
+pick e860deea161a e
+pick 652413bf663e f
+EOF
+initrepo ()
+{
+    hg init r
+    cd r
+    for x in a b c d e f ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+    echo a >> e
+    hg ci -m 'does not commute with e'
+}
+
+initrepo
+
+echo % log before edit
+hg log --graph
+
+echo % edit the history
+HGEDITOR="cat $EDITED > " hg histedit 177f92b77385 2>&1 | fixbundle
+
+echo '% fix up (pre abort)'
+echo a > e
+hg add e
+hg histedit --continue 2>&1 | fixbundle
+
+echo % abort the edit
+hg histedit --abort 2>&1 | fixbundle
+
+echo % log after abort
+hg log --graph
+echo % EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-non-commute-abort.out	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,86 @@
+% log before edit
+@  changeset:   6:bfa474341cc9
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     does not commute with e
+|
+o  changeset:   5:652413bf663e
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% edit the history
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+% fix up (pre abort)
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+file e already exists
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+% abort the edit
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% log after abort
+@  changeset:   6:bfa474341cc9
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     does not commute with e
+|
+o  changeset:   5:652413bf663e
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-non-commute.out	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,173 @@
+% log before edit
+@  changeset:   6:bfa474341cc9
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     does not commute with e
+|
+o  changeset:   5:652413bf663e
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% edit the history
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+% abort the edit
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+
+% second edit set
+@  changeset:   6:bfa474341cc9
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     does not commute with e
+|
+o  changeset:   5:652413bf663e
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:e860deea161a
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% edit the history
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+% fix up
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+file e already exists
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+
+% just continue this time
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% log after edit
+@  changeset:   5:9ab84894b459
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:1fff3ae8199d
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     does not commute with e
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% start over
+% edit the history, this time with a fold action
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+file e already exists
+1 out of 1 hunks FAILED -- saving rejects to file e.rej
+abort: Fix up the change and run hg histedit --continue
+% second edit also fails, but just continue
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% post message fix
+@  changeset:   5:6459970fb49b
+|  tag:         tip
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     f
+|
+o  changeset:   4:556f27c874b0
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     pick 177f92b77385 c
+|
+o  changeset:   3:055a42cdd887
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     d
+|
+o  changeset:   2:177f92b77385
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     c
+|
+o  changeset:   1:d2ae7f538514
+|  user:        test
+|  date:        Thu Jan 01 00:00:00 1970 +0000
+|  summary:     b
+|
+o  changeset:   0:cb9a9f314b8b
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     a
+
+% EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-outgoing	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+cat >> $HGRCPATH <<EOF
+[extensions]
+graphlog=
+histedit=
+EOF
+
+EDITED=`pwd`/editedhistory
+cat > $EDITED <<EOF
+pick 177f92b77385 c
+pick e860deea161a e
+pick 652413bf663e f
+pick 055a42cdd887 d
+EOF
+initrepo ()
+{
+    hg init r
+    cd r
+    for x in a b c ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+
+    cd ..
+    hg clone r r2 | grep -v updating
+    cd r2
+    for x in d e f ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+
+    cd ..
+    hg init r3
+    cd r3
+    for x in g h i ; do
+        echo $x > $x
+        hg add $x
+        hg ci -m $x
+    done
+    cd ..
+}
+
+initrepo
+
+echo % show the edit commands offered by outgoing
+cd r2
+HGEDITOR=cat hg histedit --outgoing ../r | grep -v comparing | grep -v searching
+cd ..
+
+echo % show the error from unrelated repos
+cd r3
+HGEDITOR=cat hg histedit --outgoing ../r | grep -v comparing | grep -v searching
+cd ..
+
+echo % show the error from unrelated repos
+cd r3
+HGEDITOR=cat hg histedit --force --outgoing ../r
+cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-outgoing.out	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,36 @@
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% show the edit commands offered by outgoing
+pick 055a42cdd887 3 d
+pick e860deea161a 4 e
+pick 652413bf663e 5 f
+
+# Edit history between 055a42cdd887 and 652413bf663e
+#
+# Commands:
+#  p, pick = use commit
+#  e, edit = use commit, but stop for amending
+#  f, fold = use commit, but fold into previous commit (combines N and N-1)
+#  d, drop = remove commit from history
+#  m, mess = edit message without changing commit content
+#
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% show the error from unrelated repos
+abort: repository is unrelated
+% show the error from unrelated repos
+comparing with ../r
+searching for changes
+warning: repository is unrelated
+pick 2a4042b45417 0 g
+pick 68c46b4927ce 1 h
+pick 51281e65ba79 2 i
+
+# Edit history between 2a4042b45417 and 51281e65ba79
+#
+# Commands:
+#  p, pick = use commit
+#  e, edit = use commit, but stop for amending
+#  f, fold = use commit, but fold into previous commit (combines N and N-1)
+#  d, drop = remove commit from history
+#  m, mess = edit message without changing commit content
+#
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-revspec.t	Wed Jun 27 17:52:54 2012 -0500
@@ -0,0 +1,62 @@
+This test requires parentrevspec support in revsets, so check for that
+and skip the test if we're on an unusual hg that supports .t tests but
+not parentrevspec.
+  $ python -c 'from mercurial import revset ; revset.methods["parentpost"]' || exit 80
+
+Enable extensions used by this test.
+  $ cat >>$HGRCPATH <<EOF
+  > [extensions]
+  > graphlog=
+  > histedit=
+  > EOF
+
+Repo setup.
+  $ hg init foo
+  $ cd foo
+  $ echo alpha >> alpha
+  $ hg addr
+  adding alpha
+  $ hg ci -m one
+  $ echo alpha >> alpha
+  $ hg ci -m two
+  $ echo alpha >> alpha
+  $ hg ci -m three
+  $ echo alpha >> alpha
+  $ hg ci -m four
+  $ echo alpha >> alpha
+  $ hg ci -m five
+
+  $ hg log --style compact --graph
+  @  4[tip]   08d98a8350f3   1970-01-01 00:00 +0000   test
+  |    five
+  |
+  o  3   c8e68270e35a   1970-01-01 00:00 +0000   test
+  |    four
+  |
+  o  2   eb57da33312f   1970-01-01 00:00 +0000   test
+  |    three
+  |
+  o  1   579e40513370   1970-01-01 00:00 +0000   test
+  |    two
+  |
+  o  0   6058cbb6cfd7   1970-01-01 00:00 +0000   test
+       one
+  
+
+Run a dummy edit to make sure we get tip^^ correctly via revsingle.
+  $ HGEDITOR=cat hg histedit tip^^
+  pick eb57da33312f 2 three
+  pick c8e68270e35a 3 four
+  pick 08d98a8350f3 4 five
+  
+  # Edit history between eb57da33312f and 08d98a8350f3
+  #
+  # Commands:
+  #  p, pick = use commit
+  #  e, edit = use commit, but stop for amending
+  #  f, fold = use commit, but fold into previous commit (combines N and N-1)
+  #  d, drop = remove commit from history
+  #  m, mess = edit message without changing commit content
+  #
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+