diff -r 1218ad94a351 -r 31e96036acd3 hgext/evolve.py --- a/hgext/evolve.py Mon May 04 10:58:14 2015 -0700 +++ b/hgext/evolve.py Mon May 04 10:56:06 2015 -0700 @@ -45,6 +45,8 @@ except (ImportError, AttributeError): gboptslist = gboptsmap = None +# Flags for enabling optional parts of evolve +commandopt = 'allnewcommands' from mercurial import bookmarks from mercurial import cmdutil @@ -144,8 +146,11 @@ """ for cont, funcname, func in self._duckpunchers: setattr(cont, funcname, func) - for command, wrapper in self._commandwrappers: - extensions.wrapcommand(commands.table, command, wrapper) + for command, wrapper, opts in self._commandwrappers: + entry = extensions.wrapcommand(commands.table, command, wrapper) + if opts: + for short, long, val, msg in opts: + entry[1].append((short, long, val, msg)) for cont, funcname, wrapper in self._functionwrappers: extensions.wrapfunction(cont, funcname, wrapper) for c in self._uicallables: @@ -166,13 +171,20 @@ revset.symbols[name] = symbol for name, kw in self._templatekws: templatekw.keywords[name] = kw - for ext, command, wrapper in self._extcommandwrappers: + for ext, command, wrapper, opts in self._extcommandwrappers: if ext not in knownexts: - e = extensions.find(ext) - if e is None: - raise util.Abort('extension %s not found' % ext) + try: + e = extensions.find(ext) + except KeyError: + # Extension isn't enabled, so don't bother trying to wrap + # it. + continue knownexts[ext] = e.cmdtable - extensions.wrapcommand(knownexts[ext], commands, wrapper) + entry = extensions.wrapcommand(knownexts[ext], command, wrapper) + if opts: + for short, long, val, msg in opts: + entry[1].append((short, long, val, msg)) + for c in self._extcallables: c(ui) @@ -260,7 +272,7 @@ return keyword return dec - def wrapcommand(self, command, extension=None): + def wrapcommand(self, command, extension=None, opts=[]): """Decorated function is a command wrapper The name of the command must be given as the decorator argument. @@ -279,12 +291,16 @@ ui.note('Barry!') return orig(ui, repo, *args, **kwargs) + The `opts` argument allows specifying additional arguments for the + command. + """ def dec(wrapper): if extension is None: - self._commandwrappers.append((command, wrapper)) + self._commandwrappers.append((command, wrapper, opts)) else: - self._extcommandwrappers.append((extension, command, wrapper)) + self._extcommandwrappers.append((extension, command, wrapper, + opts)) return wrapper return dec @@ -330,6 +346,31 @@ reposetup = eh.final_reposetup ##################################################################### +### Option configuration ### +##################################################################### + +@eh.reposetup # must be the first of its kin. +def _configureoptions(ui, repo): + # If no capabilities are specified, enable everything. + # This is so existing evolve users don't need to change their config. + evolveopts = ui.configlist('experimental', 'evolution') + if not evolveopts: + evolveopts = ['all'] + ui.setconfig('experimental', 'evolution', evolveopts) + +@eh.uisetup +def _configurecmdoptions(ui): + # Unregister evolve commands if the command capability is not specified. + # + # This must be in the same function as the option configuration above to + # guarantee it happens after the above configuration, but before the + # extsetup functions. + evolveopts = ui.configlist('experimental', 'evolution') + if evolveopts and (commandopt not in evolveopts and + 'all' not in evolveopts): + cmdtable.clear() + +##################################################################### ### experimental behavior ### ##################################################################### @@ -573,9 +614,16 @@ @eh.wrapcommand("pull") def wrapmayobsoletewc(origfn, ui, repo, *args, **opts): """Warn that the working directory parent is an obsolete changeset""" - res = origfn(ui, repo, *args, **opts) - if repo['.'].obsolete(): - ui.warn(_('working directory parent is obsolete!\n')) + def warnobsolete(): + if repo['.'].obsolete(): + ui.warn(_('working directory parent is obsolete!\n')) + wlock = None + try: + wlock = repo.wlock() + repo._afterlock(warnobsolete) + res = origfn(ui, repo, *args, **opts) + finally: + lockmod.release(wlock) return res # XXX this could wrap transaction code @@ -932,7 +980,11 @@ This function is loosely based on the extensions.wrapcommand function. ''' - aliases, entry = cmdutil.findcmd(newalias, cmdtable) + try: + aliases, entry = cmdutil.findcmd(newalias, cmdtable) + except error.UnknownCommand: + # Commands may be disabled + return for alias, e in cmdtable.iteritems(): if e is entry: break @@ -1420,6 +1472,9 @@ files = set() copied = copies.pathcopies(prec, bumped) precmanifest = prec.manifest() + # 3.3.2 needs a list. + # future 3.4 don't detect the size change during iteration + # this is fishy for key, val in list(bumped.manifest().iteritems()): precvalue = precmanifest.get(key, None) if precvalue is not None: @@ -1708,6 +1763,7 @@ [('n', 'new', [], _("successor changeset (DEPRECATED)")), ('s', 'succ', [], _("successor changeset")), ('r', 'rev', [], _("revisions to prune")), + ('k', 'keep', None, _("does not modify working copy during prune")), ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")), ('B', 'bookmark', '', _("remove revs only reachable from given" " bookmark"))] + metadataopts, @@ -1747,10 +1803,11 @@ if not revs: raise util.Abort(_('nothing to prune')) - wlock = lock = None + wlock = lock = tr = None try: wlock = repo.wlock() lock = repo.lock() + tr = repo.transaction('prune') # defines pruned changesets precs = [] revs.sort() @@ -1781,12 +1838,6 @@ if biject: relations = [(p, (s,)) for p, s in zip(precs, sucs)] - # create markers - obsolete.createmarkers(repo, relations, metadata=metadata) - - # informs that changeset have been pruned - ui.status(_('%i changesets pruned\n') % len(precs)) - wdp = repo['.'] if len(sucs) == 1 and len(precs) == 1 and wdp in precs: @@ -1796,15 +1847,43 @@ # update to an unkilled parent newnode = wdp - while newnode.obsolete(): + while newnode in precs or 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) + if opts.get('keep', False): + # This is largely the same as the implementation in + # strip.stripcmd(). We might want to refactor this somewhere + # common at some point. + + # only reset the dirstate for files that would actually change + # between the working context and uctx + descendantrevs = repo.revs("%d::." % newnode.rev()) + changedfiles = [] + for rev in descendantrevs: + # blindly reset the files, regardless of what actually changed + changedfiles.extend(repo[rev].files()) + + # reset files that only changed in the dirstate too + dirstate = repo.dirstate + dirchanges = [f for f in dirstate if dirstate[f] != 'n'] + changedfiles.extend(dirchanges) + repo.dirstate.rebuild(newnode.node(), newnode.manifest(), changedfiles) + repo.dirstate.write() + else: + commands.update(ui, repo, newnode.rev()) + ui.status(_('working directory now at %s\n') % newnode) # update bookmarks if bookmark: _deletebookmark(ui, marks, bookmark) + + # create markers + obsolete.createmarkers(repo, relations, metadata=metadata) + + # informs that changeset have been pruned + ui.status(_('%i changesets pruned\n') % len(precs)) + for ctx in repo.unfiltered().set('bookmark() and %ld', precs): # used to be: # @@ -1819,8 +1898,10 @@ updatebookmarks = _bookmarksupdater(repo, ctx.node()) updatebookmarks(dest.node()) break + + tr.close() finally: - lockmod.release(lock, wlock) + lockmod.release(tr, lock, wlock) @command('amend|refresh', [('A', 'addremove', None, @@ -2063,6 +2144,31 @@ finally: lockmod.release(lock, wlock) +@eh.wrapcommand('strip', extension='strip', opts=[ + ('', 'bundle', None, _("delete the commit entirely and move it to a " + "backup bundle")), + ]) +def stripwrapper(orig, ui, repo, *revs, **kwargs): + if (not ui.configbool('experimental', 'prunestrip') or + kwargs.get('bundle', False)): + return orig(ui, repo, *revs, **kwargs) + + if kwargs.get('force'): + ui.warn(_("warning: --force has no effect during strip with evolve " + "enabled\n")) + if kwargs.get('no_backup', False): + ui.warn(_("warning: --no-backup has no effect during strips with " + "evolve enabled\n")) + + revs = list(revs) + kwargs.pop('rev', []) + revs = set(scmutil.revrange(repo, revs)) + revs = repo.revs("(%ld)::", revs) + kwargs['rev'] = [] + kwargs['new'] = [] + kwargs['succ'] = [] + kwargs['biject'] = False + return cmdprune(ui, repo, *revs, **kwargs) + @command('^touch', [('r', 'rev', [], 'revision to update'), ('D', 'duplicate', False, @@ -2255,8 +2361,12 @@ @eh.extsetup def oldevolveextsetup(ui): for cmd in ['kill', 'uncommit', 'touch', 'fold']: - entry = extensions.wrapcommand(cmdtable, cmd, - warnobserrors) + try: + entry = extensions.wrapcommand(cmdtable, cmd, + warnobserrors) + except error.UnknownCommand: + # Commands may be disabled + continue entry = cmdutil.findcmd('commit', commands.table)[1] entry[1].append(('o', 'obsolete', [], @@ -2285,38 +2395,19 @@ topic = 'OBSEXC' ui.progress(topic, *args, **kwargs) -if getattr(exchange, '_pushdiscoveryobsmarkers', None) is not None: - @eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers') - def _pushdiscoveryobsmarkers(orig, pushop): - if (obsolete._enabled - and pushop.repo.obsstore - and 'obsolete' in pushop.remote.listkeys('namespaces')): - repo = pushop.repo - obsexcmsg(repo.ui, "computing relevant nodes\n") - revs = list(repo.revs('::%ln', pushop.futureheads)) - unfi = repo.unfiltered() - cl = unfi.changelog - if not pushop.remote.capable('_evoext_obshash_0'): - # do not trust core yet - # return orig(pushop) - nodes = [cl.node(r) for r in revs] - if nodes: - obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n" - % len(nodes)) - pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) - else: - obsexcmsg(repo.ui, "markers already in sync\n") - pushop.outobsmarkers = [] - pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) - return - - common = [] - obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" - % len(revs)) - commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads)) - common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, commonrevs) - - revs = list(unfi.revs('%ld - (::%ln)', revs, common)) +@eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers') +def _pushdiscoveryobsmarkers(orig, pushop): + if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt) + and pushop.repo.obsstore + and 'obsolete' in pushop.remote.listkeys('namespaces')): + repo = pushop.repo + obsexcmsg(repo.ui, "computing relevant nodes\n") + revs = list(repo.revs('::%ln', pushop.futureheads)) + unfi = repo.unfiltered() + cl = unfi.changelog + if not pushop.remote.capable('_evoext_obshash_0'): + # do not trust core yet + # return orig(pushop) nodes = [cl.node(r) for r in revs] if nodes: obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n" @@ -2325,12 +2416,30 @@ else: obsexcmsg(repo.ui, "markers already in sync\n") pushop.outobsmarkers = [] + pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) + return + + common = [] + obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" + % len(revs)) + commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads)) + common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, commonrevs) + + revs = list(unfi.revs('%ld - (::%ln)', revs, common)) + nodes = [cl.node(r) for r in revs] + if nodes: + obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n" + % len(nodes)) + pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) + else: + obsexcmsg(repo.ui, "markers already in sync\n") + pushop.outobsmarkers = [] @eh.wrapfunction(wireproto, 'capabilities') def discocapabilities(orig, repo, proto): """wrapper to advertise new capability""" caps = orig(repo, proto) - if obsolete._enabled: + if obsolete.isenabled(repo, obsolete.exchangeopt): caps += ' _evoext_obshash_0' return caps @@ -2490,7 +2599,7 @@ pushop.ui.debug('try to push obsolete markers to remote\n') repo = pushop.repo remote = pushop.remote - if (obsolete._enabled and repo.obsstore and + if (obsolete.isenabled(repo, obsolete.exchangeopt) and repo.obsstore and 'obsolete' in remote.listkeys('namespaces')): markers = pushop.outobsmarkers if not markers: @@ -2630,34 +2739,32 @@ kwargs['evo_obscommon'] = common return ret -if getattr(exchange, '_getbundleobsmarkerpart', None) is not None: - @eh.wrapfunction(exchange, '_getbundleobsmarkerpart') - def _getbundleobsmarkerpart(orig, bundler, repo, source, **kwargs): - if 'evo_obscommon' not in kwargs: - return orig(bundler, repo, source, **kwargs) - - heads = kwargs.get('heads') - if kwargs.get('obsmarkers', False): - if heads is None: - heads = repo.heads() - obscommon = kwargs.get('evo_obscommon', ()) - assert obscommon - obsset = repo.unfiltered().set('::%ln - ::%ln', heads, obscommon) - subset = [c.node() for c in obsset] - markers = repo.obsstore.relevantmarkers(subset) - exchange.buildobsmarkerspart(bundler, markers) - - @eh.uisetup - def installgetbundlepartgen(ui): - origfunc = exchange.getbundle2partsmapping['obsmarkers'] - def newfunc(*args, **kwargs): - return _getbundleobsmarkerpart(origfunc, *args, **kwargs) - exchange.getbundle2partsmapping['obsmarkers'] = newfunc - +@eh.wrapfunction(exchange, '_getbundleobsmarkerpart') +def _getbundleobsmarkerpart(orig, bundler, repo, source, **kwargs): + if 'evo_obscommon' not in kwargs: + return orig(bundler, repo, source, **kwargs) + + heads = kwargs.get('heads') + if kwargs.get('obsmarkers', False): + if heads is None: + heads = repo.heads() + obscommon = kwargs.get('evo_obscommon', ()) + assert obscommon + obsset = repo.unfiltered().set('::%ln - ::%ln', heads, obscommon) + subset = [c.node() for c in obsset] + markers = repo.obsstore.relevantmarkers(subset) + exchange.buildobsmarkerspart(bundler, markers) + +@eh.uisetup +def installgetbundlepartgen(ui): + origfunc = exchange.getbundle2partsmapping['obsmarkers'] + def newfunc(*args, **kwargs): + return _getbundleobsmarkerpart(origfunc, *args, **kwargs) + exchange.getbundle2partsmapping['obsmarkers'] = newfunc @eh.wrapfunction(exchange, '_pullobsolete') def _pullobsolete(orig, pullop): - if not obsolete._enabled: + if not obsolete.isenabled(pullop.repo, obsolete.exchangeopt): return None if 'obsmarkers' not in getattr(pullop, 'todosteps', ['obsmarkers']): return None @@ -2827,21 +2934,20 @@ _bestformat = max(obsolete.formats.keys()) -if getattr(obsolete, '_checkinvalidmarkers', None) is not None: - @eh.wrapfunction(obsolete, '_checkinvalidmarkers') - def _checkinvalidmarkers(orig, markers): - """search for marker with invalid data and raise error if needed - - Exist as a separated function to allow the evolve extension for a more - subtle handling. - """ - if 'debugobsconvert' in sys.argv: - return - for mark in markers: - if node.nullid in mark[1]: - raise util.Abort(_('bad obsolescence marker detected: ' - 'invalid successors nullid'), - hint=_('You should run `hg debugobsconvert`')) +@eh.wrapfunction(obsolete, '_checkinvalidmarkers') +def _checkinvalidmarkers(orig, markers): + """search for marker with invalid data and raise error if needed + + Exist as a separated function to allow the evolve extension for a more + subtle handling. + """ + if 'debugobsconvert' in sys.argv: + return + for mark in markers: + if node.nullid in mark[1]: + raise util.Abort(_('bad obsolescence marker detected: ' + 'invalid successors nullid'), + hint=_('You should run `hg debugobsconvert`')) @command( 'debugobsconvert', @@ -2876,7 +2982,7 @@ def capabilities(orig, repo, proto): """wrapper to advertise new capability""" caps = orig(repo, proto) - if obsolete._enabled: + if obsolete.isenabled(repo, obsolete.exchangeopt): caps += ' _evoext_pushobsmarkers_0' caps += ' _evoext_pullobsmarkers_0' caps += ' _evoext_obshash_0'