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