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