Mercurial > public > mercurial-scm > hg-stable
diff mercurial/cmdutil.py @ 16458:55982f62651f
commit: add option to amend the working dir parent
The --amend flag can be used to amend the parent of the working directory
with a new commit that contains the changes in the parent in addition to
those currently reported by "hg status", if there are any. The old commit
is stored in a backup bundle in ".hg/strip-backup"(see "hg help bundle"
and "hg help unbundle" on how to restore it).
Message, user and date are taken from the amended commit unless specified.
When a message isn't specified on the command line, the editor will open
with the message of the amended commit.
It is not possible to amend public changesets (see "hg help phases") or
changesets that have children.
Behind the scenes, first commit the update (if there is one) as a regular
child of the current parent. Then create a new commit on the parent's
parent with the updated contents. Then change the working copy parent
to this new combined changeset. Finally, strip the amended commit and
update commit created in the beginning.
An alternative (cleaner?) approach of doing this is suggested here:
http://selenic.com/pipermail/mercurial-devel/2012-March/038540.html
It is currently not possible to amend merge commits or recursively,
this can be added at a later time.
author | Idan Kamara <idankk86@gmail.com> |
---|---|
date | Wed, 18 Apr 2012 01:20:16 +0300 |
parents | 6883c2363f44 |
children | e596a631210e |
line wrap: on
line diff
--- 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()