diff -r 91196ebcaeed -r 55982f62651f mercurial/cmdutil.py --- a/mercurial/cmdutil.py Mon Apr 16 22:41:03 2012 -0700 +++ b/mercurial/cmdutil.py Wed Apr 18 01:20:16 2012 +0300 @@ -10,7 +10,7 @@ import os, sys, errno, re, tempfile import util, scmutil, templater, patch, error, templatekw, revlog, copies import match as matchmod -import subrepo +import subrepo, context, repair, bookmarks def parsealiases(cmd): return cmd.lstrip("^").split("|") @@ -1285,6 +1285,123 @@ return commitfunc(ui, repo, message, scmutil.match(repo[None], pats, opts), opts) +def amend(ui, repo, commitfunc, old, extra, pats, opts): + ui.note(_('amending changeset %s\n') % old) + base = old.p1() + + wlock = repo.wlock() + try: + # Fix up dirstate for copies and renames + duplicatecopies(repo, None, base.node()) + + # First, do a regular commit to record all changes in the working + # directory (if there are any) + node = commit(ui, repo, commitfunc, pats, opts) + ctx = repo[node] + + # Participating changesets: + # + # node/ctx o - new (intermediate) commit that contains changes from + # | working dir to go into amending commit (or a workingctx + # | if there were no changes) + # | + # old o - changeset to amend + # | + # base o - parent of amending changeset + + files = set(old.files()) + + # Second, we use either the commit we just did, or if there were no + # changes the parent of the working directory as the version of the + # files in the final amend commit + if node: + ui.note(_('copying changeset %s to %s\n') % (ctx, base)) + + user = ctx.user() + date = ctx.date() + message = ctx.description() + extra = ctx.extra() + + # Prune files which were reverted by the updates: if old introduced + # file X and our intermediate commit, node, renamed that file, then + # those two files are the same and we can discard X from our list + # of files. Likewise if X was deleted, it's no longer relevant + files.update(ctx.files()) + + def samefile(f): + if f in ctx.manifest(): + a = ctx.filectx(f) + if f in base.manifest(): + b = base.filectx(f) + return (a.data() == b.data() + and a.flags() == b.flags() + and a.renamed() == b.renamed()) + else: + return False + else: + return f not in base.manifest() + files = [f for f in files if not samefile(f)] + + def filectxfn(repo, ctx_, path): + try: + return ctx.filectx(path) + except KeyError: + raise IOError() + else: + ui.note(_('copying changeset %s to %s\n') % (old, base)) + + # Use version of files as in the old cset + def filectxfn(repo, ctx_, path): + try: + return old.filectx(path) + except KeyError: + raise IOError() + + # See if we got a message from -m or -l, if not, open the editor + # with the message of the changeset to amend + user = opts.get('user') or old.user() + date = opts.get('date') or old.date() + message = logmessage(ui, opts) + if not message: + cctx = context.workingctx(repo, old.description(), user, date, + extra, + repo.status(base.node(), old.node())) + message = commitforceeditor(repo, cctx, []) + + new = context.memctx(repo, + parents=[base.node(), nullid], + text=message, + files=files, + filectxfn=filectxfn, + user=user, + date=date, + extra=extra) + newid = repo.commitctx(new) + if newid != old.node(): + # Reroute the working copy parent to the new changeset + repo.dirstate.setparents(newid, nullid) + + # Move bookmarks from old parent to amend commit + bms = repo.nodebookmarks(old.node()) + if bms: + for bm in bms: + repo._bookmarks[bm] = newid + bookmarks.write(repo) + + # Strip the intermediate commit (if there was one) and the amended + # commit + lock = repo.lock() + try: + if node: + ui.note(_('stripping intermediate changeset %s\n') % ctx) + ui.note(_('stripping amended changeset %s\n') % old) + repair.strip(ui, repo, old.node(), topic='amend-backup') + finally: + lock.release() + finally: + wlock.release() + return newid + def commiteditor(repo, ctx, subs): if ctx.description(): return ctx.description()