hgext3rd: move 'directaccess' and 'inhibit' in 'evolve.hack'
We move them in the 'hgext3rd' package. In the same move we put them under the
'evolve' package for clarity. We use a sub-package 'hack' to make their status
clearer.
--- a/MANIFEST.in Tue Feb 28 15:09:03 2017 +0100
+++ b/MANIFEST.in Tue Feb 28 14:36:18 2017 +0100
@@ -1,8 +1,7 @@
exclude contrib/nopushpublish.py
-exclude hgext/directaccess.py
exclude hgext/drophack.py
-exclude hgext/inhibit.py
exclude hgext/obsolete.py
+exclude hgext3rd/evolve/hack/*.py
exclude Makefile
exclude tests/test-drop.t
exclude tests/test-inhibit.t
--- a/hgext/directaccess.py Tue Feb 28 15:09:03 2017 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-""" This extension provides 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 extensions
-from mercurial import cmdutil
-from mercurial import repoview
-from mercurial import branchmap
-from mercurial import revset
-from mercurial import error
-from mercurial import commands
-from mercurial import hg
-from mercurial import util
-from mercurial.i18n import _
-
-cmdtable = {}
-command = cmdutil.command(cmdtable)
-
-# By default, all the commands have directaccess with warnings
-# List of commands that have no directaccess and directaccess with no warning
-directaccesslevel = [
- # Format:
- # ('nowarning', 'evolve', 'prune'),
- # means: no directaccess warning, for the command in evolve named prune
- #
- # ('error', None, 'serve'),
- # means: no directaccess for the command in core named serve
- #
- # The list is ordered alphabetically by command names, starting with all
- # the commands in core then all the commands in the extensions
- #
- # The general guideline is:
- # - remove directaccess warnings for read only commands
- # - no direct access for commands with consequences outside of the repo
- # - leave directaccess warnings for all the other commands
- #
- ('nowarning', None, 'annotate'),
- ('nowarning', None, 'archive'),
- ('nowarning', None, 'bisect'),
- ('nowarning', None, 'bookmarks'),
- ('nowarning', None, 'bundle'),
- ('nowarning', None, 'cat'),
- ('nowarning', None, 'diff'),
- ('nowarning', None, 'export'),
- ('nowarning', None, 'identify'),
- ('nowarning', None, 'incoming'),
- ('nowarning', None, 'log'),
- ('nowarning', None, 'manifest'),
- ('error', None, 'outgoing'), # confusing if push errors and not outgoing
- ('error', None, 'push'), # destructive
- ('nowarning', None, 'revert'),
- ('error', None, 'serve'),
- ('nowarning', None, 'tags'),
- ('nowarning', None, 'unbundle'),
- ('nowarning', None, 'update'),
-]
-
-def reposetup(ui, repo):
- repo._explicitaccess = set()
-
-def _computehidden(repo):
- hidden = repoview.filterrevs(repo, 'visible')
- cl = repo.changelog
- dynamic = hidden & repo._explicitaccess
- if dynamic:
- blocked = cl.ancestors(dynamic, inclusive=True)
- hidden = frozenset(r for r in hidden if r not in blocked)
- return hidden
-
-def setupdirectaccess():
- """ Add two new filtername that behave like visible to provide direct access
- and direct access with warning. Wraps the commands to setup direct access
- """
- repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden})
- repoview.filtertable.update({'visible-directaccess-warn': _computehidden})
- branchmap.subsettable['visible-directaccess-nowarn'] = 'visible'
- branchmap.subsettable['visible-directaccess-warn'] = 'visible'
-
- for warn, ext, cmd in directaccesslevel:
- try:
- cmdtable = extensions.find(ext).cmdtable if ext else commands.table
- wrapper = wrapwitherror if warn == 'error' else wrapwithoutwarning
- extensions.wrapcommand(cmdtable, cmd, wrapper)
- except (error.UnknownCommand, KeyError):
- pass
-
-def wrapwitherror(orig, ui, repo, *args, **kwargs):
- if repo and repo.filtername == 'visible-directaccess-warn':
- repo = repo.filtered('visible')
- return orig(ui, repo, *args, **kwargs)
-
-def wrapwithoutwarning(orig, ui, repo, *args, **kwargs):
- if repo and repo.filtername == 'visible-directaccess-warn':
- repo = repo.filtered("visible-directaccess-nowarn")
- return orig(ui, repo, *args, **kwargs)
-
-def uisetup(ui):
- """ Change ordering of extensions to ensure that directaccess extsetup comes
- after the one of the extensions in the loadsafter list """
- loadsafter = ui.configlist('directaccess','loadsafter')
- order = list(extensions._order)
- directaccesidx = order.index('directaccess')
-
- # The min idx for directaccess to load after all the extensions in loadafter
- minidxdirectaccess = directaccesidx
-
- for ext in loadsafter:
- try:
- minidxdirectaccess = max(minidxdirectaccess, order.index(ext))
- except ValueError:
- pass # extension not loaded
-
- if minidxdirectaccess > directaccesidx:
- order.insert(minidxdirectaccess + 1, 'directaccess')
- order.remove('directaccess')
- extensions._order = order
-
-def _repository(orig, *args, **kwargs):
- """Make visible-directaccess-warn the default filter for new repos"""
- repo = orig(*args, **kwargs)
- return repo.filtered("visible-directaccess-warn")
-
-def extsetup(ui):
- extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook)
- extensions.wrapfunction(hg, 'repository', _repository)
- setupdirectaccess()
-
-hashre = util.re.compile('[0-9a-fA-F]{1,40}')
-
-_listtuple = ('symbol', '_list')
-
-def _ishashsymbol(symbol, maxrev):
- # Returns true if symbol looks like a hash
- try:
- n = int(symbol)
- if n <= maxrev:
- # It's a rev number
- return False
- except ValueError:
- pass
- return hashre.match(symbol)
-
-def gethashsymbols(tree, maxrev):
- # 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 []
-
- results = []
- if len(tree) == 2 and tree[0] == "symbol":
- results.append(tree[1])
- elif tree[0] == "func" and tree[1] == _listtuple:
- # the optimiser will group sequence of hash request
- results += tree[2][1].split('\0')
- elif len(tree) >= 3:
- for subtree in tree[1:]:
- results += gethashsymbols(subtree, maxrev)
- # return directly, we don't need to filter symbols again
- return results
- return [s for s in results if _ishashsymbol(s, maxrev)]
-
-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)
- filternm = ""
- if repo is not None:
- filternm = repo.filtername
- if filternm is not None and filternm.startswith('visible-directaccess'):
- prelength = len(repo._explicitaccess)
- accessbefore = set(repo._explicitaccess)
- cl = repo.unfiltered().changelog
- repo.symbols = gethashsymbols(tree, len(cl))
- 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):
- if repo.filtername != 'visible-directaccess-nowarn':
- unhiddencommits = repo._explicitaccess - accessbefore
- repo.ui.warn(_("Warning: accessing hidden changesets %s "
- "for write operation\n") %
- (",".join([str(repo.unfiltered()[l])
- for l in unhiddencommits])))
- repo.invalidatevolatilesets()
--- a/hgext/inhibit.py Tue Feb 28 15:09:03 2017 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,310 +0,0 @@
-"""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
-
-This extension provides 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.
-"""
-from mercurial import localrepo
-from mercurial import obsolete
-from mercurial import extensions
-from mercurial import cmdutil
-from mercurial import error
-from mercurial import scmutil
-from mercurial import commands
-from mercurial import lock as lockmod
-from mercurial import bookmarks
-from mercurial import util
-from mercurial.i18n import _
-
-cmdtable = {}
-command = cmdutil.command(cmdtable)
-
-def _inhibitenabled(repo):
- return util.safehasattr(repo, '_obsinhibit')
-
-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.svfs.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)
- if newnode is not None:
- _inhibitmarkers(repo, [newnode])
- return newnode
-
- repo.__class__ = obsinhibitedrepo
-
-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)
- elif opts.get('rename'):
- raise error.Abort('Cannot use both -m and -D')
- elif len(bookmarks) == 0:
- hint = _('make sure to put a space between -D and your bookmark name')
- raise error.Abort(_('Error, please check your command'), hint=hint)
-
- # Call prune -B
- evolve = extensions.find('evolve')
- optsdict = {
- 'new': [],
- 'succ': [],
- 'rev': [],
- 'bookmark': bookmarks,
- '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 nodes
- if getrev(n) is not None and 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.
- """
- if not _inhibitenabled(repo):
- return
-
- # we add (non public()) as a lower boundary to
- # - use the C code in 3.6 (no ancestors in C as this is written)
- # - restrict the search space. Otherwise, the ancestors can spend a lot of
- # time iterating if you have a check very low in the repo. We do not need
- # to iterate over tens of thousand of public revisions with higher
- # revision number
- #
- # In addition, the revset logic could be made significantly smarter here.
- newinhibit = repo.revs('(not public())::%ln and obsolete()', nodes)
- if newinhibit:
- node = repo.changelog.node
- lock = tr = None
- try:
- lock = repo.lock()
- tr = repo.transaction('obsinhibit')
- repo._obsinhibit.update(node(r) for r 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.
- """
- if not _inhibitenabled(repo):
- return
-
- 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
- lock = tr = None
- try:
- lock = repo.lock()
- tr = repo.transaction('add-obsolescence-marker')
- orig(repo, relations, flag, date, metadata)
- precs = (r[0].node() for r in relations)
- _deinhibitmarkers(repo, precs)
- tr.close()
- finally:
- lockmod.release(tr, lock)
-
-def _filterobsoleterevswrap(orig, repo, rebasesetrevs, *args, **kwargs):
- repo._notinhibited = rebasesetrevs
- try:
- repo.invalidatevolatilesets()
- r = orig(repo, rebasesetrevs, *args, **kwargs)
- finally:
- del repo._notinhibited
- repo.invalidatevolatilesets()
- return r
-
-def transactioncallback(orig, repo, desc, *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('obsolete() - hidden()')
- ignoreset = set(getattr(repo, '_rebaseset', []))
- ignoreset |= set(getattr(repo, '_obsoletenotrebased', []))
- visibleobsolete = list(r for r in visibleobsolete if r not in ignoreset)
- if visibleobsolete:
- _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
- transaction = orig(repo, desc, *args, **kwargs)
- if desc != 'strip' and _inhibitenabled(repo):
- transaction.addpostclose('inhibitposttransaction',
- inhibitposttransaction)
- return transaction
-
-
-# We wrap these two functions to address the following scenario:
-# - Assuming that we have markers between commits in the rebase set and
-# destination and that these markers are inhibited
-# - At the end of the rebase the nodes are still visible because rebase operate
-# without inhibition and skip these nodes
-# We keep track in repo._obsoletenotrebased of the obsolete commits skipped by
-# the rebase and lift the inhibition in the end of the rebase.
-
-def _computeobsoletenotrebased(orig, repo, *args, **kwargs):
- r = orig(repo, *args, **kwargs)
- repo._obsoletenotrebased = r.keys()
- return r
-
-def _clearrebased(orig, ui, repo, *args, **kwargs):
- r = orig(ui, repo, *args, **kwargs)
- tonode = repo.changelog.node
- if util.safehasattr(repo, '_obsoletenotrebased'):
- _deinhibitmarkers(repo, [tonode(k) for k in repo._obsoletenotrebased])
- return r
-
-
-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)
- if _inhibitenabled(repo):
- getrev = repo.changelog.nodemap.get
- blacklist = getattr(repo, '_notinhibited', set())
- for n in repo._obsinhibit:
- if getrev(n) not in blacklist:
- obs.discard(getrev(n))
- return obs
- try:
- extensions.find('directaccess')
- except KeyError:
- errormsg = _('cannot use inhibit without the direct access extension\n')
- hint = _("(please enable it or inhibit won\'t work)\n")
- ui.warn(errormsg)
- ui.warn(hint)
- return
-
- # Wrapping this to inhibit obsolete revs resulting from a transaction
- extensions.wrapfunction(localrepo.localrepository,
- 'transaction', transactioncallback)
-
- obsolete.cachefuncs['obsolete'] = _computeobsoleteset
- # wrap create marker to make it able to lift the inhibition
- extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)
- # 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 update to make sure that no obsolete commit is visible after an
- # update
- extensions.wrapcommand(commands.table, 'update', _update)
- try:
- rebase = extensions.find('rebase')
- if rebase:
- if util.safehasattr(rebase, '_filterobsoleterevs'):
- extensions.wrapfunction(rebase,
- '_filterobsoleterevs',
- _filterobsoleterevswrap)
- extensions.wrapfunction(rebase, 'clearrebased', _clearrebased)
- if util.safehasattr(rebase, '_computeobsoletenotrebased'):
- extensions.wrapfunction(rebase,
- '_computeobsoletenotrebased',
- _computeobsoletenotrebased)
-
- except KeyError:
- pass
- # There are two ways to save bookmark changes during a transation, we
- # wrap both to add inhibition markers.
- extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged)
- if getattr(bookmarks.bmstore, 'write', None) is not None:# mercurial < 3.9
- 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')))
-
-@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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/hack/directaccess.py Tue Feb 28 14:36:18 2017 +0100
@@ -0,0 +1,194 @@
+""" This extension provides 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 extensions
+from mercurial import cmdutil
+from mercurial import repoview
+from mercurial import branchmap
+from mercurial import revset
+from mercurial import error
+from mercurial import commands
+from mercurial import hg
+from mercurial import util
+from mercurial.i18n import _
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+# By default, all the commands have directaccess with warnings
+# List of commands that have no directaccess and directaccess with no warning
+directaccesslevel = [
+ # Format:
+ # ('nowarning', 'evolve', 'prune'),
+ # means: no directaccess warning, for the command in evolve named prune
+ #
+ # ('error', None, 'serve'),
+ # means: no directaccess for the command in core named serve
+ #
+ # The list is ordered alphabetically by command names, starting with all
+ # the commands in core then all the commands in the extensions
+ #
+ # The general guideline is:
+ # - remove directaccess warnings for read only commands
+ # - no direct access for commands with consequences outside of the repo
+ # - leave directaccess warnings for all the other commands
+ #
+ ('nowarning', None, 'annotate'),
+ ('nowarning', None, 'archive'),
+ ('nowarning', None, 'bisect'),
+ ('nowarning', None, 'bookmarks'),
+ ('nowarning', None, 'bundle'),
+ ('nowarning', None, 'cat'),
+ ('nowarning', None, 'diff'),
+ ('nowarning', None, 'export'),
+ ('nowarning', None, 'identify'),
+ ('nowarning', None, 'incoming'),
+ ('nowarning', None, 'log'),
+ ('nowarning', None, 'manifest'),
+ ('error', None, 'outgoing'), # confusing if push errors and not outgoing
+ ('error', None, 'push'), # destructive
+ ('nowarning', None, 'revert'),
+ ('error', None, 'serve'),
+ ('nowarning', None, 'tags'),
+ ('nowarning', None, 'unbundle'),
+ ('nowarning', None, 'update'),
+]
+
+def reposetup(ui, repo):
+ repo._explicitaccess = set()
+
+def _computehidden(repo):
+ hidden = repoview.filterrevs(repo, 'visible')
+ cl = repo.changelog
+ dynamic = hidden & repo._explicitaccess
+ if dynamic:
+ blocked = cl.ancestors(dynamic, inclusive=True)
+ hidden = frozenset(r for r in hidden if r not in blocked)
+ return hidden
+
+def setupdirectaccess():
+ """ Add two new filtername that behave like visible to provide direct access
+ and direct access with warning. Wraps the commands to setup direct access
+ """
+ repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden})
+ repoview.filtertable.update({'visible-directaccess-warn': _computehidden})
+ branchmap.subsettable['visible-directaccess-nowarn'] = 'visible'
+ branchmap.subsettable['visible-directaccess-warn'] = 'visible'
+
+ for warn, ext, cmd in directaccesslevel:
+ try:
+ cmdtable = extensions.find(ext).cmdtable if ext else commands.table
+ wrapper = wrapwitherror if warn == 'error' else wrapwithoutwarning
+ extensions.wrapcommand(cmdtable, cmd, wrapper)
+ except (error.UnknownCommand, KeyError):
+ pass
+
+def wrapwitherror(orig, ui, repo, *args, **kwargs):
+ if repo and repo.filtername == 'visible-directaccess-warn':
+ repo = repo.filtered('visible')
+ return orig(ui, repo, *args, **kwargs)
+
+def wrapwithoutwarning(orig, ui, repo, *args, **kwargs):
+ if repo and repo.filtername == 'visible-directaccess-warn':
+ repo = repo.filtered("visible-directaccess-nowarn")
+ return orig(ui, repo, *args, **kwargs)
+
+def uisetup(ui):
+ """ Change ordering of extensions to ensure that directaccess extsetup comes
+ after the one of the extensions in the loadsafter list """
+ loadsafter = ui.configlist('directaccess','loadsafter')
+ order = list(extensions._order)
+ directaccesidx = order.index('directaccess')
+
+ # The min idx for directaccess to load after all the extensions in loadafter
+ minidxdirectaccess = directaccesidx
+
+ for ext in loadsafter:
+ try:
+ minidxdirectaccess = max(minidxdirectaccess, order.index(ext))
+ except ValueError:
+ pass # extension not loaded
+
+ if minidxdirectaccess > directaccesidx:
+ order.insert(minidxdirectaccess + 1, 'directaccess')
+ order.remove('directaccess')
+ extensions._order = order
+
+def _repository(orig, *args, **kwargs):
+ """Make visible-directaccess-warn the default filter for new repos"""
+ repo = orig(*args, **kwargs)
+ return repo.filtered("visible-directaccess-warn")
+
+def extsetup(ui):
+ extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook)
+ extensions.wrapfunction(hg, 'repository', _repository)
+ setupdirectaccess()
+
+hashre = util.re.compile('[0-9a-fA-F]{1,40}')
+
+_listtuple = ('symbol', '_list')
+
+def _ishashsymbol(symbol, maxrev):
+ # Returns true if symbol looks like a hash
+ try:
+ n = int(symbol)
+ if n <= maxrev:
+ # It's a rev number
+ return False
+ except ValueError:
+ pass
+ return hashre.match(symbol)
+
+def gethashsymbols(tree, maxrev):
+ # 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 []
+
+ results = []
+ if len(tree) == 2 and tree[0] == "symbol":
+ results.append(tree[1])
+ elif tree[0] == "func" and tree[1] == _listtuple:
+ # the optimiser will group sequence of hash request
+ results += tree[2][1].split('\0')
+ elif len(tree) >= 3:
+ for subtree in tree[1:]:
+ results += gethashsymbols(subtree, maxrev)
+ # return directly, we don't need to filter symbols again
+ return results
+ return [s for s in results if _ishashsymbol(s, maxrev)]
+
+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)
+ filternm = ""
+ if repo is not None:
+ filternm = repo.filtername
+ if filternm is not None and filternm.startswith('visible-directaccess'):
+ prelength = len(repo._explicitaccess)
+ accessbefore = set(repo._explicitaccess)
+ cl = repo.unfiltered().changelog
+ repo.symbols = gethashsymbols(tree, len(cl))
+ 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):
+ if repo.filtername != 'visible-directaccess-nowarn':
+ unhiddencommits = repo._explicitaccess - accessbefore
+ repo.ui.warn(_("Warning: accessing hidden changesets %s "
+ "for write operation\n") %
+ (",".join([str(repo.unfiltered()[l])
+ for l in unhiddencommits])))
+ repo.invalidatevolatilesets()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/hack/inhibit.py Tue Feb 28 14:36:18 2017 +0100
@@ -0,0 +1,310 @@
+"""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
+
+This extension provides 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.
+"""
+from mercurial import localrepo
+from mercurial import obsolete
+from mercurial import extensions
+from mercurial import cmdutil
+from mercurial import error
+from mercurial import scmutil
+from mercurial import commands
+from mercurial import lock as lockmod
+from mercurial import bookmarks
+from mercurial import util
+from mercurial.i18n import _
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+def _inhibitenabled(repo):
+ return util.safehasattr(repo, '_obsinhibit')
+
+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.svfs.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)
+ if newnode is not None:
+ _inhibitmarkers(repo, [newnode])
+ return newnode
+
+ repo.__class__ = obsinhibitedrepo
+
+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)
+ elif opts.get('rename'):
+ raise error.Abort('Cannot use both -m and -D')
+ elif len(bookmarks) == 0:
+ hint = _('make sure to put a space between -D and your bookmark name')
+ raise error.Abort(_('Error, please check your command'), hint=hint)
+
+ # Call prune -B
+ evolve = extensions.find('evolve')
+ optsdict = {
+ 'new': [],
+ 'succ': [],
+ 'rev': [],
+ 'bookmark': bookmarks,
+ '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 nodes
+ if getrev(n) is not None and 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.
+ """
+ if not _inhibitenabled(repo):
+ return
+
+ # we add (non public()) as a lower boundary to
+ # - use the C code in 3.6 (no ancestors in C as this is written)
+ # - restrict the search space. Otherwise, the ancestors can spend a lot of
+ # time iterating if you have a check very low in the repo. We do not need
+ # to iterate over tens of thousand of public revisions with higher
+ # revision number
+ #
+ # In addition, the revset logic could be made significantly smarter here.
+ newinhibit = repo.revs('(not public())::%ln and obsolete()', nodes)
+ if newinhibit:
+ node = repo.changelog.node
+ lock = tr = None
+ try:
+ lock = repo.lock()
+ tr = repo.transaction('obsinhibit')
+ repo._obsinhibit.update(node(r) for r 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.
+ """
+ if not _inhibitenabled(repo):
+ return
+
+ 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
+ lock = tr = None
+ try:
+ lock = repo.lock()
+ tr = repo.transaction('add-obsolescence-marker')
+ orig(repo, relations, flag, date, metadata)
+ precs = (r[0].node() for r in relations)
+ _deinhibitmarkers(repo, precs)
+ tr.close()
+ finally:
+ lockmod.release(tr, lock)
+
+def _filterobsoleterevswrap(orig, repo, rebasesetrevs, *args, **kwargs):
+ repo._notinhibited = rebasesetrevs
+ try:
+ repo.invalidatevolatilesets()
+ r = orig(repo, rebasesetrevs, *args, **kwargs)
+ finally:
+ del repo._notinhibited
+ repo.invalidatevolatilesets()
+ return r
+
+def transactioncallback(orig, repo, desc, *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('obsolete() - hidden()')
+ ignoreset = set(getattr(repo, '_rebaseset', []))
+ ignoreset |= set(getattr(repo, '_obsoletenotrebased', []))
+ visibleobsolete = list(r for r in visibleobsolete if r not in ignoreset)
+ if visibleobsolete:
+ _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
+ transaction = orig(repo, desc, *args, **kwargs)
+ if desc != 'strip' and _inhibitenabled(repo):
+ transaction.addpostclose('inhibitposttransaction',
+ inhibitposttransaction)
+ return transaction
+
+
+# We wrap these two functions to address the following scenario:
+# - Assuming that we have markers between commits in the rebase set and
+# destination and that these markers are inhibited
+# - At the end of the rebase the nodes are still visible because rebase operate
+# without inhibition and skip these nodes
+# We keep track in repo._obsoletenotrebased of the obsolete commits skipped by
+# the rebase and lift the inhibition in the end of the rebase.
+
+def _computeobsoletenotrebased(orig, repo, *args, **kwargs):
+ r = orig(repo, *args, **kwargs)
+ repo._obsoletenotrebased = r.keys()
+ return r
+
+def _clearrebased(orig, ui, repo, *args, **kwargs):
+ r = orig(ui, repo, *args, **kwargs)
+ tonode = repo.changelog.node
+ if util.safehasattr(repo, '_obsoletenotrebased'):
+ _deinhibitmarkers(repo, [tonode(k) for k in repo._obsoletenotrebased])
+ return r
+
+
+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)
+ if _inhibitenabled(repo):
+ getrev = repo.changelog.nodemap.get
+ blacklist = getattr(repo, '_notinhibited', set())
+ for n in repo._obsinhibit:
+ if getrev(n) not in blacklist:
+ obs.discard(getrev(n))
+ return obs
+ try:
+ extensions.find('directaccess')
+ except KeyError:
+ errormsg = _('cannot use inhibit without the direct access extension\n')
+ hint = _("(please enable it or inhibit won\'t work)\n")
+ ui.warn(errormsg)
+ ui.warn(hint)
+ return
+
+ # Wrapping this to inhibit obsolete revs resulting from a transaction
+ extensions.wrapfunction(localrepo.localrepository,
+ 'transaction', transactioncallback)
+
+ obsolete.cachefuncs['obsolete'] = _computeobsoleteset
+ # wrap create marker to make it able to lift the inhibition
+ extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)
+ # 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 update to make sure that no obsolete commit is visible after an
+ # update
+ extensions.wrapcommand(commands.table, 'update', _update)
+ try:
+ rebase = extensions.find('rebase')
+ if rebase:
+ if util.safehasattr(rebase, '_filterobsoleterevs'):
+ extensions.wrapfunction(rebase,
+ '_filterobsoleterevs',
+ _filterobsoleterevswrap)
+ extensions.wrapfunction(rebase, 'clearrebased', _clearrebased)
+ if util.safehasattr(rebase, '_computeobsoletenotrebased'):
+ extensions.wrapfunction(rebase,
+ '_computeobsoletenotrebased',
+ _computeobsoletenotrebased)
+
+ except KeyError:
+ pass
+ # There are two ways to save bookmark changes during a transation, we
+ # wrap both to add inhibition markers.
+ extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged)
+ if getattr(bookmarks.bmstore, 'write', None) is not None:# mercurial < 3.9
+ 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')))
+
+@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)
--- a/setup.py Tue Feb 28 15:09:03 2017 +0100
+++ b/setup.py Tue Feb 28 14:36:18 2017 +0100
@@ -22,8 +22,8 @@
]
if os.environ.get('INCLUDE_INHIBIT'):
- py_modules.append('hgext.inhibit')
- py_modules.append('hgext.directaccess')
+ py_modules.append('hgext3rd.evolve.hack.inhibit')
+ py_modules.append('hgext3rd.evolve.hack.directaccess')
setup(
name='hg-evolve',
--- a/tests/test-inhibit.t Tue Feb 28 15:09:03 2017 +0100
+++ b/tests/test-inhibit.t Tue Feb 28 14:36:18 2017 +0100
@@ -9,8 +9,8 @@
> strip=
> EOF
$ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
- $ echo "directaccess=$(echo $(dirname $TESTDIR))/hgext/directaccess.py" >> $HGRCPATH
- $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext/inhibit.py" >> $HGRCPATH
+ $ echo "directaccess=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/hack/directaccess.py" >> $HGRCPATH
+ $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/hack/inhibit.py" >> $HGRCPATH
$ mkcommit() {
> echo "$1" > "$1"
> hg add "$1"
@@ -728,7 +728,7 @@
$ cat >> $HGRCPATH <<EOF
> [extensions]
> EOF
- $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext/inhibit.py" >> $HGRCPATH
+ $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/hack/inhibit.py" >> $HGRCPATH
Empty commit
$ hg amend
@@ -781,7 +781,7 @@
cannot use inhibit without the direct access extension
(please enable it or inhibit won't work)
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
- $ echo "directaccess=$(echo $(dirname $TESTDIR))/hgext/directaccess.py" >> $HGRCPATH
+ $ echo "directaccess=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/hack/directaccess.py" >> $HGRCPATH
$ cd ..
hg push should not allow directaccess unless forced with --hidden