sapling/hgext3rd/inhibit.py
Jun Wu 40503c20fb inhibit: completely rewrite the extension
Summary:
The main feature we want is to be able to unobsolete an already obsoleted
changeset. The old inhibit code is causing all kinds of unsolvable weird
cases and is hard to deubg or maintain.

This patch rewrites it completely. Basically, we now require people to use
obsmarkers to "unobsolete" changesets. We treat cycles in obsstore as a
normal case and break the cycle using date information.

It should be a neat and correct solution until we want marker exchange.

A "revive" API was provided for other extensions to use.

Tests and other code changes will be fixed in a follow up.

Test Plan:
`test-inhibit.t` was rewritten to test the new features.

Other tests are broken and skipped for now. The next diff will fix them.

Reviewers: #mercurial, kulshrax

Reviewed By: kulshrax

Subscribers: medson, mjpieters

Differential Revision: https://phabricator.intern.facebook.com/D5391320

Signature: t1:5391320:1499716172:a946381421cc242411f5175ee3b7a3a0bc5a4f07
2017-07-10 15:45:07 -07:00

107 lines
4.0 KiB
Python

# 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)