Mercurial > evolve
changeset 1807:a53efee7d8b0
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.
author | Pierre-Yves David <pierre-yves.david@ens-lyon.org> |
---|---|
date | Tue, 28 Feb 2017 14:36:18 +0100 |
parents | 9f42f819267b |
children | 202ac6c94b7f |
files | MANIFEST.in hgext/directaccess.py hgext/inhibit.py hgext3rd/evolve/hack/__init__.py hgext3rd/evolve/hack/directaccess.py hgext3rd/evolve/hack/inhibit.py setup.py tests/test-inhibit.t |
diffstat | 7 files changed, 511 insertions(+), 512 deletions(-) [+] |
line wrap: on
line diff
--- 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