rename evolution to evolve
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Fri, 17 Feb 2012 10:29:01 +0100
changeset 133 aa182b912d62
parent 132 64d16f07d67f
child 134 70c9e415242b
rename evolution to evolve too much confusion with the email client
hgext/evolution.py
hgext/evolve.py
--- a/hgext/evolution.py	Tue Jan 24 09:53:34 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,386 +0,0 @@
-# states.py - introduce the state concept for mercurial changeset
-#
-# Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
-#                Logilab SA        <contact@logilab.fr>
-#                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-'''A set of command to make changeset evolve.'''
-
-from mercurial import cmdutil
-from mercurial import scmutil
-from mercurial import node
-from mercurial import error
-from mercurial import extensions
-from mercurial import commands
-from mercurial import bookmarks
-from mercurial import phases
-from mercurial import context
-from mercurial import commands
-from mercurial import util
-from mercurial.i18n import _
-from mercurial.commands import walkopts, commitopts, commitopts2, logopt
-from mercurial import hg
-
-### util function
-#############################
-def noderange(repo, revsets):
-    """The same as revrange but return node"""
-    return map(repo.changelog.node,
-               scmutil.revrange(repo, revsets))
-
-### extension check
-#############################
-
-def extsetup(ui):
-    try:
-        obsolete = extensions.find('obsolete')
-    except KeyError:
-        raise error.Abort(_('evolution extension require obsolete extension.'))
-    try:
-        rebase = extensions.find('rebase')
-    except KeyError:
-        raise error.Abort(_('evolution extension require rebase 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()
-        if commitopts.get('message') and commitopts.get('logfile'):
-            raise util.Abort(_('options --message and --logfile are mutually'
-                               ' exclusive'))
-        if commitopts.get('logfile'):
-            message= open(commitopts['logfile']).read()
-        elif commitopts.get('message'):
-            message = commitopts['message']
-        else:
-            message = old.description()
-
-
-
-        new = context.memctx(repo,
-                             parents=newbases,
-                             text=message,
-                             files=files,
-                             filectxfn=filectxfn,
-                             user=commitopts.get('user') or None,
-                             date=commitopts.get('date') or None,
-                             extra=commitopts.get('extra') or None)
-
-        if commitopts.get('edit'):
-            new._text = cmdutil.commitforceeditor(repo, new, [])
-        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
-
-def relocate(repo, rev, dest):
-    """rewrite <rev> on dest"""
-    try:
-        rebase = extensions.find('rebase')
-        # dummy state to trick rebase node
-        assert repo[rev].p2().rev() == node.nullrev, 'no support yet'
-        cmdutil.duplicatecopies(repo, rev, repo[dest].node(),
-                                         repo[rev].p2().node())
-        rebase.rebasenode(repo, rev, dest, {node.nullrev: node.nullrev})
-        nodenew = rebase.concludenode(repo, rev, dest, node.nullid)
-        nodesrc = repo.changelog.node(rev)
-        repo.addobsolete(nodenew, nodesrc)
-        phases.retractboundary(repo, repo[nodesrc].phase(), [nodenew])
-        oldbookmarks = repo.nodebookmarks(nodesrc)
-        for book in oldbookmarks:
-            repo._bookmarks[book] = nodenew
-        if oldbookmarks:
-            bookmarks.write(repo)
-    except util.Abort:
-        # Invalidate the previous setparents
-        repo.dirstate.invalidate()
-        raise
-
-
-
-### new command
-#############################
-cmdtable = {}
-command = cmdutil.command(cmdtable)
-
-@command('^evolve',
-    [],
-    '')
-def evolve(ui, repo):
-    """suggest the next evolution step"""
-    obsolete = extensions.find('obsolete')
-    next = min(obsolete.unstables(repo))
-    obs = repo[next].parents()[0]
-    if not obs.obsolete():
-        obs = next.parents()[1]
-    assert obs.obsolete()
-    newer = obsolete.newerversion(repo, obs.node())
-    target = newer[-1]
-    repo.ui.status('hg relocate --rev %s %s\n' % (repo[next], repo[target]))
-
-shorttemplate = '[{rev}] {desc|firstline}\n'
-
-@command('^gdown',
-    [],
-    'update to working directory parent an display summary lines')
-def cmdgdown(ui, repo):
-    wkctx = repo[None]
-    wparents = wkctx.parents()
-    if len(wparents) != 1:
-        raise util.Abort('merge in progress')
-
-    parents = wparents[0].parents()
-    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    if len(parents) == 1:
-        p = parents[0]
-        hg.update(repo, p.rev())
-        displayer.show(p)
-        return 0
-    else:
-        for p in parents:
-            displayer.show(p)
-        ui.warn(_('multiple parents, explicitly update to one\n'))
-        return 1
-
-@command('^gup',
-    [],
-    'update to working directory children an display summary lines')
-def cmdup(ui, repo):
-    wkctx = repo[None]
-    wparents = wkctx.parents()
-    if len(wparents) != 1:
-        raise util.Abort('merge in progress')
-
-    children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()]
-    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    if not children:
-        ui.warn(_('No non-obsolete children\n'))
-        return 1
-    if len(children) == 1:
-        c = children[0]
-        hg.update(repo, c.rev())
-        displayer.show(c)
-        return 0
-    else:
-        for c in children:
-            displayer.show(c)
-        ui.warn(_('Multiple non-obsolete children, explicitly update to one\n'))
-        return 1
-
-
-@command('^kill',
-    [
-    ('n', 'new', [], _("New changeset that justify this one to be killed"))
-    ],
-    '<revs>')
-def kill(ui, repo, *revs, **opts):
-    """mark a changeset as obsolete
-
-    This update the parent directory to a not-killed parent if the current
-    working directory parent are killed.
-
-    XXX bookmark support
-    XXX handle merge
-    XXX check immutable first
-    """
-    wlock = repo.wlock()
-    try:
-        new = opts['new']
-        targetnodes = set(noderange(repo, revs))
-        if not new:
-            new = [node.nullid]
-        for n in targetnodes:
-            if not repo[n].mutable():
-                ui.warn(_("Can't kill immutable changeset %s") % repo[n])
-            else:
-                for ne in new:
-                    repo.addobsolete(ne, n)
-        # update to an unkilled parent
-        wdp = repo['.']
-        newnode = wdp
-        while newnode.obsolete():
-            newnode = newnode.parents()[0]
-        if newnode.node() != wdp.node():
-            commands.update(ui, repo, newnode.rev())
-            ui.status(_('working directory now at %s\n') % newnode)
-
-    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')),
-    ('b', 'branch', '',
-     _('specifies a branch for the new.'), _('REV')),
-    ('e', 'edit', False,
-     _('edit commit message.'), _('')),
-    ] + 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)
-    branch = opts.get('branch')
-    if branch:
-        opts.setdefault('extra', {})['branch'] = branch
-    else:
-        if old.branch() != 'default':
-            opts.setdefault('extra', {})['branch'] = old.branch()
-
-    lock = repo.lock()
-    try:
-        wlock = repo.wlock()
-        try:
-            if not old.phase():
-                raise util.Abort(_("can not rewrite immutable changeset %s") % old)
-
-            # commit current changes as update
-            # code copied from commands.commit to avoid noisy messages
-            ciopts = dict(opts)
-            ciopts.pop('message', None)
-            ciopts.pop('logfile', None)
-            ciopts['message'] = opts.get('note') or ('amends %s' % old.hex())
-            e = cmdutil.commiteditor
-            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 and not (opts.get('message') or opts.get('logfile') or opts.get('edit')):
-                raise error.Abort(_('no updates found'))
-            updates = [repo[n] for n in updatenodes]
-
-            # perform amend
-            if opts.get('edit'):
-                opts['force_editor'] = True
-            newid = rewrite(repo, old, updates, head,
-                            [old.p1().node(), old.p2().node()], opts)
-
-            # reroute the working copy parent to the new changeset
-            phases.retractboundary(repo, old.phase(), [newid])
-            repo.dirstate.setparents(newid, node.nullid)
-        finally:
-            wlock.release()
-    finally:
-        lock.release()
-
-def commitwrapper(orig, ui, repo, *arg, **kwargs):
-    obsoleted = kwargs.get('obsolete', [])
-    if obsoleted:
-        obsoleted = repo.set('%lr', obsoleted)
-    result = orig(ui, repo, *arg, **kwargs)
-    if not result: # commit successed
-        new = repo['-1']
-        for old in obsoleted:
-            repo.addobsolete(new.node(), old.node())
-    return result
-
-def graftwrapper(orig, ui, repo, *revs, **kwargs):
-    lock = repo.lock()
-    try:
-        if kwargs.get('old_obsolete'):
-            obsoleted = kwargs.setdefault('obsolete', [])
-            if kwargs['continue']:
-                obsoleted.extend(repo.opener.read('graftstate').splitlines())
-            else:
-                obsoleted.extend(revs)
-        return commitwrapper(orig, ui, repo,*revs, **kwargs)
-    finally:
-        lock.release()
-
-def extsetup(ui):
-    entry = extensions.wrapcommand(commands.table, 'commit', commitwrapper)
-    entry[1].append(('o', 'obsolete', [], _("this commit obsolet this revision")))
-    entry = extensions.wrapcommand(commands.table, 'graft', graftwrapper)
-    entry[1].append(('o', 'obsolete', [], _("this graft obsolet this revision")))
-    entry[1].append(('O', 'old-obsolete', False, _("graft result obsolete graft source")))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/evolve.py	Fri Feb 17 10:29:01 2012 +0100
@@ -0,0 +1,386 @@
+# states.py - introduce the state concept for mercurial changeset
+#
+# Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
+#                Logilab SA        <contact@logilab.fr>
+#                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+'''A set of command to make changeset evolve.'''
+
+from mercurial import cmdutil
+from mercurial import scmutil
+from mercurial import node
+from mercurial import error
+from mercurial import extensions
+from mercurial import commands
+from mercurial import bookmarks
+from mercurial import phases
+from mercurial import context
+from mercurial import commands
+from mercurial import util
+from mercurial.i18n import _
+from mercurial.commands import walkopts, commitopts, commitopts2, logopt
+from mercurial import hg
+
+### util function
+#############################
+def noderange(repo, revsets):
+    """The same as revrange but return node"""
+    return map(repo.changelog.node,
+               scmutil.revrange(repo, revsets))
+
+### extension check
+#############################
+
+def extsetup(ui):
+    try:
+        obsolete = extensions.find('obsolete')
+    except KeyError:
+        raise error.Abort(_('evolution extension require obsolete extension.'))
+    try:
+        rebase = extensions.find('rebase')
+    except KeyError:
+        raise error.Abort(_('evolution extension require rebase 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()
+        if commitopts.get('message') and commitopts.get('logfile'):
+            raise util.Abort(_('options --message and --logfile are mutually'
+                               ' exclusive'))
+        if commitopts.get('logfile'):
+            message= open(commitopts['logfile']).read()
+        elif commitopts.get('message'):
+            message = commitopts['message']
+        else:
+            message = old.description()
+
+
+
+        new = context.memctx(repo,
+                             parents=newbases,
+                             text=message,
+                             files=files,
+                             filectxfn=filectxfn,
+                             user=commitopts.get('user') or None,
+                             date=commitopts.get('date') or None,
+                             extra=commitopts.get('extra') or None)
+
+        if commitopts.get('edit'):
+            new._text = cmdutil.commitforceeditor(repo, new, [])
+        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
+
+def relocate(repo, rev, dest):
+    """rewrite <rev> on dest"""
+    try:
+        rebase = extensions.find('rebase')
+        # dummy state to trick rebase node
+        assert repo[rev].p2().rev() == node.nullrev, 'no support yet'
+        cmdutil.duplicatecopies(repo, rev, repo[dest].node(),
+                                         repo[rev].p2().node())
+        rebase.rebasenode(repo, rev, dest, {node.nullrev: node.nullrev})
+        nodenew = rebase.concludenode(repo, rev, dest, node.nullid)
+        nodesrc = repo.changelog.node(rev)
+        repo.addobsolete(nodenew, nodesrc)
+        phases.retractboundary(repo, repo[nodesrc].phase(), [nodenew])
+        oldbookmarks = repo.nodebookmarks(nodesrc)
+        for book in oldbookmarks:
+            repo._bookmarks[book] = nodenew
+        if oldbookmarks:
+            bookmarks.write(repo)
+    except util.Abort:
+        # Invalidate the previous setparents
+        repo.dirstate.invalidate()
+        raise
+
+
+
+### new command
+#############################
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+@command('^evolve',
+    [],
+    '')
+def evolve(ui, repo):
+    """suggest the next evolution step"""
+    obsolete = extensions.find('obsolete')
+    next = min(obsolete.unstables(repo))
+    obs = repo[next].parents()[0]
+    if not obs.obsolete():
+        obs = next.parents()[1]
+    assert obs.obsolete()
+    newer = obsolete.newerversion(repo, obs.node())
+    target = newer[-1]
+    repo.ui.status('hg relocate --rev %s %s\n' % (repo[next], repo[target]))
+
+shorttemplate = '[{rev}] {desc|firstline}\n'
+
+@command('^gdown',
+    [],
+    'update to working directory parent an display summary lines')
+def cmdgdown(ui, repo):
+    wkctx = repo[None]
+    wparents = wkctx.parents()
+    if len(wparents) != 1:
+        raise util.Abort('merge in progress')
+
+    parents = wparents[0].parents()
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+    if len(parents) == 1:
+        p = parents[0]
+        hg.update(repo, p.rev())
+        displayer.show(p)
+        return 0
+    else:
+        for p in parents:
+            displayer.show(p)
+        ui.warn(_('multiple parents, explicitly update to one\n'))
+        return 1
+
+@command('^gup',
+    [],
+    'update to working directory children an display summary lines')
+def cmdup(ui, repo):
+    wkctx = repo[None]
+    wparents = wkctx.parents()
+    if len(wparents) != 1:
+        raise util.Abort('merge in progress')
+
+    children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()]
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+    if not children:
+        ui.warn(_('No non-obsolete children\n'))
+        return 1
+    if len(children) == 1:
+        c = children[0]
+        hg.update(repo, c.rev())
+        displayer.show(c)
+        return 0
+    else:
+        for c in children:
+            displayer.show(c)
+        ui.warn(_('Multiple non-obsolete children, explicitly update to one\n'))
+        return 1
+
+
+@command('^kill',
+    [
+    ('n', 'new', [], _("New changeset that justify this one to be killed"))
+    ],
+    '<revs>')
+def kill(ui, repo, *revs, **opts):
+    """mark a changeset as obsolete
+
+    This update the parent directory to a not-killed parent if the current
+    working directory parent are killed.
+
+    XXX bookmark support
+    XXX handle merge
+    XXX check immutable first
+    """
+    wlock = repo.wlock()
+    try:
+        new = opts['new']
+        targetnodes = set(noderange(repo, revs))
+        if not new:
+            new = [node.nullid]
+        for n in targetnodes:
+            if not repo[n].mutable():
+                ui.warn(_("Can't kill immutable changeset %s") % repo[n])
+            else:
+                for ne in new:
+                    repo.addobsolete(ne, n)
+        # update to an unkilled parent
+        wdp = repo['.']
+        newnode = wdp
+        while newnode.obsolete():
+            newnode = newnode.parents()[0]
+        if newnode.node() != wdp.node():
+            commands.update(ui, repo, newnode.rev())
+            ui.status(_('working directory now at %s\n') % newnode)
+
+    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')),
+    ('b', 'branch', '',
+     _('specifies a branch for the new.'), _('REV')),
+    ('e', 'edit', False,
+     _('edit commit message.'), _('')),
+    ] + 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)
+    branch = opts.get('branch')
+    if branch:
+        opts.setdefault('extra', {})['branch'] = branch
+    else:
+        if old.branch() != 'default':
+            opts.setdefault('extra', {})['branch'] = old.branch()
+
+    lock = repo.lock()
+    try:
+        wlock = repo.wlock()
+        try:
+            if not old.phase():
+                raise util.Abort(_("can not rewrite immutable changeset %s") % old)
+
+            # commit current changes as update
+            # code copied from commands.commit to avoid noisy messages
+            ciopts = dict(opts)
+            ciopts.pop('message', None)
+            ciopts.pop('logfile', None)
+            ciopts['message'] = opts.get('note') or ('amends %s' % old.hex())
+            e = cmdutil.commiteditor
+            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 and not (opts.get('message') or opts.get('logfile') or opts.get('edit')):
+                raise error.Abort(_('no updates found'))
+            updates = [repo[n] for n in updatenodes]
+
+            # perform amend
+            if opts.get('edit'):
+                opts['force_editor'] = True
+            newid = rewrite(repo, old, updates, head,
+                            [old.p1().node(), old.p2().node()], opts)
+
+            # reroute the working copy parent to the new changeset
+            phases.retractboundary(repo, old.phase(), [newid])
+            repo.dirstate.setparents(newid, node.nullid)
+        finally:
+            wlock.release()
+    finally:
+        lock.release()
+
+def commitwrapper(orig, ui, repo, *arg, **kwargs):
+    obsoleted = kwargs.get('obsolete', [])
+    if obsoleted:
+        obsoleted = repo.set('%lr', obsoleted)
+    result = orig(ui, repo, *arg, **kwargs)
+    if not result: # commit successed
+        new = repo['-1']
+        for old in obsoleted:
+            repo.addobsolete(new.node(), old.node())
+    return result
+
+def graftwrapper(orig, ui, repo, *revs, **kwargs):
+    lock = repo.lock()
+    try:
+        if kwargs.get('old_obsolete'):
+            obsoleted = kwargs.setdefault('obsolete', [])
+            if kwargs['continue']:
+                obsoleted.extend(repo.opener.read('graftstate').splitlines())
+            else:
+                obsoleted.extend(revs)
+        return commitwrapper(orig, ui, repo,*revs, **kwargs)
+    finally:
+        lock.release()
+
+def extsetup(ui):
+    entry = extensions.wrapcommand(commands.table, 'commit', commitwrapper)
+    entry[1].append(('o', 'obsolete', [], _("this commit obsolet this revision")))
+    entry = extensions.wrapcommand(commands.table, 'graft', graftwrapper)
+    entry[1].append(('o', 'obsolete', [], _("this graft obsolet this revision")))
+    entry[1].append(('O', 'old-obsolete', False, _("graft result obsolete graft source")))