--- 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'