Mercurial > public > mercurial-scm > evolve
diff hgext/evolution.py @ 82:8108d566a8b5
[evolution]?imported hg amend from parren works
(this rocks heavily)
author | Pierre-Yves David <pierre-yves.david@ens-lyon.org> |
---|---|
date | Mon, 19 Sep 2011 01:11:10 +0200 |
parents | 5cec25ce019c |
children | 246b8fefd0a5 |
line wrap: on
line diff
--- a/hgext/evolution.py Wed Sep 14 22:29:19 2011 +0200 +++ b/hgext/evolution.py Mon Sep 19 01:11:10 2011 +0200 @@ -15,7 +15,10 @@ from mercurial import error from mercurial import extensions from mercurial import commands +from mercurial import bookmarks +from mercurial import context from mercurial.i18n import _ +from mercurial.commands import walkopts, commitopts, commitopts2, logopts ### util function ############################# @@ -33,6 +36,74 @@ except KeyError: raise error.Abort(_('evolution extension require obsolete extension.')) +### changeset rewriting logic +############################# + +def rewrite(repo, old, updates, head, newbases, commitopts): + if len(old.parents()) > 1: #XXX remove this unecessary limitation. + raise error.Abort(_('cannot amend merge changesets')) + base = old.p1() + bm = bookmarks.readcurrent(repo) + + wlock = repo.wlock() + try: + + # commit a new version of the old changeset, including the update + # collect all files which might be affected + files = set(old.files()) + for u in updates: + files.update(u.files()) + # prune files which were reverted by the updates + def samefile(f): + if f in head.manifest(): + a = head.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 = head.manifest() + def filectxfn(repo, ctx, path): + if path in headmf: + return head.filectx(path) + raise IOError() + new = context.memctx(repo, + parents=newbases, + text=commitopts.get('message') or old.description(), + files=files, + filectxfn=filectxfn, + user=commitopts.get('user') or None, + date=commitopts.get('date') or None, + extra=commitopts.get('extra') or None) + newid = repo.commitctx(new) + new = repo[newid] + + # update the bookmark + if bm: + repo._bookmarks[bm] = newid + bookmarks.write(repo) + + # hide obsolete csets + repo.changelog.hiddeninit = False + + # add evolution metadata + repo.addobsolete(new.node(), old.node()) + for u in updates: + repo.addobsolete(u.node(), old.node()) + repo.addobsolete(new.node(), u.node()) + + finally: + wlock.release() + + return newid + + ### new command ############################# cmdtable = {} @@ -68,3 +139,78 @@ finally: wlock.release() +@command('^amend', + [('A', 'addremove', None, + _('mark new/missing files as added/removed before committing')), + ('n', 'note', '', + _('use text as commit message for this update')), + ('c', 'change', '', + _('specifies the changeset to amend'), _('REV')) + ] + walkopts + commitopts + commitopts2, + _('[OPTION]... [FILE]...')) + +def amend(ui, repo, *pats, **opts): + """combine a changeset with updates and replace it with a new one + + Commits a new changeset incorporating both the changes to the given files + and all the changes from the current parent changeset into the repository. + + See :hg:`commit` for details about committing changes. + + If you don't specify -m, the parent's message will be reused. + + If you specify --change, amend additionally considers all changesets between + the indicated changeset and the working copy parent as updates to be subsumed. + This allows you to commit updates manually first. As a special shorthand you + can say `--amend .` instead of '--amend p1(p1())', which subsumes your latest + commit as an update of its parent. + + Behind the scenes, Mercurial first commits the update as a regular child + of the current parent. Then it creates a new commit on the parent's parents + with the updated contents. Then it changes the working copy parent to this + new combined changeset. Finally, the old changeset and its update are hidden + from :hg:`log` (unless you use --hidden with log). + + Returns 0 on success, 1 if nothing changed. + """ + + # determine updates to subsume + change = opts.get('change') + if change == '.': + change = 'p1(p1())' + old = scmutil.revsingle(repo, change) + + wlock = repo.wlock() + try: + + # commit current changes as update + # code copied from commands.commit to avoid noisy messages + ciopts = dict(opts) + ciopts['message'] = opts.get('note') or ('amends %s' % old.hex()) + e = cmdutil.commiteditor + if ciopts.get('force_editor'): + e = cmdutil.commitforceeditor + def commitfunc(ui, repo, message, match, opts): + return repo.commit(message, opts.get('user'), opts.get('date'), match, + editor=e) + cmdutil.commit(ui, repo, commitfunc, pats, ciopts) + + # find all changesets to be considered updates + cl = repo.changelog + head = repo['.'] + updatenodes = set(cl.nodesbetween(roots=[old.node()], + heads=[head.node()])[0]) + updatenodes.remove(old.node()) + if not updatenodes: + raise error.Abort(_('no updates found')) + updates = [repo[n] for n in updatenodes] + + # perform amend + newid = rewrite(repo, old, updates, head, + [old.p1().node(), old.p2().node()], opts) + + # reroute the working copy parent to the new changeset + repo.dirstate.setparents(newid, node.nullid) + + finally: + wlock.release()