# HG changeset patch # User Pierre-Yves David # Date 1329470941 -3600 # Node ID aa182b912d624fe786f85b4e8ae474d14b71acc7 # Parent 64d16f07d67f13a2c41b569d6f84654c826174f5 rename evolution to evolve too much confusion with the email client diff -r 64d16f07d67f -r aa182b912d62 hgext/evolution.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 -# Logilab SA -# Pierre-Yves David -# -# 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 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")) - ], - '') -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"))) diff -r 64d16f07d67f -r aa182b912d62 hgext/evolve.py --- /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 +# Logilab SA +# Pierre-Yves David +# +# 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 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")) + ], + '') +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")))