# inhibit.py - redefine obsolete(), bumped(), divergent() revsets # # Copyright 2017 Facebook, Inc. # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. """redefine obsolete(), bumped(), divergent() revsets""" from __future__ import absolute_import from mercurial import ( error, extensions, obsolete, ) def _obsoletedrevs(repo): """Redefine "obsolete()" revset. Previously, X is obsoleted if X appears as a precursor in a marker. Now, X is obsoleted if X is a precursor in marker M1, *and* is not a successor in marker M2 where M2.date >= M1.date. This allows undo to return to old hashes, and is correct as long as obsmarker is not exchanged. """ getnode = repo.changelog.node markersbysuccessor = repo.obsstore.precursors.get markersbyprecursor = repo.obsstore.successors.get result = set() for r in obsolete._mutablerevs(repo): n = getnode(r) m1s = markersbyprecursor(n) m2s = markersbysuccessor(n) if m1s: if m2s: # marker: (prec, [succ], flag, meta, (date, timezone), parent) d1 = max(m[4][0] for m in m1s) d2 = max(m[4][0] for m in m2s) if d2 < d1: result.add(r) else: result.add(r) return result def _obsstorecreate(orig, self, tr, prec, succs=(), flag=0, parents=None, date=None, metadata=None, ui=None): # make "prec in succs" in-marker cycle check a no-op succs = _nocontainslist(succs) # if prec is a successor of an existing marker, make default date bigger so # the old marker won't revive the precursor accidentally. if date is None: markers = self.precursors.get(prec) if markers: maxdate = max(m[4] for m in markers) date = (maxdate[0] + 1, maxdate[1]) return orig(self, tr, prec, succs, flag, parents, date, metadata, ui) class _nocontainslist(list): # like "list", but __contains__ returns False, used by _obsstorecreate def __contains__(self, x): return False def _createmarkers(orig, repo, rels, *args, **kwargs): # make precursor context unfiltered so parents() won't raise unfi = repo.unfiltered() rels = [list(r) for r in rels] # make mutable for r in rels: try: r[0] = unfi[r[0].node()] except error.RepoLookupError: # node could be unknown in current repo pass return orig(unfi, rels, *args, **kwargs) def revive(ctxlist, operation='revive'): """un-obsolete revisions (public API used by other extensions)""" torevive = [c for c in ctxlist if c.obsolete()] if not torevive: return # revive it by creating a self cycle marker repo = torevive[0].repo() with repo.lock(), repo.transaction(operation) as tr: meta = {'user': repo.ui.username(), 'operation': operation} for ctx in torevive: node = ctx.node() # use low-level API instead of obsolete.createmarkers since its # cycle check is problematic repo.obsstore.create(tr, node, (node,), metadata=meta, ui=repo.ui) def uisetup(ui): revsets = obsolete.cachefuncs # redefine obsolete(): handle cycles and make nodes visible revsets['obsolete'] = _obsoletedrevs # make divergent() and bumped() empty # NOTE: we should avoid doing this but just change templates to only show a # subset of troubles we care about. revsets['divergent'] = revsets['bumped'] = lambda repo: frozenset() # make obsstore.create not complain about in-marker cycles, since we want # to write X -> X to revive X. extensions.wrapfunction(obsolete.obsstore, 'create', _obsstorecreate) # make createmarkers use unfiltered precursor ctx, workarounds an issue # that prec.parents() may raise FilteredIndexError. # NOTE: should be fixed upstream once hash-preserving obsstore is a thing. extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)