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