##// END OF EJS Templates
repoview: fix memory leak of filtered repo classes...
Georges Racinet -
r47775:76ae43d5 stable
parent child Browse files
Show More
@@ -1,483 +1,486 b''
1 # repoview.py - Filtered view of a localrepo object
1 # repoview.py - Filtered view of a localrepo object
2 #
2 #
3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 # Logilab SA <contact@logilab.fr>
4 # Logilab SA <contact@logilab.fr>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import copy
11 import copy
12 import weakref
12 import weakref
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullrev,
17 nullrev,
18 )
18 )
19 from .pycompat import (
19 from .pycompat import (
20 delattr,
20 delattr,
21 getattr,
21 getattr,
22 setattr,
22 setattr,
23 )
23 )
24 from . import (
24 from . import (
25 error,
25 error,
26 obsolete,
26 obsolete,
27 phases,
27 phases,
28 pycompat,
28 pycompat,
29 tags as tagsmod,
29 tags as tagsmod,
30 util,
30 util,
31 )
31 )
32 from .utils import repoviewutil
32 from .utils import repoviewutil
33
33
34
34
35 def hideablerevs(repo):
35 def hideablerevs(repo):
36 """Revision candidates to be hidden
36 """Revision candidates to be hidden
37
37
38 This is a standalone function to allow extensions to wrap it.
38 This is a standalone function to allow extensions to wrap it.
39
39
40 Because we use the set of immutable changesets as a fallback subset in
40 Because we use the set of immutable changesets as a fallback subset in
41 branchmap (see mercurial.utils.repoviewutils.subsettable), you cannot set
41 branchmap (see mercurial.utils.repoviewutils.subsettable), you cannot set
42 "public" changesets as "hideable". Doing so would break multiple code
42 "public" changesets as "hideable". Doing so would break multiple code
43 assertions and lead to crashes."""
43 assertions and lead to crashes."""
44 obsoletes = obsolete.getrevs(repo, b'obsolete')
44 obsoletes = obsolete.getrevs(repo, b'obsolete')
45 internals = repo._phasecache.getrevset(repo, phases.localhiddenphases)
45 internals = repo._phasecache.getrevset(repo, phases.localhiddenphases)
46 internals = frozenset(internals)
46 internals = frozenset(internals)
47 return obsoletes | internals
47 return obsoletes | internals
48
48
49
49
50 def pinnedrevs(repo):
50 def pinnedrevs(repo):
51 """revisions blocking hidden changesets from being filtered"""
51 """revisions blocking hidden changesets from being filtered"""
52
52
53 cl = repo.changelog
53 cl = repo.changelog
54 pinned = set()
54 pinned = set()
55 pinned.update([par.rev() for par in repo[None].parents()])
55 pinned.update([par.rev() for par in repo[None].parents()])
56 pinned.update([cl.rev(bm) for bm in repo._bookmarks.values()])
56 pinned.update([cl.rev(bm) for bm in repo._bookmarks.values()])
57
57
58 tags = {}
58 tags = {}
59 tagsmod.readlocaltags(repo.ui, repo, tags, {})
59 tagsmod.readlocaltags(repo.ui, repo, tags, {})
60 if tags:
60 if tags:
61 rev = cl.index.get_rev
61 rev = cl.index.get_rev
62 pinned.update(rev(t[0]) for t in tags.values())
62 pinned.update(rev(t[0]) for t in tags.values())
63 pinned.discard(None)
63 pinned.discard(None)
64
64
65 # Avoid cycle: mercurial.filemerge -> mercurial.templater ->
65 # Avoid cycle: mercurial.filemerge -> mercurial.templater ->
66 # mercurial.templatefuncs -> mercurial.revset -> mercurial.repoview ->
66 # mercurial.templatefuncs -> mercurial.revset -> mercurial.repoview ->
67 # mercurial.mergestate -> mercurial.filemerge
67 # mercurial.mergestate -> mercurial.filemerge
68 from . import mergestate
68 from . import mergestate
69
69
70 ms = mergestate.mergestate.read(repo)
70 ms = mergestate.mergestate.read(repo)
71 if ms.active() and ms.unresolvedcount():
71 if ms.active() and ms.unresolvedcount():
72 for node in (ms.local, ms.other):
72 for node in (ms.local, ms.other):
73 rev = cl.index.get_rev(node)
73 rev = cl.index.get_rev(node)
74 if rev is not None:
74 if rev is not None:
75 pinned.add(rev)
75 pinned.add(rev)
76
76
77 return pinned
77 return pinned
78
78
79
79
80 def _revealancestors(pfunc, hidden, revs):
80 def _revealancestors(pfunc, hidden, revs):
81 """reveals contiguous chains of hidden ancestors of 'revs' by removing them
81 """reveals contiguous chains of hidden ancestors of 'revs' by removing them
82 from 'hidden'
82 from 'hidden'
83
83
84 - pfunc(r): a funtion returning parent of 'r',
84 - pfunc(r): a funtion returning parent of 'r',
85 - hidden: the (preliminary) hidden revisions, to be updated
85 - hidden: the (preliminary) hidden revisions, to be updated
86 - revs: iterable of revnum,
86 - revs: iterable of revnum,
87
87
88 (Ancestors are revealed exclusively, i.e. the elements in 'revs' are
88 (Ancestors are revealed exclusively, i.e. the elements in 'revs' are
89 *not* revealed)
89 *not* revealed)
90 """
90 """
91 stack = list(revs)
91 stack = list(revs)
92 while stack:
92 while stack:
93 for p in pfunc(stack.pop()):
93 for p in pfunc(stack.pop()):
94 if p != nullrev and p in hidden:
94 if p != nullrev and p in hidden:
95 hidden.remove(p)
95 hidden.remove(p)
96 stack.append(p)
96 stack.append(p)
97
97
98
98
99 def computehidden(repo, visibilityexceptions=None):
99 def computehidden(repo, visibilityexceptions=None):
100 """compute the set of hidden revision to filter
100 """compute the set of hidden revision to filter
101
101
102 During most operation hidden should be filtered."""
102 During most operation hidden should be filtered."""
103 assert not repo.changelog.filteredrevs
103 assert not repo.changelog.filteredrevs
104
104
105 hidden = hideablerevs(repo)
105 hidden = hideablerevs(repo)
106 if hidden:
106 if hidden:
107 hidden = set(hidden - pinnedrevs(repo))
107 hidden = set(hidden - pinnedrevs(repo))
108 if visibilityexceptions:
108 if visibilityexceptions:
109 hidden -= visibilityexceptions
109 hidden -= visibilityexceptions
110 pfunc = repo.changelog.parentrevs
110 pfunc = repo.changelog.parentrevs
111 mutable = repo._phasecache.getrevset(repo, phases.mutablephases)
111 mutable = repo._phasecache.getrevset(repo, phases.mutablephases)
112
112
113 visible = mutable - hidden
113 visible = mutable - hidden
114 _revealancestors(pfunc, hidden, visible)
114 _revealancestors(pfunc, hidden, visible)
115 return frozenset(hidden)
115 return frozenset(hidden)
116
116
117
117
118 def computesecret(repo, visibilityexceptions=None):
118 def computesecret(repo, visibilityexceptions=None):
119 """compute the set of revision that can never be exposed through hgweb
119 """compute the set of revision that can never be exposed through hgweb
120
120
121 Changeset in the secret phase (or above) should stay unaccessible."""
121 Changeset in the secret phase (or above) should stay unaccessible."""
122 assert not repo.changelog.filteredrevs
122 assert not repo.changelog.filteredrevs
123 secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases)
123 secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases)
124 return frozenset(secrets)
124 return frozenset(secrets)
125
125
126
126
127 def computeunserved(repo, visibilityexceptions=None):
127 def computeunserved(repo, visibilityexceptions=None):
128 """compute the set of revision that should be filtered when used a server
128 """compute the set of revision that should be filtered when used a server
129
129
130 Secret and hidden changeset should not pretend to be here."""
130 Secret and hidden changeset should not pretend to be here."""
131 assert not repo.changelog.filteredrevs
131 assert not repo.changelog.filteredrevs
132 # fast path in simple case to avoid impact of non optimised code
132 # fast path in simple case to avoid impact of non optimised code
133 hiddens = filterrevs(repo, b'visible')
133 hiddens = filterrevs(repo, b'visible')
134 secrets = filterrevs(repo, b'served.hidden')
134 secrets = filterrevs(repo, b'served.hidden')
135 if secrets:
135 if secrets:
136 return frozenset(hiddens | secrets)
136 return frozenset(hiddens | secrets)
137 else:
137 else:
138 return hiddens
138 return hiddens
139
139
140
140
141 def computemutable(repo, visibilityexceptions=None):
141 def computemutable(repo, visibilityexceptions=None):
142 assert not repo.changelog.filteredrevs
142 assert not repo.changelog.filteredrevs
143 # fast check to avoid revset call on huge repo
143 # fast check to avoid revset call on huge repo
144 if repo._phasecache.hasnonpublicphases(repo):
144 if repo._phasecache.hasnonpublicphases(repo):
145 return frozenset(repo._phasecache.getrevset(repo, phases.mutablephases))
145 return frozenset(repo._phasecache.getrevset(repo, phases.mutablephases))
146 return frozenset()
146 return frozenset()
147
147
148
148
149 def computeimpactable(repo, visibilityexceptions=None):
149 def computeimpactable(repo, visibilityexceptions=None):
150 """Everything impactable by mutable revision
150 """Everything impactable by mutable revision
151
151
152 The immutable filter still have some chance to get invalidated. This will
152 The immutable filter still have some chance to get invalidated. This will
153 happen when:
153 happen when:
154
154
155 - you garbage collect hidden changeset,
155 - you garbage collect hidden changeset,
156 - public phase is moved backward,
156 - public phase is moved backward,
157 - something is changed in the filtering (this could be fixed)
157 - something is changed in the filtering (this could be fixed)
158
158
159 This filter out any mutable changeset and any public changeset that may be
159 This filter out any mutable changeset and any public changeset that may be
160 impacted by something happening to a mutable revision.
160 impacted by something happening to a mutable revision.
161
161
162 This is achieved by filtered everything with a revision number equal or
162 This is achieved by filtered everything with a revision number equal or
163 higher than the first mutable changeset is filtered."""
163 higher than the first mutable changeset is filtered."""
164 assert not repo.changelog.filteredrevs
164 assert not repo.changelog.filteredrevs
165 cl = repo.changelog
165 cl = repo.changelog
166 firstmutable = len(cl)
166 firstmutable = len(cl)
167 roots = repo._phasecache.nonpublicphaseroots(repo)
167 roots = repo._phasecache.nonpublicphaseroots(repo)
168 if roots:
168 if roots:
169 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
169 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
170 # protect from nullrev root
170 # protect from nullrev root
171 firstmutable = max(0, firstmutable)
171 firstmutable = max(0, firstmutable)
172 return frozenset(pycompat.xrange(firstmutable, len(cl)))
172 return frozenset(pycompat.xrange(firstmutable, len(cl)))
173
173
174
174
175 # function to compute filtered set
175 # function to compute filtered set
176 #
176 #
177 # When adding a new filter you MUST update the table at:
177 # When adding a new filter you MUST update the table at:
178 # mercurial.utils.repoviewutil.subsettable
178 # mercurial.utils.repoviewutil.subsettable
179 # Otherwise your filter will have to recompute all its branches cache
179 # Otherwise your filter will have to recompute all its branches cache
180 # from scratch (very slow).
180 # from scratch (very slow).
181 filtertable = {
181 filtertable = {
182 b'visible': computehidden,
182 b'visible': computehidden,
183 b'visible-hidden': computehidden,
183 b'visible-hidden': computehidden,
184 b'served.hidden': computesecret,
184 b'served.hidden': computesecret,
185 b'served': computeunserved,
185 b'served': computeunserved,
186 b'immutable': computemutable,
186 b'immutable': computemutable,
187 b'base': computeimpactable,
187 b'base': computeimpactable,
188 }
188 }
189
189
190 # set of filter level that will include the working copy parent no matter what.
190 # set of filter level that will include the working copy parent no matter what.
191 filter_has_wc = {b'visible', b'visible-hidden'}
191 filter_has_wc = {b'visible', b'visible-hidden'}
192
192
193 _basefiltername = list(filtertable)
193 _basefiltername = list(filtertable)
194
194
195
195
196 def extrafilter(ui):
196 def extrafilter(ui):
197 """initialize extra filter and return its id
197 """initialize extra filter and return its id
198
198
199 If extra filtering is configured, we make sure the associated filtered view
199 If extra filtering is configured, we make sure the associated filtered view
200 are declared and return the associated id.
200 are declared and return the associated id.
201 """
201 """
202 frevs = ui.config(b'experimental', b'extra-filter-revs')
202 frevs = ui.config(b'experimental', b'extra-filter-revs')
203 if frevs is None:
203 if frevs is None:
204 return None
204 return None
205
205
206 fid = pycompat.sysbytes(util.DIGESTS[b'sha1'](frevs).hexdigest())[:12]
206 fid = pycompat.sysbytes(util.DIGESTS[b'sha1'](frevs).hexdigest())[:12]
207
207
208 combine = lambda fname: fname + b'%' + fid
208 combine = lambda fname: fname + b'%' + fid
209
209
210 subsettable = repoviewutil.subsettable
210 subsettable = repoviewutil.subsettable
211
211
212 if combine(b'base') not in filtertable:
212 if combine(b'base') not in filtertable:
213 for name in _basefiltername:
213 for name in _basefiltername:
214
214
215 def extrafilteredrevs(repo, *args, **kwargs):
215 def extrafilteredrevs(repo, *args, **kwargs):
216 baserevs = filtertable[name](repo, *args, **kwargs)
216 baserevs = filtertable[name](repo, *args, **kwargs)
217 extrarevs = frozenset(repo.revs(frevs))
217 extrarevs = frozenset(repo.revs(frevs))
218 return baserevs | extrarevs
218 return baserevs | extrarevs
219
219
220 filtertable[combine(name)] = extrafilteredrevs
220 filtertable[combine(name)] = extrafilteredrevs
221 if name in subsettable:
221 if name in subsettable:
222 subsettable[combine(name)] = combine(subsettable[name])
222 subsettable[combine(name)] = combine(subsettable[name])
223 return fid
223 return fid
224
224
225
225
226 def filterrevs(repo, filtername, visibilityexceptions=None):
226 def filterrevs(repo, filtername, visibilityexceptions=None):
227 """returns set of filtered revision for this filter name
227 """returns set of filtered revision for this filter name
228
228
229 visibilityexceptions is a set of revs which must are exceptions for
229 visibilityexceptions is a set of revs which must are exceptions for
230 hidden-state and must be visible. They are dynamic and hence we should not
230 hidden-state and must be visible. They are dynamic and hence we should not
231 cache it's result"""
231 cache it's result"""
232 if filtername not in repo.filteredrevcache:
232 if filtername not in repo.filteredrevcache:
233 if repo.ui.configbool(b'devel', b'debug.repo-filters'):
233 if repo.ui.configbool(b'devel', b'debug.repo-filters'):
234 msg = b'computing revision filter for "%s"'
234 msg = b'computing revision filter for "%s"'
235 msg %= filtername
235 msg %= filtername
236 if repo.ui.tracebackflag and repo.ui.debugflag:
236 if repo.ui.tracebackflag and repo.ui.debugflag:
237 # XXX use ui.write_err
237 # XXX use ui.write_err
238 util.debugstacktrace(
238 util.debugstacktrace(
239 msg,
239 msg,
240 f=repo.ui._fout,
240 f=repo.ui._fout,
241 otherf=repo.ui._ferr,
241 otherf=repo.ui._ferr,
242 prefix=b'debug.filters: ',
242 prefix=b'debug.filters: ',
243 )
243 )
244 else:
244 else:
245 repo.ui.debug(b'debug.filters: %s\n' % msg)
245 repo.ui.debug(b'debug.filters: %s\n' % msg)
246 func = filtertable[filtername]
246 func = filtertable[filtername]
247 if visibilityexceptions:
247 if visibilityexceptions:
248 return func(repo.unfiltered, visibilityexceptions)
248 return func(repo.unfiltered, visibilityexceptions)
249 repo.filteredrevcache[filtername] = func(repo.unfiltered())
249 repo.filteredrevcache[filtername] = func(repo.unfiltered())
250 return repo.filteredrevcache[filtername]
250 return repo.filteredrevcache[filtername]
251
251
252
252
253 def wrapchangelog(unfichangelog, filteredrevs):
253 def wrapchangelog(unfichangelog, filteredrevs):
254 cl = copy.copy(unfichangelog)
254 cl = copy.copy(unfichangelog)
255 cl.filteredrevs = filteredrevs
255 cl.filteredrevs = filteredrevs
256
256
257 class filteredchangelog(filteredchangelogmixin, cl.__class__):
257 class filteredchangelog(filteredchangelogmixin, cl.__class__):
258 pass
258 pass
259
259
260 cl.__class__ = filteredchangelog
260 cl.__class__ = filteredchangelog
261
261
262 return cl
262 return cl
263
263
264
264
265 class filteredchangelogmixin(object):
265 class filteredchangelogmixin(object):
266 def tiprev(self):
266 def tiprev(self):
267 """filtered version of revlog.tiprev"""
267 """filtered version of revlog.tiprev"""
268 for i in pycompat.xrange(len(self) - 1, -2, -1):
268 for i in pycompat.xrange(len(self) - 1, -2, -1):
269 if i not in self.filteredrevs:
269 if i not in self.filteredrevs:
270 return i
270 return i
271
271
272 def __contains__(self, rev):
272 def __contains__(self, rev):
273 """filtered version of revlog.__contains__"""
273 """filtered version of revlog.__contains__"""
274 return 0 <= rev < len(self) and rev not in self.filteredrevs
274 return 0 <= rev < len(self) and rev not in self.filteredrevs
275
275
276 def __iter__(self):
276 def __iter__(self):
277 """filtered version of revlog.__iter__"""
277 """filtered version of revlog.__iter__"""
278
278
279 def filterediter():
279 def filterediter():
280 for i in pycompat.xrange(len(self)):
280 for i in pycompat.xrange(len(self)):
281 if i not in self.filteredrevs:
281 if i not in self.filteredrevs:
282 yield i
282 yield i
283
283
284 return filterediter()
284 return filterediter()
285
285
286 def revs(self, start=0, stop=None):
286 def revs(self, start=0, stop=None):
287 """filtered version of revlog.revs"""
287 """filtered version of revlog.revs"""
288 for i in super(filteredchangelogmixin, self).revs(start, stop):
288 for i in super(filteredchangelogmixin, self).revs(start, stop):
289 if i not in self.filteredrevs:
289 if i not in self.filteredrevs:
290 yield i
290 yield i
291
291
292 def _checknofilteredinrevs(self, revs):
292 def _checknofilteredinrevs(self, revs):
293 """raise the appropriate error if 'revs' contains a filtered revision
293 """raise the appropriate error if 'revs' contains a filtered revision
294
294
295 This returns a version of 'revs' to be used thereafter by the caller.
295 This returns a version of 'revs' to be used thereafter by the caller.
296 In particular, if revs is an iterator, it is converted into a set.
296 In particular, if revs is an iterator, it is converted into a set.
297 """
297 """
298 safehasattr = util.safehasattr
298 safehasattr = util.safehasattr
299 if safehasattr(revs, '__next__'):
299 if safehasattr(revs, '__next__'):
300 # Note that inspect.isgenerator() is not true for iterators,
300 # Note that inspect.isgenerator() is not true for iterators,
301 revs = set(revs)
301 revs = set(revs)
302
302
303 filteredrevs = self.filteredrevs
303 filteredrevs = self.filteredrevs
304 if safehasattr(revs, 'first'): # smartset
304 if safehasattr(revs, 'first'): # smartset
305 offenders = revs & filteredrevs
305 offenders = revs & filteredrevs
306 else:
306 else:
307 offenders = filteredrevs.intersection(revs)
307 offenders = filteredrevs.intersection(revs)
308
308
309 for rev in offenders:
309 for rev in offenders:
310 raise error.FilteredIndexError(rev)
310 raise error.FilteredIndexError(rev)
311 return revs
311 return revs
312
312
313 def headrevs(self, revs=None):
313 def headrevs(self, revs=None):
314 if revs is None:
314 if revs is None:
315 try:
315 try:
316 return self.index.headrevsfiltered(self.filteredrevs)
316 return self.index.headrevsfiltered(self.filteredrevs)
317 # AttributeError covers non-c-extension environments and
317 # AttributeError covers non-c-extension environments and
318 # old c extensions without filter handling.
318 # old c extensions without filter handling.
319 except AttributeError:
319 except AttributeError:
320 return self._headrevs()
320 return self._headrevs()
321
321
322 revs = self._checknofilteredinrevs(revs)
322 revs = self._checknofilteredinrevs(revs)
323 return super(filteredchangelogmixin, self).headrevs(revs)
323 return super(filteredchangelogmixin, self).headrevs(revs)
324
324
325 def strip(self, *args, **kwargs):
325 def strip(self, *args, **kwargs):
326 # XXX make something better than assert
326 # XXX make something better than assert
327 # We can't expect proper strip behavior if we are filtered.
327 # We can't expect proper strip behavior if we are filtered.
328 assert not self.filteredrevs
328 assert not self.filteredrevs
329 super(filteredchangelogmixin, self).strip(*args, **kwargs)
329 super(filteredchangelogmixin, self).strip(*args, **kwargs)
330
330
331 def rev(self, node):
331 def rev(self, node):
332 """filtered version of revlog.rev"""
332 """filtered version of revlog.rev"""
333 r = super(filteredchangelogmixin, self).rev(node)
333 r = super(filteredchangelogmixin, self).rev(node)
334 if r in self.filteredrevs:
334 if r in self.filteredrevs:
335 raise error.FilteredLookupError(
335 raise error.FilteredLookupError(
336 hex(node), self.indexfile, _(b'filtered node')
336 hex(node), self.indexfile, _(b'filtered node')
337 )
337 )
338 return r
338 return r
339
339
340 def node(self, rev):
340 def node(self, rev):
341 """filtered version of revlog.node"""
341 """filtered version of revlog.node"""
342 if rev in self.filteredrevs:
342 if rev in self.filteredrevs:
343 raise error.FilteredIndexError(rev)
343 raise error.FilteredIndexError(rev)
344 return super(filteredchangelogmixin, self).node(rev)
344 return super(filteredchangelogmixin, self).node(rev)
345
345
346 def linkrev(self, rev):
346 def linkrev(self, rev):
347 """filtered version of revlog.linkrev"""
347 """filtered version of revlog.linkrev"""
348 if rev in self.filteredrevs:
348 if rev in self.filteredrevs:
349 raise error.FilteredIndexError(rev)
349 raise error.FilteredIndexError(rev)
350 return super(filteredchangelogmixin, self).linkrev(rev)
350 return super(filteredchangelogmixin, self).linkrev(rev)
351
351
352 def parentrevs(self, rev):
352 def parentrevs(self, rev):
353 """filtered version of revlog.parentrevs"""
353 """filtered version of revlog.parentrevs"""
354 if rev in self.filteredrevs:
354 if rev in self.filteredrevs:
355 raise error.FilteredIndexError(rev)
355 raise error.FilteredIndexError(rev)
356 return super(filteredchangelogmixin, self).parentrevs(rev)
356 return super(filteredchangelogmixin, self).parentrevs(rev)
357
357
358 def flags(self, rev):
358 def flags(self, rev):
359 """filtered version of revlog.flags"""
359 """filtered version of revlog.flags"""
360 if rev in self.filteredrevs:
360 if rev in self.filteredrevs:
361 raise error.FilteredIndexError(rev)
361 raise error.FilteredIndexError(rev)
362 return super(filteredchangelogmixin, self).flags(rev)
362 return super(filteredchangelogmixin, self).flags(rev)
363
363
364
364
365 class repoview(object):
365 class repoview(object):
366 """Provide a read/write view of a repo through a filtered changelog
366 """Provide a read/write view of a repo through a filtered changelog
367
367
368 This object is used to access a filtered version of a repository without
368 This object is used to access a filtered version of a repository without
369 altering the original repository object itself. We can not alter the
369 altering the original repository object itself. We can not alter the
370 original object for two main reasons:
370 original object for two main reasons:
371 - It prevents the use of a repo with multiple filters at the same time. In
371 - It prevents the use of a repo with multiple filters at the same time. In
372 particular when multiple threads are involved.
372 particular when multiple threads are involved.
373 - It makes scope of the filtering harder to control.
373 - It makes scope of the filtering harder to control.
374
374
375 This object behaves very closely to the original repository. All attribute
375 This object behaves very closely to the original repository. All attribute
376 operations are done on the original repository:
376 operations are done on the original repository:
377 - An access to `repoview.someattr` actually returns `repo.someattr`,
377 - An access to `repoview.someattr` actually returns `repo.someattr`,
378 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
378 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
379 - A deletion of `repoview.someattr` actually drops `someattr`
379 - A deletion of `repoview.someattr` actually drops `someattr`
380 from `repo.__dict__`.
380 from `repo.__dict__`.
381
381
382 The only exception is the `changelog` property. It is overridden to return
382 The only exception is the `changelog` property. It is overridden to return
383 a (surface) copy of `repo.changelog` with some revisions filtered. The
383 a (surface) copy of `repo.changelog` with some revisions filtered. The
384 `filtername` attribute of the view control the revisions that need to be
384 `filtername` attribute of the view control the revisions that need to be
385 filtered. (the fact the changelog is copied is an implementation detail).
385 filtered. (the fact the changelog is copied is an implementation detail).
386
386
387 Unlike attributes, this object intercepts all method calls. This means that
387 Unlike attributes, this object intercepts all method calls. This means that
388 all methods are run on the `repoview` object with the filtered `changelog`
388 all methods are run on the `repoview` object with the filtered `changelog`
389 property. For this purpose the simple `repoview` class must be mixed with
389 property. For this purpose the simple `repoview` class must be mixed with
390 the actual class of the repository. This ensures that the resulting
390 the actual class of the repository. This ensures that the resulting
391 `repoview` object have the very same methods than the repo object. This
391 `repoview` object have the very same methods than the repo object. This
392 leads to the property below.
392 leads to the property below.
393
393
394 repoview.method() --> repo.__class__.method(repoview)
394 repoview.method() --> repo.__class__.method(repoview)
395
395
396 The inheritance has to be done dynamically because `repo` can be of any
396 The inheritance has to be done dynamically because `repo` can be of any
397 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
397 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
398 """
398 """
399
399
400 def __init__(self, repo, filtername, visibilityexceptions=None):
400 def __init__(self, repo, filtername, visibilityexceptions=None):
401 object.__setattr__(self, '_unfilteredrepo', repo)
401 object.__setattr__(self, '_unfilteredrepo', repo)
402 object.__setattr__(self, 'filtername', filtername)
402 object.__setattr__(self, 'filtername', filtername)
403 object.__setattr__(self, '_clcachekey', None)
403 object.__setattr__(self, '_clcachekey', None)
404 object.__setattr__(self, '_clcache', None)
404 object.__setattr__(self, '_clcache', None)
405 # revs which are exceptions and must not be hidden
405 # revs which are exceptions and must not be hidden
406 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
406 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
407
407
408 # not a propertycache on purpose we shall implement a proper cache later
408 # not a propertycache on purpose we shall implement a proper cache later
409 @property
409 @property
410 def changelog(self):
410 def changelog(self):
411 """return a filtered version of the changeset
411 """return a filtered version of the changeset
412
412
413 this changelog must not be used for writing"""
413 this changelog must not be used for writing"""
414 # some cache may be implemented later
414 # some cache may be implemented later
415 unfi = self._unfilteredrepo
415 unfi = self._unfilteredrepo
416 unfichangelog = unfi.changelog
416 unfichangelog = unfi.changelog
417 # bypass call to changelog.method
417 # bypass call to changelog.method
418 unfiindex = unfichangelog.index
418 unfiindex = unfichangelog.index
419 unfilen = len(unfiindex)
419 unfilen = len(unfiindex)
420 unfinode = unfiindex[unfilen - 1][7]
420 unfinode = unfiindex[unfilen - 1][7]
421 with util.timedcm('repo filter for %s', self.filtername):
421 with util.timedcm('repo filter for %s', self.filtername):
422 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
422 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
423 cl = self._clcache
423 cl = self._clcache
424 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
424 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
425 # if cl.index is not unfiindex, unfi.changelog would be
425 # if cl.index is not unfiindex, unfi.changelog would be
426 # recreated, and our clcache refers to garbage object
426 # recreated, and our clcache refers to garbage object
427 if cl is not None and (
427 if cl is not None and (
428 cl.index is not unfiindex or newkey != self._clcachekey
428 cl.index is not unfiindex or newkey != self._clcachekey
429 ):
429 ):
430 cl = None
430 cl = None
431 # could have been made None by the previous if
431 # could have been made None by the previous if
432 if cl is None:
432 if cl is None:
433 # Only filter if there's something to filter
433 # Only filter if there's something to filter
434 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
434 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
435 object.__setattr__(self, '_clcache', cl)
435 object.__setattr__(self, '_clcache', cl)
436 object.__setattr__(self, '_clcachekey', newkey)
436 object.__setattr__(self, '_clcachekey', newkey)
437 return cl
437 return cl
438
438
439 def unfiltered(self):
439 def unfiltered(self):
440 """Return an unfiltered version of a repo"""
440 """Return an unfiltered version of a repo"""
441 return self._unfilteredrepo
441 return self._unfilteredrepo
442
442
443 def filtered(self, name, visibilityexceptions=None):
443 def filtered(self, name, visibilityexceptions=None):
444 """Return a filtered version of a repository"""
444 """Return a filtered version of a repository"""
445 if name == self.filtername and not visibilityexceptions:
445 if name == self.filtername and not visibilityexceptions:
446 return self
446 return self
447 return self.unfiltered().filtered(name, visibilityexceptions)
447 return self.unfiltered().filtered(name, visibilityexceptions)
448
448
449 def __repr__(self):
449 def __repr__(self):
450 return '<%s:%s %r>' % (
450 return '<%s:%s %r>' % (
451 self.__class__.__name__,
451 self.__class__.__name__,
452 pycompat.sysstr(self.filtername),
452 pycompat.sysstr(self.filtername),
453 self.unfiltered(),
453 self.unfiltered(),
454 )
454 )
455
455
456 # everything access are forwarded to the proxied repo
456 # everything access are forwarded to the proxied repo
457 def __getattr__(self, attr):
457 def __getattr__(self, attr):
458 return getattr(self._unfilteredrepo, attr)
458 return getattr(self._unfilteredrepo, attr)
459
459
460 def __setattr__(self, attr, value):
460 def __setattr__(self, attr, value):
461 return setattr(self._unfilteredrepo, attr, value)
461 return setattr(self._unfilteredrepo, attr, value)
462
462
463 def __delattr__(self, attr):
463 def __delattr__(self, attr):
464 return delattr(self._unfilteredrepo, attr)
464 return delattr(self._unfilteredrepo, attr)
465
465
466
466
467 # Python <3.4 easily leaks types via __mro__. See
467 # Python <3.4 easily leaks types via __mro__. See
468 # https://bugs.python.org/issue17950. We cache dynamically created types
468 # https://bugs.python.org/issue17950. We cache dynamically created types
469 # so they won't be leaked on every invocation of repo.filtered().
469 # so they won't be leaked on every invocation of repo.filtered().
470 _filteredrepotypes = weakref.WeakKeyDictionary()
470 _filteredrepotypes = weakref.WeakKeyDictionary()
471
471
472
472
473 def newtype(base):
473 def newtype(base):
474 """Create a new type with the repoview mixin and the given base class"""
474 """Create a new type with the repoview mixin and the given base class"""
475 cls = _filteredrepotypes.get(base)
475 ref = _filteredrepotypes.get(base)
476 if cls is not None:
476 if ref is not None:
477 return cls
477 cls = ref()
478 if cls is not None:
479 return cls
478
480
479 class filteredrepo(repoview, base):
481 class filteredrepo(repoview, base):
480 pass
482 pass
481
483
482 _filteredrepotypes[base] = filteredrepo
484 _filteredrepotypes[base] = weakref.ref(filteredrepo)
485 # do not reread from weakref to be 100% sure not to return None
483 return filteredrepo
486 return filteredrepo
General Comments 0
You need to be logged in to leave comments. Login now