##// END OF EJS Templates
repoview: use class literal for creating filteredchangelog...
Martin von Zweigbergk -
r43910:85628a59 default
parent child Browse files
Show More
@@ -1,453 +1,454 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 cl.__class__ = type(
230 class filteredchangelog(filteredchangelogmixin, cl.__class__):
231 'filteredchangelog', (filteredchangelogmixin, cl.__class__), {}
231 pass
232 )
232
233 cl.__class__ = filteredchangelog
233
234
234 return cl
235 return cl
235
236
236
237
237 class filteredchangelogmixin(object):
238 class filteredchangelogmixin(object):
238 def tiprev(self):
239 def tiprev(self):
239 """filtered version of revlog.tiprev"""
240 """filtered version of revlog.tiprev"""
240 for i in pycompat.xrange(len(self) - 1, -2, -1):
241 for i in pycompat.xrange(len(self) - 1, -2, -1):
241 if i not in self.filteredrevs:
242 if i not in self.filteredrevs:
242 return i
243 return i
243
244
244 def __contains__(self, rev):
245 def __contains__(self, rev):
245 """filtered version of revlog.__contains__"""
246 """filtered version of revlog.__contains__"""
246 return 0 <= rev < len(self) and rev not in self.filteredrevs
247 return 0 <= rev < len(self) and rev not in self.filteredrevs
247
248
248 def __iter__(self):
249 def __iter__(self):
249 """filtered version of revlog.__iter__"""
250 """filtered version of revlog.__iter__"""
250
251
251 def filterediter():
252 def filterediter():
252 for i in pycompat.xrange(len(self)):
253 for i in pycompat.xrange(len(self)):
253 if i not in self.filteredrevs:
254 if i not in self.filteredrevs:
254 yield i
255 yield i
255
256
256 return filterediter()
257 return filterediter()
257
258
258 def revs(self, start=0, stop=None):
259 def revs(self, start=0, stop=None):
259 """filtered version of revlog.revs"""
260 """filtered version of revlog.revs"""
260 for i in super(filteredchangelogmixin, self).revs(start, stop):
261 for i in super(filteredchangelogmixin, self).revs(start, stop):
261 if i not in self.filteredrevs:
262 if i not in self.filteredrevs:
262 yield i
263 yield i
263
264
264 def _checknofilteredinrevs(self, revs):
265 def _checknofilteredinrevs(self, revs):
265 """raise the appropriate error if 'revs' contains a filtered revision
266 """raise the appropriate error if 'revs' contains a filtered revision
266
267
267 This returns a version of 'revs' to be used thereafter by the caller.
268 This returns a version of 'revs' to be used thereafter by the caller.
268 In particular, if revs is an iterator, it is converted into a set.
269 In particular, if revs is an iterator, it is converted into a set.
269 """
270 """
270 safehasattr = util.safehasattr
271 safehasattr = util.safehasattr
271 if safehasattr(revs, '__next__'):
272 if safehasattr(revs, '__next__'):
272 # Note that inspect.isgenerator() is not true for iterators,
273 # Note that inspect.isgenerator() is not true for iterators,
273 revs = set(revs)
274 revs = set(revs)
274
275
275 filteredrevs = self.filteredrevs
276 filteredrevs = self.filteredrevs
276 if safehasattr(revs, 'first'): # smartset
277 if safehasattr(revs, 'first'): # smartset
277 offenders = revs & filteredrevs
278 offenders = revs & filteredrevs
278 else:
279 else:
279 offenders = filteredrevs.intersection(revs)
280 offenders = filteredrevs.intersection(revs)
280
281
281 for rev in offenders:
282 for rev in offenders:
282 raise error.FilteredIndexError(rev)
283 raise error.FilteredIndexError(rev)
283 return revs
284 return revs
284
285
285 def headrevs(self, revs=None):
286 def headrevs(self, revs=None):
286 if revs is None:
287 if revs is None:
287 try:
288 try:
288 return self.index.headrevsfiltered(self.filteredrevs)
289 return self.index.headrevsfiltered(self.filteredrevs)
289 # AttributeError covers non-c-extension environments and
290 # AttributeError covers non-c-extension environments and
290 # old c extensions without filter handling.
291 # old c extensions without filter handling.
291 except AttributeError:
292 except AttributeError:
292 return self._headrevs()
293 return self._headrevs()
293
294
294 revs = self._checknofilteredinrevs(revs)
295 revs = self._checknofilteredinrevs(revs)
295 return super(filteredchangelogmixin, self).headrevs(revs)
296 return super(filteredchangelogmixin, self).headrevs(revs)
296
297
297 def strip(self, *args, **kwargs):
298 def strip(self, *args, **kwargs):
298 # XXX make something better than assert
299 # XXX make something better than assert
299 # We can't expect proper strip behavior if we are filtered.
300 # We can't expect proper strip behavior if we are filtered.
300 assert not self.filteredrevs
301 assert not self.filteredrevs
301 super(filteredchangelogmixin, self).strip(*args, **kwargs)
302 super(filteredchangelogmixin, self).strip(*args, **kwargs)
302
303
303 def rev(self, node):
304 def rev(self, node):
304 """filtered version of revlog.rev"""
305 """filtered version of revlog.rev"""
305 r = super(filteredchangelogmixin, self).rev(node)
306 r = super(filteredchangelogmixin, self).rev(node)
306 if r in self.filteredrevs:
307 if r in self.filteredrevs:
307 raise error.FilteredLookupError(
308 raise error.FilteredLookupError(
308 hex(node), self.indexfile, _(b'filtered node')
309 hex(node), self.indexfile, _(b'filtered node')
309 )
310 )
310 return r
311 return r
311
312
312 def node(self, rev):
313 def node(self, rev):
313 """filtered version of revlog.node"""
314 """filtered version of revlog.node"""
314 if rev in self.filteredrevs:
315 if rev in self.filteredrevs:
315 raise error.FilteredIndexError(rev)
316 raise error.FilteredIndexError(rev)
316 return super(filteredchangelogmixin, self).node(rev)
317 return super(filteredchangelogmixin, self).node(rev)
317
318
318 def linkrev(self, rev):
319 def linkrev(self, rev):
319 """filtered version of revlog.linkrev"""
320 """filtered version of revlog.linkrev"""
320 if rev in self.filteredrevs:
321 if rev in self.filteredrevs:
321 raise error.FilteredIndexError(rev)
322 raise error.FilteredIndexError(rev)
322 return super(filteredchangelogmixin, self).linkrev(rev)
323 return super(filteredchangelogmixin, self).linkrev(rev)
323
324
324 def parentrevs(self, rev):
325 def parentrevs(self, rev):
325 """filtered version of revlog.parentrevs"""
326 """filtered version of revlog.parentrevs"""
326 if rev in self.filteredrevs:
327 if rev in self.filteredrevs:
327 raise error.FilteredIndexError(rev)
328 raise error.FilteredIndexError(rev)
328 return super(filteredchangelogmixin, self).parentrevs(rev)
329 return super(filteredchangelogmixin, self).parentrevs(rev)
329
330
330 def flags(self, rev):
331 def flags(self, rev):
331 """filtered version of revlog.flags"""
332 """filtered version of revlog.flags"""
332 if rev in self.filteredrevs:
333 if rev in self.filteredrevs:
333 raise error.FilteredIndexError(rev)
334 raise error.FilteredIndexError(rev)
334 return super(filteredchangelogmixin, self).flags(rev)
335 return super(filteredchangelogmixin, self).flags(rev)
335
336
336
337
337 class repoview(object):
338 class repoview(object):
338 """Provide a read/write view of a repo through a filtered changelog
339 """Provide a read/write view of a repo through a filtered changelog
339
340
340 This object is used to access a filtered version of a repository without
341 This object is used to access a filtered version of a repository without
341 altering the original repository object itself. We can not alter the
342 altering the original repository object itself. We can not alter the
342 original object for two main reasons:
343 original object for two main reasons:
343 - It prevents the use of a repo with multiple filters at the same time. In
344 - It prevents the use of a repo with multiple filters at the same time. In
344 particular when multiple threads are involved.
345 particular when multiple threads are involved.
345 - It makes scope of the filtering harder to control.
346 - It makes scope of the filtering harder to control.
346
347
347 This object behaves very closely to the original repository. All attribute
348 This object behaves very closely to the original repository. All attribute
348 operations are done on the original repository:
349 operations are done on the original repository:
349 - An access to `repoview.someattr` actually returns `repo.someattr`,
350 - An access to `repoview.someattr` actually returns `repo.someattr`,
350 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
351 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
351 - A deletion of `repoview.someattr` actually drops `someattr`
352 - A deletion of `repoview.someattr` actually drops `someattr`
352 from `repo.__dict__`.
353 from `repo.__dict__`.
353
354
354 The only exception is the `changelog` property. It is overridden to return
355 The only exception is the `changelog` property. It is overridden to return
355 a (surface) copy of `repo.changelog` with some revisions filtered. The
356 a (surface) copy of `repo.changelog` with some revisions filtered. The
356 `filtername` attribute of the view control the revisions that need to be
357 `filtername` attribute of the view control the revisions that need to be
357 filtered. (the fact the changelog is copied is an implementation detail).
358 filtered. (the fact the changelog is copied is an implementation detail).
358
359
359 Unlike attributes, this object intercepts all method calls. This means that
360 Unlike attributes, this object intercepts all method calls. This means that
360 all methods are run on the `repoview` object with the filtered `changelog`
361 all methods are run on the `repoview` object with the filtered `changelog`
361 property. For this purpose the simple `repoview` class must be mixed with
362 property. For this purpose the simple `repoview` class must be mixed with
362 the actual class of the repository. This ensures that the resulting
363 the actual class of the repository. This ensures that the resulting
363 `repoview` object have the very same methods than the repo object. This
364 `repoview` object have the very same methods than the repo object. This
364 leads to the property below.
365 leads to the property below.
365
366
366 repoview.method() --> repo.__class__.method(repoview)
367 repoview.method() --> repo.__class__.method(repoview)
367
368
368 The inheritance has to be done dynamically because `repo` can be of any
369 The inheritance has to be done dynamically because `repo` can be of any
369 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
370 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
370 """
371 """
371
372
372 def __init__(self, repo, filtername, visibilityexceptions=None):
373 def __init__(self, repo, filtername, visibilityexceptions=None):
373 object.__setattr__(self, '_unfilteredrepo', repo)
374 object.__setattr__(self, '_unfilteredrepo', repo)
374 object.__setattr__(self, 'filtername', filtername)
375 object.__setattr__(self, 'filtername', filtername)
375 object.__setattr__(self, '_clcachekey', None)
376 object.__setattr__(self, '_clcachekey', None)
376 object.__setattr__(self, '_clcache', None)
377 object.__setattr__(self, '_clcache', None)
377 # revs which are exceptions and must not be hidden
378 # revs which are exceptions and must not be hidden
378 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
379 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
379
380
380 # not a propertycache on purpose we shall implement a proper cache later
381 # not a propertycache on purpose we shall implement a proper cache later
381 @property
382 @property
382 def changelog(self):
383 def changelog(self):
383 """return a filtered version of the changeset
384 """return a filtered version of the changeset
384
385
385 this changelog must not be used for writing"""
386 this changelog must not be used for writing"""
386 # some cache may be implemented later
387 # some cache may be implemented later
387 unfi = self._unfilteredrepo
388 unfi = self._unfilteredrepo
388 unfichangelog = unfi.changelog
389 unfichangelog = unfi.changelog
389 # bypass call to changelog.method
390 # bypass call to changelog.method
390 unfiindex = unfichangelog.index
391 unfiindex = unfichangelog.index
391 unfilen = len(unfiindex)
392 unfilen = len(unfiindex)
392 unfinode = unfiindex[unfilen - 1][7]
393 unfinode = unfiindex[unfilen - 1][7]
393 with util.timedcm('repo filter for %s', self.filtername):
394 with util.timedcm('repo filter for %s', self.filtername):
394 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
395 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
395 cl = self._clcache
396 cl = self._clcache
396 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
397 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
397 # if cl.index is not unfiindex, unfi.changelog would be
398 # if cl.index is not unfiindex, unfi.changelog would be
398 # recreated, and our clcache refers to garbage object
399 # recreated, and our clcache refers to garbage object
399 if cl is not None and (
400 if cl is not None and (
400 cl.index is not unfiindex or newkey != self._clcachekey
401 cl.index is not unfiindex or newkey != self._clcachekey
401 ):
402 ):
402 cl = None
403 cl = None
403 # could have been made None by the previous if
404 # could have been made None by the previous if
404 if cl is None:
405 if cl is None:
405 # Only filter if there's something to filter
406 # Only filter if there's something to filter
406 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
407 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
407 object.__setattr__(self, '_clcache', cl)
408 object.__setattr__(self, '_clcache', cl)
408 object.__setattr__(self, '_clcachekey', newkey)
409 object.__setattr__(self, '_clcachekey', newkey)
409 return cl
410 return cl
410
411
411 def unfiltered(self):
412 def unfiltered(self):
412 """Return an unfiltered version of a repo"""
413 """Return an unfiltered version of a repo"""
413 return self._unfilteredrepo
414 return self._unfilteredrepo
414
415
415 def filtered(self, name, visibilityexceptions=None):
416 def filtered(self, name, visibilityexceptions=None):
416 """Return a filtered version of a repository"""
417 """Return a filtered version of a repository"""
417 if name == self.filtername and not visibilityexceptions:
418 if name == self.filtername and not visibilityexceptions:
418 return self
419 return self
419 return self.unfiltered().filtered(name, visibilityexceptions)
420 return self.unfiltered().filtered(name, visibilityexceptions)
420
421
421 def __repr__(self):
422 def __repr__(self):
422 return '<%s:%s %r>' % (
423 return '<%s:%s %r>' % (
423 self.__class__.__name__,
424 self.__class__.__name__,
424 pycompat.sysstr(self.filtername),
425 pycompat.sysstr(self.filtername),
425 self.unfiltered(),
426 self.unfiltered(),
426 )
427 )
427
428
428 # everything access are forwarded to the proxied repo
429 # everything access are forwarded to the proxied repo
429 def __getattr__(self, attr):
430 def __getattr__(self, attr):
430 return getattr(self._unfilteredrepo, attr)
431 return getattr(self._unfilteredrepo, attr)
431
432
432 def __setattr__(self, attr, value):
433 def __setattr__(self, attr, value):
433 return setattr(self._unfilteredrepo, attr, value)
434 return setattr(self._unfilteredrepo, attr, value)
434
435
435 def __delattr__(self, attr):
436 def __delattr__(self, attr):
436 return delattr(self._unfilteredrepo, attr)
437 return delattr(self._unfilteredrepo, attr)
437
438
438
439
439 # Python <3.4 easily leaks types via __mro__. See
440 # Python <3.4 easily leaks types via __mro__. See
440 # https://bugs.python.org/issue17950. We cache dynamically created types
441 # https://bugs.python.org/issue17950. We cache dynamically created types
441 # so they won't be leaked on every invocation of repo.filtered().
442 # so they won't be leaked on every invocation of repo.filtered().
442 _filteredrepotypes = weakref.WeakKeyDictionary()
443 _filteredrepotypes = weakref.WeakKeyDictionary()
443
444
444
445
445 def newtype(base):
446 def newtype(base):
446 """Create a new type with the repoview mixin and the given base class"""
447 """Create a new type with the repoview mixin and the given base class"""
447 if base not in _filteredrepotypes:
448 if base not in _filteredrepotypes:
448
449
449 class filteredrepo(repoview, base):
450 class filteredrepo(repoview, base):
450 pass
451 pass
451
452
452 _filteredrepotypes[base] = filteredrepo
453 _filteredrepotypes[base] = filteredrepo
453 return _filteredrepotypes[base]
454 return _filteredrepotypes[base]
General Comments 0
You need to be logged in to leave comments. Login now