repoview.py
487 lines
| 17.0 KiB
| text/x-python
|
PythonLexer
/ mercurial / repoview.py
Pierre-Yves David
|
r18100 | # repoview.py - Filtered view of a localrepo object | ||
# | ||||
# Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org> | ||||
# Logilab SA <contact@logilab.fr> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Gregory Szorc
|
r25972 | |||
Pierre-Yves David
|
r18100 | import copy | ||
Yuya Nishihara
|
r35248 | import weakref | ||
Gregory Szorc
|
r25972 | |||
Martin von Zweigbergk
|
r43754 | from .i18n import _ | ||
from .node import ( | ||||
hex, | ||||
nullrev, | ||||
) | ||||
Gregory Szorc
|
r25972 | from . import ( | ||
Martin von Zweigbergk
|
r43752 | error, | ||
Gregory Szorc
|
r25972 | obsolete, | ||
phases, | ||||
Yuya Nishihara
|
r35249 | pycompat, | ||
Gregory Szorc
|
r25972 | tags as tagsmod, | ||
r42417 | util, | |||
) | ||||
Augie Fackler
|
r43346 | from .utils import repoviewutil | ||
Pierre-Yves David
|
r18242 | |||
Pierre-Yves David
|
r18293 | def hideablerevs(repo): | ||
Pierre-Yves David
|
r28780 | """Revision candidates to be hidden | ||
This is a standalone function to allow extensions to wrap it. | ||||
Pierre-Yves David
|
r18293 | |||
Pierre-Yves David
|
r28780 | Because we use the set of immutable changesets as a fallback subset in | ||
r42314 | branchmap (see mercurial.utils.repoviewutils.subsettable), you cannot set | |||
"public" changesets as "hideable". Doing so would break multiple code | ||||
assertions and lead to crashes.""" | ||||
Augie Fackler
|
r43347 | obsoletes = obsolete.getrevs(repo, b'obsolete') | ||
Boris Feld
|
r39333 | internals = repo._phasecache.getrevset(repo, phases.localhiddenphases) | ||
internals = frozenset(internals) | ||||
return obsoletes | internals | ||||
Pierre-Yves David
|
r18293 | |||
Augie Fackler
|
r43346 | |||
Martin von Zweigbergk
|
r32580 | def pinnedrevs(repo): | ||
Augie Fackler
|
r46554 | """revisions blocking hidden changesets from being filtered""" | ||
r32426 | ||||
cl = repo.changelog | ||||
Martin von Zweigbergk
|
r32580 | pinned = set() | ||
pinned.update([par.rev() for par in repo[None].parents()]) | ||||
pinned.update([cl.rev(bm) for bm in repo._bookmarks.values()]) | ||||
r32426 | ||||
tags = {} | ||||
tagsmod.readlocaltags(repo.ui, repo, tags, {}) | ||||
if tags: | ||||
r43959 | rev = cl.index.get_rev | |||
pinned.update(rev(t[0]) for t in tags.values()) | ||||
pinned.discard(None) | ||||
Matt Harbison
|
r45972 | |||
# Avoid cycle: mercurial.filemerge -> mercurial.templater -> | ||||
# mercurial.templatefuncs -> mercurial.revset -> mercurial.repoview -> | ||||
# mercurial.mergestate -> mercurial.filemerge | ||||
from . import mergestate | ||||
ms = mergestate.mergestate.read(repo) | ||||
Matt Harbison
|
r46383 | if ms.active() and ms.unresolvedcount(): | ||
Martin von Zweigbergk
|
r46116 | for node in (ms.local, ms.other): | ||
rev = cl.index.get_rev(node) | ||||
if rev is not None: | ||||
pinned.add(rev) | ||||
Matt Harbison
|
r45972 | |||
Martin von Zweigbergk
|
r32580 | return pinned | ||
r32426 | ||||
r32476 | ||||
Martin von Zweigbergk
|
r32582 | def _revealancestors(pfunc, hidden, revs): | ||
"""reveals contiguous chains of hidden ancestors of 'revs' by removing them | ||||
from 'hidden' | ||||
r32474 | ||||
- pfunc(r): a funtion returning parent of 'r', | ||||
Martin von Zweigbergk
|
r32581 | - hidden: the (preliminary) hidden revisions, to be updated | ||
r32474 | - revs: iterable of revnum, | |||
Martin von Zweigbergk
|
r32585 | (Ancestors are revealed exclusively, i.e. the elements in 'revs' are | ||
*not* revealed) | ||||
r32474 | """ | |||
stack = list(revs) | ||||
while stack: | ||||
for p in pfunc(stack.pop()): | ||||
Martin von Zweigbergk
|
r32582 | if p != nullrev and p in hidden: | ||
Martin von Zweigbergk
|
r32581 | hidden.remove(p) | ||
r32474 | stack.append(p) | |||
Augie Fackler
|
r43346 | |||
Pulkit Goyal
|
r35509 | def computehidden(repo, visibilityexceptions=None): | ||
Pierre-Yves David
|
r18242 | """compute the set of hidden revision to filter | ||
During most operation hidden should be filtered.""" | ||||
assert not repo.changelog.filteredrevs | ||||
David Soria Parra
|
r22151 | |||
r32478 | hidden = hideablerevs(repo) | |||
if hidden: | ||||
Martin von Zweigbergk
|
r32586 | hidden = set(hidden - pinnedrevs(repo)) | ||
Pulkit Goyal
|
r35509 | if visibilityexceptions: | ||
hidden -= visibilityexceptions | ||||
r32478 | pfunc = repo.changelog.parentrevs | |||
Boris Feld
|
r38174 | mutable = repo._phasecache.getrevset(repo, phases.mutablephases) | ||
r32478 | ||||
Martin von Zweigbergk
|
r32587 | visible = mutable - hidden | ||
_revealancestors(pfunc, hidden, visible) | ||||
r32478 | return frozenset(hidden) | |||
Pierre-Yves David
|
r18242 | |||
Augie Fackler
|
r43346 | |||
r42295 | def computesecret(repo, visibilityexceptions=None): | |||
"""compute the set of revision that can never be exposed through hgweb | ||||
Changeset in the secret phase (or above) should stay unaccessible.""" | ||||
assert not repo.changelog.filteredrevs | ||||
secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases) | ||||
return frozenset(secrets) | ||||
Augie Fackler
|
r43346 | |||
Pulkit Goyal
|
r35509 | def computeunserved(repo, visibilityexceptions=None): | ||
Pierre-Yves David
|
r18102 | """compute the set of revision that should be filtered when used a server | ||
Secret and hidden changeset should not pretend to be here.""" | ||||
assert not repo.changelog.filteredrevs | ||||
# fast path in simple case to avoid impact of non optimised code | ||||
Augie Fackler
|
r43347 | hiddens = filterrevs(repo, b'visible') | ||
secrets = filterrevs(repo, b'served.hidden') | ||||
r42294 | if secrets: | |||
r42295 | return frozenset(hiddens | secrets) | |||
Pierre-Yves David
|
r18273 | else: | ||
return hiddens | ||||
Pierre-Yves David
|
r18100 | |||
Augie Fackler
|
r43346 | |||
Pulkit Goyal
|
r35509 | def computemutable(repo, visibilityexceptions=None): | ||
Pierre-Yves David
|
r18245 | assert not repo.changelog.filteredrevs | ||
# fast check to avoid revset call on huge repo | ||||
Joerg Sonnenberger
|
r45674 | if repo._phasecache.hasnonpublicphases(repo): | ||
Joerg Sonnenberger
|
r45675 | return frozenset(repo._phasecache.getrevset(repo, phases.mutablephases)) | ||
Pierre-Yves David
|
r18245 | return frozenset() | ||
Augie Fackler
|
r43346 | |||
Pulkit Goyal
|
r35509 | def computeimpactable(repo, visibilityexceptions=None): | ||
Pierre-Yves David
|
r18246 | """Everything impactable by mutable revision | ||
Pierre-Yves David
|
r18462 | The immutable filter still have some chance to get invalidated. This will | ||
Pierre-Yves David
|
r18246 | happen when: | ||
- you garbage collect hidden changeset, | ||||
- public phase is moved backward, | ||||
- something is changed in the filtering (this could be fixed) | ||||
This filter out any mutable changeset and any public changeset that may be | ||||
impacted by something happening to a mutable revision. | ||||
Joerg Sonnenberger
|
r46811 | This is achieved by filtered everything with a revision number equal or | ||
Pierre-Yves David
|
r18246 | higher than the first mutable changeset is filtered.""" | ||
assert not repo.changelog.filteredrevs | ||||
cl = repo.changelog | ||||
firstmutable = len(cl) | ||||
Joerg Sonnenberger
|
r45674 | roots = repo._phasecache.nonpublicphaseroots(repo) | ||
if roots: | ||||
r52299 | firstmutable = min(firstmutable, min(roots)) | |||
Pierre-Yves David
|
r18443 | # protect from nullrev root | ||
firstmutable = max(0, firstmutable) | ||||
Manuel Jacob
|
r50179 | return frozenset(range(firstmutable, len(cl))) | ||
Pierre-Yves David
|
r18246 | |||
Augie Fackler
|
r43346 | |||
Pierre-Yves David
|
r18100 | # function to compute filtered set | ||
Pierre-Yves David
|
r20196 | # | ||
Mads Kiilerich
|
r20549 | # When adding a new filter you MUST update the table at: | ||
r42314 | # mercurial.utils.repoviewutil.subsettable | |||
Pierre-Yves David
|
r20196 | # Otherwise your filter will have to recompute all its branches cache | ||
# from scratch (very slow). | ||||
Augie Fackler
|
r43346 | filtertable = { | ||
Augie Fackler
|
r43347 | b'visible': computehidden, | ||
b'visible-hidden': computehidden, | ||||
b'served.hidden': computesecret, | ||||
b'served': computeunserved, | ||||
b'immutable': computemutable, | ||||
b'base': computeimpactable, | ||||
Augie Fackler
|
r43346 | } | ||
Pierre-Yves David
|
r18100 | |||
r44205 | # set of filter level that will include the working copy parent no matter what. | |||
filter_has_wc = {b'visible', b'visible-hidden'} | ||||
r42417 | _basefiltername = list(filtertable) | |||
Augie Fackler
|
r43346 | |||
r42417 | def extrafilter(ui): | |||
"""initialize extra filter and return its id | ||||
If extra filtering is configured, we make sure the associated filtered view | ||||
are declared and return the associated id. | ||||
""" | ||||
Augie Fackler
|
r43347 | frevs = ui.config(b'experimental', b'extra-filter-revs') | ||
r42417 | if frevs is None: | |||
return None | ||||
Augie Fackler
|
r43347 | fid = pycompat.sysbytes(util.DIGESTS[b'sha1'](frevs).hexdigest())[:12] | ||
r42417 | ||||
Augie Fackler
|
r43347 | combine = lambda fname: fname + b'%' + fid | ||
r42417 | ||||
subsettable = repoviewutil.subsettable | ||||
Augie Fackler
|
r43347 | if combine(b'base') not in filtertable: | ||
r51665 | for base_name in _basefiltername: | |||
Augie Fackler
|
r43346 | |||
r51665 | def extrafilteredrevs(repo, *args, name=base_name, **kwargs): | |||
r42417 | baserevs = filtertable[name](repo, *args, **kwargs) | |||
extrarevs = frozenset(repo.revs(frevs)) | ||||
return baserevs | extrarevs | ||||
Augie Fackler
|
r43346 | |||
r51665 | filtertable[combine(base_name)] = extrafilteredrevs | |||
if base_name in subsettable: | ||||
subsettable[combine(base_name)] = combine( | ||||
subsettable[base_name] | ||||
) | ||||
r42417 | return fid | |||
Augie Fackler
|
r43346 | |||
Pulkit Goyal
|
r35509 | def filterrevs(repo, filtername, visibilityexceptions=None): | ||
"""returns set of filtered revision for this filter name | ||||
visibilityexceptions is a set of revs which must are exceptions for | ||||
hidden-state and must be visible. They are dynamic and hence we should not | ||||
cache it's result""" | ||||
Pierre-Yves David
|
r18101 | if filtername not in repo.filteredrevcache: | ||
r44192 | if repo.ui.configbool(b'devel', b'debug.repo-filters'): | |||
r44194 | msg = b'computing revision filter for "%s"' | |||
msg %= filtername | ||||
if repo.ui.tracebackflag and repo.ui.debugflag: | ||||
# XXX use ui.write_err | ||||
util.debugstacktrace( | ||||
msg, | ||||
f=repo.ui._fout, | ||||
otherf=repo.ui._ferr, | ||||
prefix=b'debug.filters: ', | ||||
) | ||||
else: | ||||
repo.ui.debug(b'debug.filters: %s\n' % msg) | ||||
Pierre-Yves David
|
r18101 | func = filtertable[filtername] | ||
Pulkit Goyal
|
r35509 | if visibilityexceptions: | ||
return func(repo.unfiltered, visibilityexceptions) | ||||
Pierre-Yves David
|
r18101 | repo.filteredrevcache[filtername] = func(repo.unfiltered()) | ||
return repo.filteredrevcache[filtername] | ||||
Pierre-Yves David
|
r18100 | |||
Augie Fackler
|
r43346 | |||
Martin von Zweigbergk
|
r43746 | def wrapchangelog(unfichangelog, filteredrevs): | ||
cl = copy.copy(unfichangelog) | ||||
cl.filteredrevs = filteredrevs | ||||
Martin von Zweigbergk
|
r43747 | |||
Martin von Zweigbergk
|
r43910 | class filteredchangelog(filteredchangelogmixin, cl.__class__): | ||
pass | ||||
cl.__class__ = filteredchangelog | ||||
Martin von Zweigbergk
|
r43747 | |||
Martin von Zweigbergk
|
r43797 | return cl | ||
Martin von Zweigbergk
|
r43749 | |||
Gregory Szorc
|
r49801 | class filteredchangelogmixin: | ||
Martin von Zweigbergk
|
r43797 | def tiprev(self): | ||
"""filtered version of revlog.tiprev""" | ||||
Manuel Jacob
|
r50179 | for i in range(len(self) - 1, -2, -1): | ||
Martin von Zweigbergk
|
r43797 | if i not in self.filteredrevs: | ||
return i | ||||
Martin von Zweigbergk
|
r43750 | |||
Martin von Zweigbergk
|
r43797 | def __contains__(self, rev): | ||
"""filtered version of revlog.__contains__""" | ||||
return 0 <= rev < len(self) and rev not in self.filteredrevs | ||||
Martin von Zweigbergk
|
r43750 | |||
Martin von Zweigbergk
|
r43797 | def __iter__(self): | ||
"""filtered version of revlog.__iter__""" | ||||
Martin von Zweigbergk
|
r43750 | |||
Martin von Zweigbergk
|
r43797 | def filterediter(): | ||
Manuel Jacob
|
r50179 | for i in range(len(self)): | ||
Martin von Zweigbergk
|
r43751 | if i not in self.filteredrevs: | ||
yield i | ||||
Martin von Zweigbergk
|
r43797 | return filterediter() | ||
def revs(self, start=0, stop=None): | ||||
"""filtered version of revlog.revs""" | ||||
for i in super(filteredchangelogmixin, self).revs(start, stop): | ||||
if i not in self.filteredrevs: | ||||
yield i | ||||
Martin von Zweigbergk
|
r43752 | |||
Martin von Zweigbergk
|
r43797 | def _checknofilteredinrevs(self, revs): | ||
"""raise the appropriate error if 'revs' contains a filtered revision | ||||
This returns a version of 'revs' to be used thereafter by the caller. | ||||
In particular, if revs is an iterator, it is converted into a set. | ||||
""" | ||||
r51821 | if hasattr(revs, '__next__'): | |||
Martin von Zweigbergk
|
r43797 | # Note that inspect.isgenerator() is not true for iterators, | ||
revs = set(revs) | ||||
Martin von Zweigbergk
|
r43752 | |||
Martin von Zweigbergk
|
r43797 | filteredrevs = self.filteredrevs | ||
r51821 | if hasattr(revs, 'first'): # smartset | |||
Martin von Zweigbergk
|
r43797 | offenders = revs & filteredrevs | ||
else: | ||||
offenders = filteredrevs.intersection(revs) | ||||
Martin von Zweigbergk
|
r43752 | |||
Martin von Zweigbergk
|
r43797 | for rev in offenders: | ||
raise error.FilteredIndexError(rev) | ||||
return revs | ||||
Martin von Zweigbergk
|
r43752 | |||
Raphaël Gomès
|
r52155 | def _head_node_ids(self): | ||
# no Rust fast path implemented yet, so just loop in Python | ||||
return [self.node(r) for r in self.headrevs()] | ||||
r52870 | def headrevs(self, revs=None, stop_rev=None): | |||
Martin von Zweigbergk
|
r43797 | if revs is None: | ||
r52872 | return self.index.headrevs(self.filteredrevs, stop_rev) | |||
# it is ignored from here, so better double check we passed the right argument | ||||
r52870 | assert stop_rev is None | |||
Martin von Zweigbergk
|
r43752 | |||
Martin von Zweigbergk
|
r43797 | revs = self._checknofilteredinrevs(revs) | ||
return super(filteredchangelogmixin, self).headrevs(revs) | ||||
def strip(self, *args, **kwargs): | ||||
# XXX make something better than assert | ||||
# We can't expect proper strip behavior if we are filtered. | ||||
assert not self.filteredrevs | ||||
super(filteredchangelogmixin, self).strip(*args, **kwargs) | ||||
Martin von Zweigbergk
|
r43753 | |||
Martin von Zweigbergk
|
r43797 | def rev(self, node): | ||
"""filtered version of revlog.rev""" | ||||
r = super(filteredchangelogmixin, self).rev(node) | ||||
if r in self.filteredrevs: | ||||
raise error.FilteredLookupError( | ||||
r47925 | hex(node), self.display_id, _(b'filtered node') | |||
Martin von Zweigbergk
|
r43797 | ) | ||
return r | ||||
Martin von Zweigbergk
|
r43755 | |||
Martin von Zweigbergk
|
r43797 | def node(self, rev): | ||
"""filtered version of revlog.node""" | ||||
if rev in self.filteredrevs: | ||||
raise error.FilteredIndexError(rev) | ||||
return super(filteredchangelogmixin, self).node(rev) | ||||
def linkrev(self, rev): | ||||
"""filtered version of revlog.linkrev""" | ||||
if rev in self.filteredrevs: | ||||
raise error.FilteredIndexError(rev) | ||||
return super(filteredchangelogmixin, self).linkrev(rev) | ||||
Martin von Zweigbergk
|
r43756 | |||
Martin von Zweigbergk
|
r43797 | def parentrevs(self, rev): | ||
"""filtered version of revlog.parentrevs""" | ||||
if rev in self.filteredrevs: | ||||
raise error.FilteredIndexError(rev) | ||||
return super(filteredchangelogmixin, self).parentrevs(rev) | ||||
Martin von Zweigbergk
|
r43757 | |||
Martin von Zweigbergk
|
r43797 | def flags(self, rev): | ||
"""filtered version of revlog.flags""" | ||||
if rev in self.filteredrevs: | ||||
raise error.FilteredIndexError(rev) | ||||
return super(filteredchangelogmixin, self).flags(rev) | ||||
Martin von Zweigbergk
|
r43746 | |||
Gregory Szorc
|
r49801 | class repoview: | ||
Pierre-Yves David
|
r18100 | """Provide a read/write view of a repo through a filtered changelog | ||
This object is used to access a filtered version of a repository without | ||||
altering the original repository object itself. We can not alter the | ||||
original object for two main reasons: | ||||
- It prevents the use of a repo with multiple filters at the same time. In | ||||
particular when multiple threads are involved. | ||||
- It makes scope of the filtering harder to control. | ||||
This object behaves very closely to the original repository. All attribute | ||||
operations are done on the original repository: | ||||
- An access to `repoview.someattr` actually returns `repo.someattr`, | ||||
- A write to `repoview.someattr` actually sets value of `repo.someattr`, | ||||
- A deletion of `repoview.someattr` actually drops `someattr` | ||||
from `repo.__dict__`. | ||||
The only exception is the `changelog` property. It is overridden to return | ||||
a (surface) copy of `repo.changelog` with some revisions filtered. The | ||||
`filtername` attribute of the view control the revisions that need to be | ||||
filtered. (the fact the changelog is copied is an implementation detail). | ||||
Unlike attributes, this object intercepts all method calls. This means that | ||||
all methods are run on the `repoview` object with the filtered `changelog` | ||||
property. For this purpose the simple `repoview` class must be mixed with | ||||
the actual class of the repository. This ensures that the resulting | ||||
`repoview` object have the very same methods than the repo object. This | ||||
leads to the property below. | ||||
repoview.method() --> repo.__class__.method(repoview) | ||||
The inheritance has to be done dynamically because `repo` can be of any | ||||
Mads Kiilerich
|
r18644 | subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`. | ||
Pierre-Yves David
|
r18100 | """ | ||
Pulkit Goyal
|
r35508 | def __init__(self, repo, filtername, visibilityexceptions=None): | ||
r52362 | if filtername is None: | |||
msg = "repoview should have a non-None filtername" | ||||
raise error.ProgrammingError(msg) | ||||
Augie Fackler
|
r43906 | object.__setattr__(self, '_unfilteredrepo', repo) | ||
object.__setattr__(self, 'filtername', filtername) | ||||
object.__setattr__(self, '_clcachekey', None) | ||||
object.__setattr__(self, '_clcache', None) | ||||
Pulkit Goyal
|
r35508 | # revs which are exceptions and must not be hidden | ||
Augie Fackler
|
r43906 | object.__setattr__(self, '_visibilityexceptions', visibilityexceptions) | ||
Pierre-Yves David
|
r18100 | |||
Mads Kiilerich
|
r18644 | # not a propertycache on purpose we shall implement a proper cache later | ||
Pierre-Yves David
|
r18100 | @property | ||
def changelog(self): | ||||
"""return a filtered version of the changeset | ||||
this changelog must not be used for writing""" | ||||
# some cache may be implemented later | ||||
Pierre-Yves David
|
r18445 | unfi = self._unfilteredrepo | ||
unfichangelog = unfi.changelog | ||||
Pierre-Yves David
|
r27258 | # bypass call to changelog.method | ||
unfiindex = unfichangelog.index | ||||
Martin von Zweigbergk
|
r38887 | unfilen = len(unfiindex) | ||
Pierre-Yves David
|
r27258 | unfinode = unfiindex[unfilen - 1][7] | ||
Augie Fackler
|
r43534 | with util.timedcm('repo filter for %s', self.filtername): | ||
revs = filterrevs(unfi, self.filtername, self._visibilityexceptions) | ||||
Pierre-Yves David
|
r18445 | cl = self._clcache | ||
r51997 | newkey = (unfilen, unfinode, hash(revs), unfichangelog.is_delaying) | |||
FUJIWARA Katsunori
|
r28265 | # if cl.index is not unfiindex, unfi.changelog would be | ||
# recreated, and our clcache refers to garbage object | ||||
Augie Fackler
|
r43346 | if cl is not None and ( | ||
cl.index is not unfiindex or newkey != self._clcachekey | ||||
): | ||||
Pierre-Yves David
|
r27258 | cl = None | ||
Pierre-Yves David
|
r18445 | # could have been made None by the previous if | ||
if cl is None: | ||||
Martin von Zweigbergk
|
r43759 | # Only filter if there's something to filter | ||
cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog | ||||
Augie Fackler
|
r43906 | object.__setattr__(self, '_clcache', cl) | ||
object.__setattr__(self, '_clcachekey', newkey) | ||||
Pierre-Yves David
|
r18100 | return cl | ||
def unfiltered(self): | ||||
"""Return an unfiltered version of a repo""" | ||||
return self._unfilteredrepo | ||||
Pulkit Goyal
|
r35508 | def filtered(self, name, visibilityexceptions=None): | ||
Pierre-Yves David
|
r18100 | """Return a filtered version of a repository""" | ||
Pulkit Goyal
|
r35508 | if name == self.filtername and not visibilityexceptions: | ||
Pierre-Yves David
|
r18100 | return self | ||
Pulkit Goyal
|
r35508 | return self.unfiltered().filtered(name, visibilityexceptions) | ||
Pierre-Yves David
|
r18100 | |||
Yuya Nishihara
|
r35249 | def __repr__(self): | ||
Augie Fackler
|
r43906 | return '<%s:%s %r>' % ( | ||
Augie Fackler
|
r43346 | self.__class__.__name__, | ||
pycompat.sysstr(self.filtername), | ||||
self.unfiltered(), | ||||
) | ||||
Yuya Nishihara
|
r35249 | |||
Pierre-Yves David
|
r18100 | # everything access are forwarded to the proxied repo | ||
def __getattr__(self, attr): | ||||
return getattr(self._unfilteredrepo, attr) | ||||
def __setattr__(self, attr, value): | ||||
return setattr(self._unfilteredrepo, attr, value) | ||||
def __delattr__(self, attr): | ||||
return delattr(self._unfilteredrepo, attr) | ||||
Yuya Nishihara
|
r35248 | |||
Augie Fackler
|
r43346 | |||
Georges Racinet
|
r47776 | # Dynamically created classes introduce memory cycles via __mro__. See | ||
# https://bugs.python.org/issue17950. | ||||
# This need of the garbage collector can turn into memory leak in | ||||
# Python <3.4, which is the first version released with PEP 442. | ||||
Yuya Nishihara
|
r35248 | _filteredrepotypes = weakref.WeakKeyDictionary() | ||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r35248 | def newtype(base): | ||
"""Create a new type with the repoview mixin and the given base class""" | ||||
Georges Racinet
|
r47775 | ref = _filteredrepotypes.get(base) | ||
if ref is not None: | ||||
cls = ref() | ||||
if cls is not None: | ||||
return cls | ||||
Augie Fackler
|
r43346 | |||
Georges Racinet
|
r47774 | class filteredrepo(repoview, base): | ||
pass | ||||
Augie Fackler
|
r43346 | |||
Georges Racinet
|
r47775 | _filteredrepotypes[base] = weakref.ref(filteredrepo) | ||
# do not reread from weakref to be 100% sure not to return None | ||||
Georges Racinet
|
r47774 | return filteredrepo | ||