[PATCH RFC] commit: add --amend option to amend the parent changeset
Matt Mackall
mpm at selenic.com
Thu Feb 2 18:35:26 UTC 2012
On Thu, 2012-02-02 at 14:35 +0200, Idan Kamara wrote:
> # HG changeset patch
> # User Idan Kamara <idankk86 at gmail.com>
> # Date 1328185747 -7200
> # Branch stable
> # Node ID cc7dec00d5016f03acf789c35ce6ef50b204f0cb
> # Parent 0620421044a2bcaafd054a6ee454614888699de8
> commit: add --amend option to amend the parent changeset
>
> Commits a new changeset incorporating both the changes to the given files
> and all the changes from the current parent changeset into the repository.
Not sure what you mean by "incorporating" here. Is the result not a
snapshot of the working directory?
Does this do the right thing when amending merges? In particular,
preserving the very tricky to reproduce file-level history for merged
files?
> You cannot amend public changesets. Amending a changeset with children
> results in a warning (we might want to forbid this).
I assume this just creates a new head rather than implicitly rebasing
all the children. Seems like the former is a surprise and the latter is
overly-complex. So I think we should just forbid it.
> Behind the scenes, first commit the update as a regular child of the current
> parent. Then create a new commit on the parent's parents with the updated
> contents. Then change the working copy parent to this new combined changeset.
> Finally, strip the intermediate commit created in the beginning (might
> want to also strip the amended commit if it has no children).
>
> This is an RFC, which last discussed one year ago: http://markmail.org/message/skalggb4typm27um
>
> I think this attempt is less intrusive and more contained than the previous one
> (it was done quickly to see if there's interest, so still unfinished), with a
> much cleaner implementation (largely due to parren's work on evolution, thanks ;).
>
> It doesn't try to be too smart, and now that we have phases it should be safe.
>
> This is arguably one of the most common history editing operations. I don't
> think referring people to mq (or rollback, which is worse) for this is particularly good.
> It requires activating a 'heavy' extension and use new commands that the user
> isn't familiar with. This way the user is in familiar territory, knowing all the flags etc.
>
> diff --git a/mercurial/commands.py b/mercurial/commands.py
> --- a/mercurial/commands.py
> +++ b/mercurial/commands.py
> @@ -9,7 +9,7 @@
> from lock import release
> from i18n import _, gettext
> import os, re, difflib, time, tempfile, errno
> -import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
> +import hg, scmutil, util, revlog, extensions, copies, error, bookmarks, repair
> import patch, help, url, encoding, templatekw, discovery
> import archival, changegroup, cmdutil, hbisect
> import sshserver, hgweb, hgweb.server, commandserver
> @@ -1163,6 +1163,7 @@
> _('mark new/missing files as added/removed before committing')),
> ('', 'close-branch', None,
> _('mark a branch as closed, hiding it from the branch list')),
> + ('', 'amend', None, _('amend a commit')),
> ] + walkopts + commitopts + commitopts2 + subrepoopts,
> _('[OPTION]... [FILE]...'))
> def commit(ui, repo, *pats, **opts):
> @@ -1209,6 +1210,15 @@
> branch = repo[None].branch()
> bheads = repo.branchheads(branch)
>
> + if opts.get('amend'):
> + curr = repo['.']
> + if len(curr.parents()) > 1:
> + raise util.Abort(_('cannot amend merge changesets'))
> + if curr.phase() == phases.public:
> + raise util.Abort(_('cannot amend public changesets'))
> + if len(curr.children()):
> + ui.warn(_('warning: amending changeset with children'))
> +
> node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
> if not node:
> stat = repo.status(match=scmutil.match(repo[None], pats, opts))
> @@ -1222,6 +1232,68 @@
> ctx = repo[node]
> parents = ctx.parents()
>
> + if opts.get('amend'):
> + old = repo['.'].p1()
> + base = old.p1()
> + bm = bookmarks.readcurrent(repo)
> +
> + # commit a new version of the old changeset, including the update
> + # collect all files which might be affected
> + files = set(old.files())
> + files.update(ctx.files())
> + # prune files which were reverted by the updates
> + 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)]
> + # commit version of these files as defined by head
> + headmf = ctx.manifest()
> + def filectxfn(repo, ctx_, path):
> + if path in headmf:
> + return ctx.filectx(path)
> + raise IOError()
> + new = context.memctx(repo,
> + parents=[old.p1().node(), old.p2().node()],
> + text=opts.get('message') or old.description(),
> + files=files,
> + filectxfn=filectxfn,
> + user=opts.get('user'),
> + date=opts.get('date'),
> + extra=extra)
> + newid = repo.commitctx(new)
> +
> + wlock = repo.wlock()
> + try:
> + # reroute the working copy parent to the new changeset
> + repo.dirstate.setparents(newid, nullid)
> +
> + # update the bookmark
> + if bm:
> + repo._bookmarks[bm] = newid
> + bookmarks.write(repo)
> + finally:
> + wlock.release()
> +
> + ui.note('stripping intermediate commit %s\n' % ctx)
> + lock = repo.lock()
> + try:
> + # no backup
> + repair.strip(ui, repo, node, '')
> + finally:
> + lock.release()
> +
> + ctx = repo[newid]
> + parents = ctx.parents()
> +
> if (bheads and node not in bheads and not
> [x for x in parents if x.node() in bheads and x.branch() == branch]):
> ui.status(_('created new head\n'))
> diff --git a/tests/test-commit.t b/tests/test-commit.t
> --- a/tests/test-commit.t
> +++ b/tests/test-commit.t
> @@ -88,6 +88,36 @@
> dir/file
> committed changeset 4:49176991390e
>
> +commit --amend
> +
> + $ echo foo >> foo
> + $ hg commit -m 'base'
> + $ hg diff --nodates -c . foo
> + diff -r 49176991390e -r 1846759e3d5f foo
> + --- a/foo
> + +++ b/foo
> + @@ -1,2 +1,3 @@
> + foo
> + foo
> + +foo
> + $ echo bar >> foo
> + $ hg phase -r . -p
> + $ hg commit --amend
> + abort: cannot amend public changesets
> + [255]
> + $ hg phase -f -r . -d
> + $ hg commit --amend -m 'base amend'
> + created new head
> + $ hg diff --nodates -c . foo
> + diff -r 49176991390e -r 013d43f99c45 foo
> + --- a/foo
> + +++ b/foo
> + @@ -1,2 +1,4 @@
> + foo
> + foo
> + +foo
> + +bar
> +
> An empty date was interpreted as epoch origin
>
> $ echo foo >> foo
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel
--
Mathematics is the supreme nostalgia of our time.
More information about the Mercurial-devel
mailing list