hgext/evolve.py
changeset 1313 31e96036acd3
parent 1303 508f9911b042
parent 1311 d6a740947197
child 1314 d13d97363521
--- 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'