--- 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'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/inhibit.py Mon May 04 10:56:06 2015 -0700
@@ -0,0 +1,280 @@
+"""Reduce the changesets evolution feature scope for early and noob friendly UI
+
+The full scale changeset evolution have some massive bleeding edge and it is
+very easy for people not very intimate with the concept to end up in intricate
+situation. In order to get some of the benefit sooner, this extension is
+disabling some of the less polished aspect of evolution. It should gradually
+get thinner and thinner as changeset evolution will get more polished. This
+extension is only recommended for large scale organisations. Individual user
+should probably stick on using Evolution in its current state, understand its
+concept and provide feedback
+
+The first feature provided by this extension is the ability to "inhibit"
+obsolescence markers. Obsolete revision can be cheaply brought back to life
+that way. However as the inhibitor are not fitting in an append only model,
+this is incompatible with sharing mutable history.
+
+The second feature is called direct access. It is the ability to refer and
+access hidden sha in commands provided that you know their value.
+For example hg log -r XXX where XXX is a commit has should work whether XXX is
+hidden or not as we assume that the user knows what he is doing when referring
+to XXX.
+"""
+from mercurial import localrepo
+from mercurial import obsolete
+from mercurial import extensions
+from mercurial import cmdutil
+from mercurial import scmutil
+from mercurial import repoview
+from mercurial import revset
+from mercurial import error
+from mercurial import commands
+from mercurial import lock as lockmod
+from mercurial import bookmarks
+from mercurial import lock as lockmod
+from mercurial.i18n import _
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+def reposetup(ui, repo):
+
+ class obsinhibitedrepo(repo.__class__):
+
+ @localrepo.storecache('obsinhibit')
+ def _obsinhibit(self):
+ # XXX we should make sure it is invalidated by transaction failure
+ obsinhibit = set()
+ raw = self.sopener.tryread('obsinhibit')
+ for i in xrange(0, len(raw), 20):
+ obsinhibit.add(raw[i:i+20])
+ return obsinhibit
+
+ def commit(self, *args, **kwargs):
+ newnode = super(obsinhibitedrepo, self).commit(*args, **kwargs)
+ _inhibitmarkers(repo, [newnode])
+ return newnode
+
+ # Wrapping this to inhibit obsolete revs resulting from a transaction
+ extensions.wrapfunction(localrepo.localrepository,
+ 'transaction', transactioncallback)
+
+ repo.__class__ = obsinhibitedrepo
+ repo._explicitaccess = set()
+
+
+def _update(orig, ui, repo, *args, **kwargs):
+ """
+ When moving to a commit we want to inhibit any obsolete commit affecting
+ the changeset we are updating to. In other words we don't want any visible
+ commit to be obsolete.
+ """
+ wlock = None
+ try:
+ # Evolve is running a hook on lock release to display a warning message
+ # if the workind dir's parent is obsolete.
+ # We take the lock here to make sure that we inhibit the parent before
+ # that hook get a chance to run.
+ wlock = repo.wlock()
+ res = orig(ui, repo, *args, **kwargs)
+ newhead = repo['.'].node()
+ _inhibitmarkers(repo, [newhead])
+ return res
+ finally:
+ lockmod.release(wlock)
+
+def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
+ """ Add inhibition markers to every obsolete bookmarks """
+ repo = bkmstoreinst._repo
+ bkmstorenodes = [repo[v].node() for v in bkmstoreinst.values()]
+ _inhibitmarkers(repo, bkmstorenodes)
+ return orig(bkmstoreinst, *args, **kwargs)
+
+def _bookmark(orig, ui, repo, *bookmarks, **opts):
+ """ Add a -D option to the bookmark command, map it to prune -B """
+ haspruneopt = opts.get('prune', False)
+ if not haspruneopt:
+ return orig(ui, repo, *bookmarks, **opts)
+
+ # Call prune -B
+ evolve = extensions.find('evolve')
+ optsdict = {
+ 'new': [],
+ 'succ': [],
+ 'rev': [],
+ 'bookmark': bookmarks[0],
+ 'keep': None,
+ 'biject': False,
+ }
+ evolve.cmdprune(ui, repo, **optsdict)
+
+# obsolescence inhibitor
+########################
+
+def _schedulewrite(tr, obsinhibit):
+ """Make sure on disk content will be updated on transaction commit"""
+ def writer(fp):
+ """Serialize the inhibited list to disk.
+ """
+ raw = ''.join(obsinhibit)
+ fp.write(raw)
+ tr.addfilegenerator('obsinhibit', ('obsinhibit',), writer)
+ tr.hookargs['obs_inbihited'] = '1'
+
+def _filterpublic(repo, nodes):
+ """filter out inhibitor on public changeset
+
+ Public changesets are already immune to obsolescence"""
+ getrev = repo.changelog.nodemap.get
+ getphase = repo._phasecache.phase
+ return (n for n in repo._obsinhibit if getphase(repo, getrev(n)))
+
+def _inhibitmarkers(repo, nodes):
+ """add marker inhibitor for all obsolete revision under <nodes>
+
+ Content of <nodes> and all mutable ancestors are considered. Marker for
+ obsolete revision only are created.
+ """
+ newinhibit = repo.set('::%ln and obsolete()', nodes)
+ if newinhibit:
+ lock = tr = None
+ try:
+ lock = repo.lock()
+ tr = repo.transaction('obsinhibit')
+ repo._obsinhibit.update(c.node() for c in newinhibit)
+ _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
+ repo.invalidatevolatilesets()
+ tr.close()
+ finally:
+ lockmod.release(tr, lock)
+
+def _deinhibitmarkers(repo, nodes):
+ """lift obsolescence inhibition on a set of nodes
+
+ This will be triggered when inhibited nodes received new obsolescence
+ markers. Otherwise the new obsolescence markers would also be inhibited.
+ """
+ deinhibited = repo._obsinhibit & set(nodes)
+ if deinhibited:
+ tr = repo.transaction('obsinhibit')
+ try:
+ repo._obsinhibit -= deinhibited
+ _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
+ repo.invalidatevolatilesets()
+ tr.close()
+ finally:
+ tr.release()
+
+def _createmarkers(orig, repo, relations, flag=0, date=None, metadata=None):
+ """wrap markers create to make sure we de-inhibit target nodes"""
+ # wrapping transactio to unify the one in each function
+ tr = repo.transaction('add-obsolescence-marker')
+ try:
+ orig(repo, relations, flag, date, metadata)
+ precs = (r[0].node() for r in relations)
+ _deinhibitmarkers(repo, precs)
+ tr.close()
+ finally:
+ tr.release()
+
+
+def transactioncallback(orig, repo, *args, **kwargs):
+ """ Wrap localrepo.transaction to inhibit new obsolete changes """
+ def inhibitposttransaction(transaction):
+ # At the end of the transaction we catch all the new visible and
+ # obsolete commit to inhibit them
+ visibleobsolete = repo.revs('(not hidden()) and obsolete()')
+ if visibleobsolete:
+ _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
+ transaction = orig(repo, *args, **kwargs)
+ transaction.addpostclose('inhibitposttransaction', inhibitposttransaction)
+ return transaction
+
+def extsetup(ui):
+ # lets wrap the computation of the obsolete set
+ # We apply inhibition there
+ obsfunc = obsolete.cachefuncs['obsolete']
+ def _computeobsoleteset(repo):
+ """remove any inhibited nodes from the obsolete set
+
+ This will trickle down to other part of mercurial (hidden, log, etc)"""
+ obs = obsfunc(repo)
+ getrev = repo.changelog.nodemap.get
+ for n in repo._obsinhibit:
+ obs.discard(getrev(n))
+ return obs
+ obsolete.cachefuncs['obsolete'] = _computeobsoleteset
+ # drop divergence computation since it is incompatible with "light revive"
+ obsolete.cachefuncs['divergent'] = lambda repo: set()
+ # drop bumped computation since it is incompatible with "light revive"
+ obsolete.cachefuncs['bumped'] = lambda repo: set()
+ # wrap create marker to make it able to lift the inhibition
+ extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)
+ extensions.wrapfunction(repoview, '_getdynamicblockers', _accessvisible)
+ extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook)
+ # wrap update to make sure that no obsolete commit is visible after an
+ # update
+ extensions.wrapcommand(commands.table, 'update', _update)
+ # There are two ways to save bookmark changes during a transation, we
+ # wrap both to add inhibition markers.
+ extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged)
+ extensions.wrapfunction(bookmarks.bmstore, 'write', _bookmarkchanged)
+ # Add bookmark -D option
+ entry = extensions.wrapcommand(commands.table, 'bookmark', _bookmark)
+ entry[1].append(('D','prune',None,
+ _('delete the bookmark and prune the commits underneath')))
+
+
+
+def gethashsymbols(tree):
+ # Returns the list of symbols of the tree that look like hashes
+ # for example for the revset 3::abe3ff it will return ('abe3ff')
+ if not tree:
+ return []
+
+ if len(tree) == 2 and tree[0] == "symbol":
+ try:
+ int(tree[1])
+ return []
+ except ValueError as e:
+ return [tree[1]]
+ elif len(tree) == 3:
+ return gethashsymbols(tree[1]) + gethashsymbols(tree[2])
+ else:
+ return []
+
+def _posttreebuilthook(orig, tree, repo):
+ # This is use to enabled direct hash access
+ # We extract the symbols that look like hashes and add them to the
+ # explicitaccess set
+ orig(tree, repo)
+ if repo is not None and repo.filtername == 'visible':
+ prelength = len(repo._explicitaccess)
+ repo.symbols = gethashsymbols(tree)
+ cl = repo.unfiltered().changelog
+ for node in repo.symbols:
+ try:
+ node = cl._partialmatch(node)
+ except error.LookupError:
+ node = None
+ if node is not None:
+ rev = cl.rev(node)
+ if rev not in repo.changelog:
+ repo._explicitaccess.add(rev)
+ if prelength != len(repo._explicitaccess):
+ repo.invalidatevolatilesets()
+
+@command('debugobsinhibit', [], '')
+def cmddebugobsinhibit(ui, repo, *revs):
+ """inhibit obsolescence markers effect on a set of revs"""
+ nodes = (repo[r].node() for r in scmutil.revrange(repo, revs))
+ _inhibitmarkers(repo, nodes)
+
+# ensure revision accessed by hash are visible
+###############################################
+
+def _accessvisible(orig, repo):
+ """ensure accessed revs stay visible"""
+ blockers = orig(repo)
+ blockers.update(getattr(repo, '_explicitaccess', ()))
+ return blockers
--- a/hgext/obsolete.py Mon May 04 10:58:14 2015 -0700
+++ b/hgext/obsolete.py Mon May 04 10:56:06 2015 -0700
@@ -14,8 +14,6 @@
try:
from mercurial import obsolete
- if not obsolete._enabled:
- obsolete._enabled = True
except ImportError:
raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)')
@@ -40,6 +38,10 @@
"""
if not repo.local():
return
+ evolveopts = ui.configlist('experimental', 'evolution')
+ if not evolveopts:
+ evolveopts = 'all'
+ ui.setconfig('experimental', 'evolution', evolveopts)
for arg in sys.argv:
if 'debugc' in arg:
break
--- a/hgext/pushexperiment.py Mon May 04 10:58:14 2015 -0700
+++ b/hgext/pushexperiment.py Mon May 04 10:56:06 2015 -0700
@@ -49,7 +49,8 @@
def syncpush(orig, repo, remote):
"""wraper for obsolete.syncpush to use the fast way if possible"""
- if not (obsolete._enabled and repo.obsstore):
+ if not (obsolete.isenabled(repo, obsolete.exchangeopt) and
+ repo.obsstore):
return
if remote.capable('_push_experiment_pushobsmarkers_0'):
return # already pushed before changeset
@@ -75,7 +76,7 @@
"""push wrapped that call the wire protocol command"""
if not remote.canpush():
raise util.Abort(_("destination does not support push"))
- if (obsolete._enabled and repo.obsstore
+ if (obsolete.isenabled(repo, obsolete.exchangeopt) and repo.obsstore
and remote.capable('_push_experiment_pushobsmarkers_0')):
# push marker early to limit damage of pushing too early.
try:
@@ -94,7 +95,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 += ' _push_experiment_pushobsmarkers_0'
caps += ' _push_experiment_notifypushend_0'
return caps
--- a/hgext/simple4server.py Mon May 04 10:58:14 2015 -0700
+++ b/hgext/simple4server.py Mon May 04 10:56:06 2015 -0700
@@ -12,7 +12,6 @@
buglink = 'https://bitbucket.org/marmoute/mutable-history/issues'
import mercurial.obsolete
-mercurial.obsolete._enabled = True
import struct
from mercurial import util
@@ -31,8 +30,6 @@
gboptslist = gboptsmap = None
try:
from mercurial import obsolete
- if not obsolete._enabled:
- obsolete._enabled = True
from mercurial import wireproto
gboptslist = getattr(wireproto, 'gboptslist', None)
gboptsmap = getattr(wireproto, 'gboptsmap', None)
@@ -247,7 +244,7 @@
"""wrapper to advertise new capability"""
caps = orig(repo, proto)
advertise = repo.ui.configbool('__temporary__', 'advertiseobsolete', True)
- if obsolete._enabled and advertise:
+ if obsolete.isenabled(repo, obsolete.exchangeopt) and advertise:
caps += ' _evoext_pushobsmarkers_0'
caps += ' _evoext_pullobsmarkers_0'
caps += ' _evoext_obshash_0'
@@ -303,3 +300,8 @@
extensions.wrapfunction(pushkey, '_nslist', _nslist)
pushkey._namespaces['namespaces'] = (lambda *x: False, pushkey._nslist)
+def reposetup(ui, repo):
+ evolveopts = ui.configlist('experimental', 'evolution')
+ if not evolveopts:
+ evolveopts = 'all'
+ ui.setconfig('experimental', 'evolution', evolveopts)
--- a/setup.py Mon May 04 10:58:14 2015 -0700
+++ b/setup.py Mon May 04 10:56:06 2015 -0700
@@ -1,6 +1,7 @@
# Copied from histedit setup.py
# Credit to Augie Fackler <durin42@gmail.com>
+import os
from distutils.core import setup
from os.path import dirname, join
@@ -14,6 +15,13 @@
if "'" in line:
return line.split("'")[1]
+py_modules = [
+ 'hgext.evolve',
+]
+
+if os.environ.get('INCLUDE_INHIBIT'):
+ py_modules.append('hgext.inhibit')
+
setup(
name='hg-evolve',
version=get_version('hgext/evolve.py'),
@@ -25,5 +33,5 @@
long_description=open('README').read(),
keywords='hg mercurial',
license='GPLv2+',
- py_modules=['hgext.evolve'],
+ py_modules=py_modules
)
--- a/tests/killdaemons.py Mon May 04 10:58:14 2015 -0700
+++ b/tests/killdaemons.py Mon May 04 10:56:06 2015 -0700
@@ -1,25 +1,91 @@
#!/usr/bin/env python
-import os, time, errno, signal
+import os, sys, time, errno, signal
+
+if os.name =='nt':
+ import ctypes
+
+ def _check(ret, expectederr=None):
+ if ret == 0:
+ winerrno = ctypes.GetLastError()
+ if winerrno == expectederr:
+ return True
+ raise ctypes.WinError(winerrno)
-# Kill off any leftover daemon processes
-try:
- fp = open(os.environ['DAEMON_PIDS'])
- for line in fp:
+ def kill(pid, logfn, tryhard=True):
+ logfn('# Killing daemon process %d' % pid)
+ PROCESS_TERMINATE = 1
+ PROCESS_QUERY_INFORMATION = 0x400
+ SYNCHRONIZE = 0x00100000
+ WAIT_OBJECT_0 = 0
+ WAIT_TIMEOUT = 258
+ handle = ctypes.windll.kernel32.OpenProcess(
+ PROCESS_TERMINATE|SYNCHRONIZE|PROCESS_QUERY_INFORMATION,
+ False, pid)
+ if handle == 0:
+ _check(0, 87) # err 87 when process not found
+ return # process not found, already finished
try:
- pid = int(line)
- except ValueError:
- continue
+ r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
+ if r == WAIT_OBJECT_0:
+ pass # terminated, but process handle still available
+ elif r == WAIT_TIMEOUT:
+ _check(ctypes.windll.kernel32.TerminateProcess(handle, -1))
+ else:
+ _check(r)
+
+ # TODO?: forcefully kill when timeout
+ # and ?shorter waiting time? when tryhard==True
+ r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
+ # timeout = 100 ms
+ if r == WAIT_OBJECT_0:
+ pass # process is terminated
+ elif r == WAIT_TIMEOUT:
+ logfn('# Daemon process %d is stuck')
+ else:
+ _check(r) # any error
+ except: #re-raises
+ ctypes.windll.kernel32.CloseHandle(handle) # no _check, keep error
+ raise
+ _check(ctypes.windll.kernel32.CloseHandle(handle))
+
+else:
+ def kill(pid, logfn, tryhard=True):
try:
os.kill(pid, 0)
+ logfn('# Killing daemon process %d' % pid)
os.kill(pid, signal.SIGTERM)
- for i in range(10):
- time.sleep(0.05)
+ if tryhard:
+ for i in range(10):
+ time.sleep(0.05)
+ os.kill(pid, 0)
+ else:
+ time.sleep(0.1)
os.kill(pid, 0)
+ logfn('# Daemon process %d is stuck - really killing it' % pid)
os.kill(pid, signal.SIGKILL)
except OSError, err:
if err.errno != errno.ESRCH:
raise
- fp.close()
-except IOError:
- pass
+
+def killdaemons(pidfile, tryhard=True, remove=False, logfn=None):
+ if not logfn:
+ logfn = lambda s: s
+ # Kill off any leftover daemon processes
+ try:
+ fp = open(pidfile)
+ for line in fp:
+ try:
+ pid = int(line)
+ except ValueError:
+ continue
+ kill(pid, logfn, tryhard)
+ fp.close()
+ if remove:
+ os.unlink(pidfile)
+ except IOError:
+ pass
+
+if __name__ == '__main__':
+ path, = sys.argv[1:]
+ killdaemons(path)
--- a/tests/test-evolve.t Mon May 04 10:58:14 2015 -0700
+++ b/tests/test-evolve.t Mon May 04 10:56:06 2015 -0700
@@ -69,9 +69,9 @@
$ hg id -n
5
$ hg kill .
- 1 changesets pruned
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
working directory now at fbb94e3a0ecf
+ 1 changesets pruned
$ hg qlog
4 - fbb94e3a0ecf add e (draft)
3 - 47d2a3944de8 add d (draft)
@@ -82,9 +82,9 @@
test multiple kill
$ hg kill 4 -r 3
- 2 changesets pruned
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
working directory now at 7c3bad9141dc
+ 2 changesets pruned
$ hg qlog
2 - 4538525df7e2 add c (draft)
1 - 7c3bad9141dc add b (public)
@@ -97,9 +97,9 @@
$ echo 4 > g
$ hg add g
$ hg kill .
- 1 changesets pruned
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
working directory now at 7c3bad9141dc
+ 1 changesets pruned
$ hg st
A g
--- a/tests/test-exchange-D2.t Mon May 04 10:58:14 2015 -0700
+++ b/tests/test-exchange-D2.t Mon May 04 10:56:06 2015 -0700
@@ -37,9 +37,9 @@
created new head
$ hg debugobsolete `getid 'desc(A0)'` `getid 'desc(A1)'`
$ hg prune --date '0 0' .
- 1 changesets pruned
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
working directory now at a9bdc8b26820
+ 1 changesets pruned
$ hg strip --hidden -q 'desc(A1)'
$ hg log -G --hidden
x 28b51eb45704 (draft): A0
--- a/tests/test-exchange-D3.t Mon May 04 10:58:14 2015 -0700
+++ b/tests/test-exchange-D3.t Mon May 04 10:56:06 2015 -0700
@@ -39,9 +39,9 @@
$ mkcommit A1
$ hg debugobsolete `getid 'desc(A0)'` `getid 'desc(A1)'`
$ hg prune -d '0 0' .
- 1 changesets pruned
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
working directory now at 35b183996678
+ 1 changesets pruned
$ hg strip --hidden -q 'desc(A1)'
$ hg log -G --hidden
@ 35b183996678 (draft): B
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-inhibit.t Mon May 04 10:56:06 2015 -0700
@@ -0,0 +1,541 @@
+ $ cat >> $HGRCPATH <<EOF
+ > [ui]
+ > logtemplate = {rev}:{node|short} {desc}\n
+ > [extensions]
+ > rebase=
+ > EOF
+ $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+ $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext/inhibit.py" >> $HGRCPATH
+ $ mkcommit() {
+ > echo "$1" > "$1"
+ > hg add "$1"
+ > hg ci -m "add $1"
+ > }
+
+ $ hg init inhibit
+ $ cd inhibit
+ $ mkcommit cA
+ $ mkcommit cB
+ $ mkcommit cC
+ $ mkcommit cD
+ $ hg up 'desc(cA)'
+ 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+ $ mkcommit cE
+ created new head
+ $ mkcommit cG
+ $ mkcommit cH
+ $ mkcommit cJ
+ $ hg log -G
+ @ 7:18214586bf78 add cJ
+ |
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ | o 3:2db36d8066ff add cD
+ | |
+ | o 2:7df62a38b9bf add cC
+ | |
+ | o 1:02bcbc3f6e56 add cB
+ |/
+ o 0:54ccbc537fc2 add cA
+
+
+plain prune
+
+ $ hg prune 1::
+ 3 changesets pruned
+ $ hg log -G
+ @ 7:18214586bf78 add cJ
+ |
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+ $ hg debugobsinhibit --hidden 1::
+ $ hg log -G
+ @ 7:18214586bf78 add cJ
+ |
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ | o 3:2db36d8066ff add cD
+ | |
+ | o 2:7df62a38b9bf add cC
+ | |
+ | o 1:02bcbc3f6e56 add cB
+ |/
+ o 0:54ccbc537fc2 add cA
+
+ $ hg prune --hidden 1::
+ 3 changesets pruned
+ $ hg log -G
+ @ 7:18214586bf78 add cJ
+ |
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+
+after amend
+
+ $ echo babar > cJ
+ $ hg amend
+ $ hg log -G
+ @ 9:55c73a90e4b4 add cJ
+ |
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+ $ hg debugobsinhibit --hidden 18214586bf78
+ $ hg log -G
+ @ 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+
+and no divergence
+
+ $ hg summary
+ parent: 9:55c73a90e4b4 tip
+ add cJ
+ branch: default
+ commit: (clean)
+ update: 1 new changesets, 2 branch heads (merge)
+
+check public revision got cleared
+(when adding the second inhibitor, the first one is removed because it is public)
+
+ $ wc -m .hg/store/obsinhibit | sed -e 's/^[ \t]*//'
+ 20 .hg/store/obsinhibit
+ $ hg prune 7
+ 1 changesets pruned
+ $ hg debugobsinhibit --hidden 18214586bf78
+ $ wc -m .hg/store/obsinhibit | sed -e 's/^[ \t]*//'
+ 20 .hg/store/obsinhibit
+ $ hg log -G
+ @ 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+ $ hg phase --public 7
+ $ hg prune 9
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ working directory now at cf5c4f4554ce
+ 1 changesets pruned
+ $ hg log -G
+ o 7:18214586bf78 add cJ
+ |
+ @ 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+ $ hg debugobsinhibit --hidden 55c73a90e4b4
+ $ wc -m .hg/store/obsinhibit | sed -e 's/^[ \t]*//'
+ 20 .hg/store/obsinhibit
+ $ hg log -G
+ o 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ @ 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+Update should inhibit all related unstable commits
+
+ $ hg update 2 --hidden
+ 2 files updated, 0 files merged, 3 files removed, 0 files unresolved
+ $ hg log -G
+ o 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ | @ 2:7df62a38b9bf add cC
+ | |
+ | o 1:02bcbc3f6e56 add cB
+ |/
+ o 0:54ccbc537fc2 add cA
+
+
+ $ hg update 9
+ 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ $ hg log -G
+ @ 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ | o 2:7df62a38b9bf add cC
+ | |
+ | o 1:02bcbc3f6e56 add cB
+ |/
+ o 0:54ccbc537fc2 add cA
+
+ $ hg prune --hidden 1::
+ 3 changesets pruned
+ $ hg log -G
+ @ 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+
+Bookmark should inhibit all related unstable commits
+ $ hg bookmark -r 2 book1 --hidden
+ $ hg log -G
+ @ 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ | o 2:7df62a38b9bf add cC
+ | |
+ | o 1:02bcbc3f6e56 add cB
+ |/
+ o 0:54ccbc537fc2 add cA
+
+
+Removing a bookmark with bookmark -D should prune the changes underneath
+that are not reachable from another bookmark or head
+
+ $ hg bookmark -r 1 book2
+ $ hg bookmark -D book1 --config experimental.evolution=createmarkers #--config to make sure prune is not registered as a command.
+ bookmark 'book1' deleted
+ 1 changesets pruned
+ $ hg log -G
+ @ 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ | o 1:02bcbc3f6e56 add cB
+ |/
+ o 0:54ccbc537fc2 add cA
+
+ $ hg bookmark -D book2
+ bookmark 'book2' deleted
+ 1 changesets pruned
+ $ hg log -G
+ @ 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+Test that direct access make changesets visible
+
+ $ hg export 2db36d8066ff 02bcbc3f6e56
+ # HG changeset patch
+ # User test
+ # Date 0 0
+ # Thu Jan 01 00:00:00 1970 +0000
+ # Node ID 2db36d8066ff50e8be3d3e6c2da1ebc0a8381d82
+ # Parent 7df62a38b9bf9daf968de235043ba88a8ef43393
+ add cD
+
+ diff -r 7df62a38b9bf -r 2db36d8066ff cD
+ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+ +++ b/cD Thu Jan 01 00:00:00 1970 +0000
+ @@ -0,0 +1,1 @@
+ +cD
+ # HG changeset patch
+ # User test
+ # Date 0 0
+ # Thu Jan 01 00:00:00 1970 +0000
+ # Node ID 02bcbc3f6e56fb2928efec2c6e24472720bf5511
+ # Parent 54ccbc537fc2d6845a5d61337c1cfb80d1d2815e
+ add cB
+
+ diff -r 54ccbc537fc2 -r 02bcbc3f6e56 cB
+ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+ +++ b/cB Thu Jan 01 00:00:00 1970 +0000
+ @@ -0,0 +1,1 @@
+ +cB
+
+But only with hash
+
+ $ hg export 2db36d8066ff::
+ # HG changeset patch
+ # User test
+ # Date 0 0
+ # Thu Jan 01 00:00:00 1970 +0000
+ # Node ID 2db36d8066ff50e8be3d3e6c2da1ebc0a8381d82
+ # Parent 7df62a38b9bf9daf968de235043ba88a8ef43393
+ add cD
+
+ diff -r 7df62a38b9bf -r 2db36d8066ff cD
+ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+ +++ b/cD Thu Jan 01 00:00:00 1970 +0000
+ @@ -0,0 +1,1 @@
+ +cD
+
+ $ hg export 1 3
+ abort: hidden revision '1'!
+ (use --hidden to access hidden revisions)
+ [255]
+
+
+With severals hidden sha, rebase of one hidden stack onto another one:
+ $ hg update -C 0
+ 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+ $ mkcommit cK
+ created new head
+ $ mkcommit cL
+ $ hg update -C 9
+ 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ $ hg log -G
+ o 11:53a94305e133 add cL
+ |
+ o 10:ad78ff7d621f add cK
+ |
+ | @ 9:55c73a90e4b4 add cJ
+ | |
+ | | o 7:18214586bf78 add cJ
+ | |/
+ | o 6:cf5c4f4554ce add cH
+ | |
+ | o 5:5419eb264a33 add cG
+ | |
+ | o 4:98065434e5c6 add cE
+ |/
+ o 0:54ccbc537fc2 add cA
+
+ $ hg prune 10:
+ 2 changesets pruned
+ $ hg log -G
+ @ 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+ $ hg rebase -s 10 -d 3
+ abort: hidden revision '3'!
+ (use --hidden to access hidden revisions)
+ [255]
+ $ hg rebase -r ad78ff7d621f -r 53a94305e133 -d 2db36d8066ff
+ rebasing 10:ad78ff7d621f "add cK"
+ rebasing 11:53a94305e133 "add cL"
+ $ hg log -G
+ o 13:2f7b7704d714 add cL
+ |
+ o 12:fe1634cbe235 add cK
+ |
+ | o 11:53a94305e133 add cL
+ | |
+ | o 10:ad78ff7d621f add cK
+ | |
+ | | @ 9:55c73a90e4b4 add cJ
+ | | |
+ | | | o 7:18214586bf78 add cJ
+ | | |/
+ | | o 6:cf5c4f4554ce add cH
+ | | |
+ | | o 5:5419eb264a33 add cG
+ | | |
+ | | o 4:98065434e5c6 add cE
+ | |/
+ o | 3:2db36d8066ff add cD
+ | |
+ o | 2:7df62a38b9bf add cC
+ | |
+ o | 1:02bcbc3f6e56 add cB
+ |/
+ o 0:54ccbc537fc2 add cA
+
+Check that amending in the middle of a stack does not show obsolete revs
+
+ $ hg prune 1::
+ 5 changesets pruned
+ $ hg prune 10::
+ 2 changesets pruned
+ $ hg log -G
+ @ 9:55c73a90e4b4 add cJ
+ |
+ | o 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+ $ hg up 7
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ mkcommit cL
+ $ mkcommit cM
+ $ mkcommit cN
+ $ hg log -G
+ @ 16:a438c045eb37 add cN
+ |
+ o 15:2d66e189f5b5 add cM
+ |
+ o 14:d66ccb8c5871 add cL
+ |
+ | o 9:55c73a90e4b4 add cJ
+ | |
+ o | 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+ $ hg up 15
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ echo "mmm" >> cM
+ $ hg amend
+ $ hg log -G
+ @ 18:210589181b14 add cM
+ |
+ | o 16:a438c045eb37 add cN
+ | |
+ | o 15:2d66e189f5b5 add cM
+ |/
+ o 14:d66ccb8c5871 add cL
+ |
+ | o 9:55c73a90e4b4 add cJ
+ | |
+ o | 7:18214586bf78 add cJ
+ |/
+ o 6:cf5c4f4554ce add cH
+ |
+ o 5:5419eb264a33 add cG
+ |
+ o 4:98065434e5c6 add cE
+ |
+ o 0:54ccbc537fc2 add cA
+
+Check that rebasing a commit twice makes the commit visible again
+
+ $ hg rebase -d 18 -r 16 --keep
+ rebasing 16:a438c045eb37 "add cN"
+ $ hg log -r 14:: -G
+ o 19:104eed5354c7 add cN
+ |
+ @ 18:210589181b14 add cM
+ |
+ | o 16:a438c045eb37 add cN
+ | |
+ | o 15:2d66e189f5b5 add cM
+ |/
+ o 14:d66ccb8c5871 add cL
+ |
+ $ hg prune -r 104eed5354c7
+ 1 changesets pruned
+ $ hg rebase -d 18 -r 16 --keep
+ rebasing 16:a438c045eb37 "add cN"
+ $ hg log -r 14:: -G
+ o 19:104eed5354c7 add cN
+ |
+ @ 18:210589181b14 add cM
+ |
+ | o 16:a438c045eb37 add cN
+ | |
+ | o 15:2d66e189f5b5 add cM
+ |/
+ o 14:d66ccb8c5871 add cL
+ |
+
+Test prunestrip
+
+ $ hg book foo -r 104eed5354c7
+ $ hg strip -r 210589181b14 --config experimental.prunestrip=True --config extensions.strip=
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ working directory now at d66ccb8c5871
+ 2 changesets pruned
+ $ hg log -r 14:: -G -T '{rev}:{node|short} {desc|firstline} {bookmarks}\n'
+ o 16:a438c045eb37 add cN
+ |
+ o 15:2d66e189f5b5 add cM
+ |
+ @ 14:d66ccb8c5871 add cL foo
+ |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-options.t Mon May 04 10:56:06 2015 -0700
@@ -0,0 +1,30 @@
+ $ cat >> $HGRCPATH <<EOF
+ > [ui]
+ > logtemplate={rev}:{node|short}[{bookmarks}] ({obsolete}/{phase}) {desc|firstline}\n
+ > [extensions]
+ > EOF
+ $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+
+ $ mkcommit() {
+ > echo "$1" > "$1"
+ > hg add "$1"
+ > hg ci -m "add $1"
+ > }
+
+ $ hg init repo
+ $ cd repo
+ $ mkcommit a
+ $ mkcommit b
+
+test disabling commands
+
+ $ cat >> .hg/hgrc <<EOF
+ > [experimental]
+ > evolution=createmarkers
+ > allowunstable
+ > exchange
+ > EOF
+ $ hg prune | head -n 2
+ hg: unknown command 'prune'
+ Mercurial Distributed SCM
+
--- a/tests/test-prune.t Mon May 04 10:58:14 2015 -0700
+++ b/tests/test-prune.t Mon May 04 10:56:06 2015 -0700
@@ -38,10 +38,10 @@
prune current and tip changeset
$ hg prune --user blah --date '1979-12-15' .
- 1 changesets pruned
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
(leaving bookmark BABAR)
working directory now at 47d2a3944de8
+ 1 changesets pruned
$ hg debugobsolete
9d206ffc875e1bc304590549be293be36821e66c 0 {47d2a3944de8b013de3be9578e8e344ea2e6c097} (Sat Dec 15 00:00:00 1979 +0000) {'user': 'blah'}
@@ -57,9 +57,9 @@
pruning multiple changeset at once
$ hg prune 2:
- 2 changesets pruned
0 files updated, 0 files merged, 3 files removed, 0 files unresolved
working directory now at 1f0dee641bb7
+ 2 changesets pruned
$ hg debugobsolete
9d206ffc875e1bc304590549be293be36821e66c 0 {47d2a3944de8b013de3be9578e8e344ea2e6c097} (Sat Dec 15 00:00:00 1979 +0000) {'user': 'blah'}
7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {1f0dee641bb7258c56bd60e93edfa2405381c41e} (*) {'user': 'test'} (glob)
@@ -118,9 +118,9 @@
$ hg up 'desc("add ee")'
4 files updated, 0 files merged, 4 files removed, 0 files unresolved
$ hg prune 'desc("add ee")' -s 'desc("add nE")'
- 1 changesets pruned
4 files updated, 0 files merged, 4 files removed, 0 files unresolved
working directory now at 6e8148413dd5
+ 1 changesets pruned
$ hg debugobsolete
9d206ffc875e1bc304590549be293be36821e66c 0 {47d2a3944de8b013de3be9578e8e344ea2e6c097} (Sat Dec 15 00:00:00 1979 +0000) {'user': 'blah'}
7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {1f0dee641bb7258c56bd60e93edfa2405381c41e} (*) {'user': 'test'} (glob)
@@ -208,9 +208,9 @@
$ mkcommit n2
$ hg prune 'desc("add n1")::desc("add n2")' -s 'desc("add nD")::desc("add nE")' --biject
- 2 changesets pruned
0 files updated, 0 files merged, 2 files removed, 0 files unresolved
working directory now at 1f0dee641bb7
+ 2 changesets pruned
$ hg debugobsolete
9d206ffc875e1bc304590549be293be36821e66c 0 {47d2a3944de8b013de3be9578e8e344ea2e6c097} (Sat Dec 15 00:00:00 1979 +0000) {'user': 'blah'}
7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {1f0dee641bb7258c56bd60e93edfa2405381c41e} (*) {'user': 'test'} (glob)
@@ -223,6 +223,35 @@
cb7f8f706a6532967b98cf8583a81baab79a0fa7 8ee176ff1d4b2034ce51e3efc579c2de346b631d 0 (*) {'user': 'test'} (glob)
21b6f2f1cece8c10326e575dd38239189d467190 6e8148413dd541855b72a920a90c06fca127c7e7 0 (*) {'user': 'test'} (glob)
+test hg strip replacement
+
+ $ hg up 10
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ mkcommit n1
+ created new head
+ $ mkcommit n2
+ $ hg --config extensions.strip= --config experimental.prunestrip=True strip -r .
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ working directory now at c7e58696a948
+ 1 changesets pruned
+ $ hg --config extensions.strip= --config experimental.prunestrip=True strip -r . --bundle
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/repo/.hg/strip-backup/c7e58696a948-69ca36d3-backup.hg (glob)
+
+test hg prune --keep
+ $ mkcommit n1
+ created new head
+ $ hg diff -r .^
+ diff -r aa96dc3f04c2 n1
+ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+ +++ b/n1 * +0000 (glob)
+ @@ -0,0 +1,1 @@
+ +n1
+ $ hg prune -r . --keep
+ 1 changesets pruned
+ $ hg status
+ ? n1
+
test hg prune -B bookmark
yoinked from test-mq-strip.t
@@ -243,11 +272,11 @@
[255]
$ hg tag --remove --local a
$ hg prune -B todelete
- 1 changesets pruned
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
(leaving bookmark todelete)
working directory now at d62d843c9a01
bookmark 'todelete' deleted
+ 1 changesets pruned
$ hg id -ir dcbb326fdec2
abort: hidden revision 'dcbb326fdec2'!
(use --hidden to access hidden revisions)
@@ -258,8 +287,8 @@
B 10:ff43616e5d0f
delete 6:2702dd0c91e7
$ hg prune -B delete
+ bookmark 'delete' deleted
3 changesets pruned
- bookmark 'delete' deleted
$ hg tag --remove --local c
$ hg id -ir 6:2702dd0c91e7
abort: hidden revision '6'!
--- a/tests/test-simple4server-bundle2.t Mon May 04 10:58:14 2015 -0700
+++ b/tests/test-simple4server-bundle2.t Mon May 04 10:56:06 2015 -0700
@@ -140,7 +140,7 @@
$ echo '[__temporary__]' >> server/.hg/hgrc
$ echo 'advertiseobsolete=False' >> server/.hg/hgrc
- $ $TESTDIR/killdaemons.py
+ $ $TESTDIR/killdaemons.py $DAEMON_PIDS
$ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
$ cat hg.pid >> $DAEMON_PIDS
@@ -148,13 +148,9 @@
bookmarks
namespaces
phases
- $ wget -q -O - http://localhost:$HGPORT/?cmd=hello
- capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
- $ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities
- lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 (no-eol)
$ echo 'advertiseobsolete=True' >> server/.hg/hgrc
- $ $TESTDIR/killdaemons.py
+ $ $TESTDIR/killdaemons.py $DAEMON_PIDS
$ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
$ cat hg.pid >> $DAEMON_PIDS
@@ -163,6 +159,7 @@
namespaces
obsolete
phases
+
$ wget -q -O - http://localhost:$HGPORT/?cmd=hello
capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 _evoext_pushobsmarkers_0 _evoext_pullobsmarkers_0 _evoext_obshash_0 _evoext_obshash_1 _evoext_getbundle_obscommon
$ wget -q -O - http://localhost:$HGPORT/?cmd=capabilities
--- a/tests/test-simple4server.t Mon May 04 10:58:14 2015 -0700
+++ b/tests/test-simple4server.t Mon May 04 10:56:06 2015 -0700
@@ -141,7 +141,7 @@
$ echo '[__temporary__]' >> server/.hg/hgrc
$ echo 'advertiseobsolete=False' >> server/.hg/hgrc
- $ $TESTDIR/killdaemons.py
+ $ $TESTDIR/killdaemons.py $DAEMON_PIDS
$ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
$ cat hg.pid >> $DAEMON_PIDS
@@ -155,7 +155,7 @@
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 (no-eol)
$ echo 'advertiseobsolete=True' >> server/.hg/hgrc
- $ $TESTDIR/killdaemons.py
+ $ $TESTDIR/killdaemons.py $DAEMON_PIDS
$ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
$ cat hg.pid >> $DAEMON_PIDS
--- a/tests/test-tutorial.t Mon May 04 10:58:14 2015 -0700
+++ b/tests/test-tutorial.t Mon May 04 10:56:06 2015 -0700
@@ -289,9 +289,9 @@
not fit well in my standard shopping list)
$ hg prune . # "." is for working directory parent
- 1 changesets pruned
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
working directory now at 41aff6a42b75
+ 1 changesets pruned
The silly changeset is gone.
@@ -804,9 +804,9 @@
In the mean time I noticed you can't buy animals in a super market and I prune the animal changeset:
$ hg prune ee942144f952
- 1 changesets pruned
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
working directory now at a44c85f957d3
+ 1 changesets pruned
1 new unstable changesets
--- a/tests/test-userguide.t Mon May 04 10:58:14 2015 -0700
+++ b/tests/test-userguide.t Mon May 04 10:56:06 2015 -0700
@@ -66,9 +66,9 @@
$ echo 'debug hack' >> file1.c
$ hg commit -m 'debug hack'
$ hg prune .
- 1 changesets pruned
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
working directory now at 934359450037
+ 1 changesets pruned
$ hg parents --template '{rev}:{node|short} {desc|firstline}\n'
3:934359450037 implement feature Y
$ hg --hidden shortlog -G -r 3: