# HG changeset patch # User Augie Fackler # Date 2012-06-27 22:52:54 # Node ID 168cc52ad7c2b47f9baeeaff4ac39a31d7dd95b6 # Parent 3fbc6e3abdbd462bf74656486e18df98de886e81 histedit: new extension for interactive history editing diff --git a/hgext/histedit.py b/hgext/histedit.py new file mode 100644 --- /dev/null +++ b/hgext/histedit.py @@ -0,0 +1,564 @@ +# histedit.py - interactive history editing for mercurial +# +# Copyright 2009 Augie Fackler +# +# 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 + """ + # 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__, + ), +} diff --git a/tests/histedit-helpers.sh b/tests/histedit-helpers.sh new file mode 100644 --- /dev/null +++ b/tests/histedit-helpers.sh @@ -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" +} diff --git a/tests/missing-comment.hg b/tests/missing-comment.hg new file mode 100644 index 0000000000000000000000000000000000000000..7a5be69289bfcdc472842bb7eb7bbac4eff25a4b GIT binary patch literal 1077 zc%1WaH#BgH%CIzaj8qGbJS4K>Jp+T{|Nnoc9Z$Yr|Nn3O<>@#6zyJT`P33zd9_JUv zzP)zIozJ)3zrY+9+?*}J#;d8;@YP6)ap3|y)87?sJG8i#{0b7Gqt{_9^ zvRRqS1WrykVK7U|fW^c>t*1xfKGe`#BF@a~^*nKA~QG=(V<6Bf*K`X9}D=zEQ8%Y$P(u8MRB z*e1*{@UrGwR3s&+IO+bTbB`9CJ12JXqj=Z%Ye$;g4sKX2y--rqVA`bVe-CbdX2qeS zaK=>V;6XN@nF&YP+Kf34SxG3aFi}jHZ+7S}e`o53i%ddwj`IBn4;*!~sF3hgpQUPX zg2$=1;$@f9RwYrdw;`Q74ohs1a_)Sw<@{^b#z`DoZEpl$5GkAb>Qbw6P1tLLx;t^} z?)L;OOsTmgx1~kND|6D*#at_I-B~cf1(ARKK#dfh?DNHKUz!j(8cb-<2BT;LVx2&4{nEyU0KH!XC~ioR+O@Q?#^A zMCF4VD>*kwspKSWSo`*XOp26%?W61masPSjs!UZ3PVRK=Y>H#%`#-0%sBHDm8=^;V zzL)ZMHWivU!_hKLjwf1%E9LPGiJk5hG5(I#yG7E@ORM?%T{U2i5M_R)awb^SV=m|Z zEuG05!p{~gYIvo`c_h*DfQ5?G$=qpsJ(brSdDOZgn%VNB@XbI$nbg5F*U7&ri9MJ}C1L4jsfZP8*&45} zXfxsQT{c_f%H<~(8dlFN zsfw@8w+TMv+gQ+{vHj`%1?P+fc?A}QD}SmzeVZk4LEWM4rfiE83cd+MI)72gYdRj8 nq@a<{aHjU*0tta4CMMC7+> $HGRCPATH < [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 < 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 + diff --git a/tests/test-histedit-commute b/tests/test-histedit-commute new file mode 100755 --- /dev/null +++ b/tests/test-histedit-commute @@ -0,0 +1,97 @@ +#!/bin/sh + +. "$TESTDIR/histedit-helpers.sh" + +cat >> $HGRCPATH < $EDITED < $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 <&1 | fixbundle + +hg log --graph + + +echo % slightly different this time + +cat > $EDITED <&1 | fixbundle +hg log --graph + + +echo % keep prevents stripping dead revs +cat > $EDITED <&1 | fixbundle +hg log --graph + +echo '% try with --rev' +cat > $EDITED <&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 diff --git a/tests/test-histedit-commute.out b/tests/test-histedit-commute.out new file mode 100644 --- /dev/null +++ b/tests/test-histedit-commute.out @@ -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 +| date: Mon Nov 28 16:40:04 2011 +0000 +| summary: Update file. +| +o changeset: 1:3b3e956f9171 +| user: Robert Altman +| date: Mon Nov 28 16:37:57 2011 +0000 +| +o changeset: 0:141947992243 + user: Robert Altman + 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 diff --git a/tests/test-histedit-drop b/tests/test-histedit-drop new file mode 100755 --- /dev/null +++ b/tests/test-histedit-drop @@ -0,0 +1,43 @@ +#!/bin/sh + +. "$TESTDIR/histedit-helpers.sh" + +cat >> $HGRCPATH < $EDITED < $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 diff --git a/tests/test-histedit-drop.out b/tests/test-histedit-drop.out new file mode 100644 --- /dev/null +++ b/tests/test-histedit-drop.out @@ -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 diff --git a/tests/test-histedit-edit b/tests/test-histedit-edit new file mode 100755 --- /dev/null +++ b/tests/test-histedit-edit @@ -0,0 +1,79 @@ +#!/bin/sh + +. "$TESTDIR/histedit-helpers.sh" + +cat >> $HGRCPATH < $EDITED < $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 <&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 < 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 <&1 | fixbundle +hg status +hg log --limit 1 + +echo % rollback should not work after a histedit +hg rollback + +echo % EOF diff --git a/tests/test-histedit-edit.out b/tests/test-histedit-edit.out new file mode 100644 --- /dev/null +++ b/tests/test-histedit-edit.out @@ -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 diff --git a/tests/test-histedit-fold b/tests/test-histedit-fold new file mode 100755 --- /dev/null +++ b/tests/test-histedit-fold @@ -0,0 +1,43 @@ +#!/bin/sh + +. "$TESTDIR/histedit-helpers.sh" + +cat >> $HGRCPATH < $EDITED < $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 diff --git a/tests/test-histedit-fold-non-commute b/tests/test-histedit-fold-non-commute new file mode 100755 --- /dev/null +++ b/tests/test-histedit-fold-non-commute @@ -0,0 +1,65 @@ +#!/bin/sh + +. "$TESTDIR/histedit-helpers.sh" + +cat >> $HGRCPATH < $EDITED < $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 <&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 diff --git a/tests/test-histedit-fold-non-commute.out b/tests/test-histedit-fold-non-commute.out new file mode 100644 --- /dev/null +++ b/tests/test-histedit-fold-non-commute.out @@ -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 diff --git a/tests/test-histedit-fold.out b/tests/test-histedit-fold.out new file mode 100644 --- /dev/null +++ b/tests/test-histedit-fold.out @@ -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 diff --git a/tests/test-histedit-no-change b/tests/test-histedit-no-change new file mode 100755 --- /dev/null +++ b/tests/test-histedit-no-change @@ -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 < $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 < \${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 diff --git a/tests/test-histedit-no-change.out b/tests/test-histedit-no-change.out new file mode 100644 --- /dev/null +++ b/tests/test-histedit-no-change.out @@ -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 diff --git a/tests/test-histedit-non-commute b/tests/test-histedit-non-commute new file mode 100755 --- /dev/null +++ b/tests/test-histedit-non-commute @@ -0,0 +1,90 @@ +#!/bin/sh + +. "$TESTDIR/histedit-helpers.sh" + +cat >> $HGRCPATH < $EDITED < $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 <&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 diff --git a/tests/test-histedit-non-commute-abort b/tests/test-histedit-non-commute-abort new file mode 100755 --- /dev/null +++ b/tests/test-histedit-non-commute-abort @@ -0,0 +1,50 @@ +#!/bin/sh + +. "$TESTDIR/histedit-helpers.sh" + +cat >> $HGRCPATH < $EDITED < $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 diff --git a/tests/test-histedit-non-commute-abort.out b/tests/test-histedit-non-commute-abort.out new file mode 100644 --- /dev/null +++ b/tests/test-histedit-non-commute-abort.out @@ -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 diff --git a/tests/test-histedit-non-commute.out b/tests/test-histedit-non-commute.out new file mode 100644 --- /dev/null +++ b/tests/test-histedit-non-commute.out @@ -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 diff --git a/tests/test-histedit-outgoing b/tests/test-histedit-outgoing new file mode 100755 --- /dev/null +++ b/tests/test-histedit-outgoing @@ -0,0 +1,61 @@ +#!/bin/sh + +cat >> $HGRCPATH < $EDITED < $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 .. diff --git a/tests/test-histedit-outgoing.out b/tests/test-histedit-outgoing.out new file mode 100644 --- /dev/null +++ b/tests/test-histedit-outgoing.out @@ -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 diff --git a/tests/test-histedit-revspec.t b/tests/test-histedit-revspec.t new file mode 100644 --- /dev/null +++ b/tests/test-histedit-revspec.t @@ -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 < [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 +