##// END OF EJS Templates
repoview: add an explicit set of all filter that show the wc parents...
marmoute -
r44154:a1cd3617 default draft
parent child Browse files
Show More
@@ -1,468 +1,471 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 = 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 # set of filter level that will include the working copy parent no matter what.
181 filter_has_wc = {b'visible', b'visible-hidden'}
182
180 _basefiltername = list(filtertable)
183 _basefiltername = list(filtertable)
181
184
182
185
183 def extrafilter(ui):
186 def extrafilter(ui):
184 """initialize extra filter and return its id
187 """initialize extra filter and return its id
185
188
186 If extra filtering is configured, we make sure the associated filtered view
189 If extra filtering is configured, we make sure the associated filtered view
187 are declared and return the associated id.
190 are declared and return the associated id.
188 """
191 """
189 frevs = ui.config(b'experimental', b'extra-filter-revs')
192 frevs = ui.config(b'experimental', b'extra-filter-revs')
190 if frevs is None:
193 if frevs is None:
191 return None
194 return None
192
195
193 fid = pycompat.sysbytes(util.DIGESTS[b'sha1'](frevs).hexdigest())[:12]
196 fid = pycompat.sysbytes(util.DIGESTS[b'sha1'](frevs).hexdigest())[:12]
194
197
195 combine = lambda fname: fname + b'%' + fid
198 combine = lambda fname: fname + b'%' + fid
196
199
197 subsettable = repoviewutil.subsettable
200 subsettable = repoviewutil.subsettable
198
201
199 if combine(b'base') not in filtertable:
202 if combine(b'base') not in filtertable:
200 for name in _basefiltername:
203 for name in _basefiltername:
201
204
202 def extrafilteredrevs(repo, *args, **kwargs):
205 def extrafilteredrevs(repo, *args, **kwargs):
203 baserevs = filtertable[name](repo, *args, **kwargs)
206 baserevs = filtertable[name](repo, *args, **kwargs)
204 extrarevs = frozenset(repo.revs(frevs))
207 extrarevs = frozenset(repo.revs(frevs))
205 return baserevs | extrarevs
208 return baserevs | extrarevs
206
209
207 filtertable[combine(name)] = extrafilteredrevs
210 filtertable[combine(name)] = extrafilteredrevs
208 if name in subsettable:
211 if name in subsettable:
209 subsettable[combine(name)] = combine(subsettable[name])
212 subsettable[combine(name)] = combine(subsettable[name])
210 return fid
213 return fid
211
214
212
215
213 def filterrevs(repo, filtername, visibilityexceptions=None):
216 def filterrevs(repo, filtername, visibilityexceptions=None):
214 """returns set of filtered revision for this filter name
217 """returns set of filtered revision for this filter name
215
218
216 visibilityexceptions is a set of revs which must are exceptions for
219 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
220 hidden-state and must be visible. They are dynamic and hence we should not
218 cache it's result"""
221 cache it's result"""
219 if filtername not in repo.filteredrevcache:
222 if filtername not in repo.filteredrevcache:
220 if repo.ui.configbool(b'devel', b'debug.repo-filters'):
223 if repo.ui.configbool(b'devel', b'debug.repo-filters'):
221 msg = b'computing revision filter for "%s"'
224 msg = b'computing revision filter for "%s"'
222 msg %= filtername
225 msg %= filtername
223 if repo.ui.tracebackflag and repo.ui.debugflag:
226 if repo.ui.tracebackflag and repo.ui.debugflag:
224 # XXX use ui.write_err
227 # XXX use ui.write_err
225 util.debugstacktrace(
228 util.debugstacktrace(
226 msg,
229 msg,
227 f=repo.ui._fout,
230 f=repo.ui._fout,
228 otherf=repo.ui._ferr,
231 otherf=repo.ui._ferr,
229 prefix=b'debug.filters: ',
232 prefix=b'debug.filters: ',
230 )
233 )
231 else:
234 else:
232 repo.ui.debug(b'debug.filters: %s\n' % msg)
235 repo.ui.debug(b'debug.filters: %s\n' % msg)
233 func = filtertable[filtername]
236 func = filtertable[filtername]
234 if visibilityexceptions:
237 if visibilityexceptions:
235 return func(repo.unfiltered, visibilityexceptions)
238 return func(repo.unfiltered, visibilityexceptions)
236 repo.filteredrevcache[filtername] = func(repo.unfiltered())
239 repo.filteredrevcache[filtername] = func(repo.unfiltered())
237 return repo.filteredrevcache[filtername]
240 return repo.filteredrevcache[filtername]
238
241
239
242
240 def wrapchangelog(unfichangelog, filteredrevs):
243 def wrapchangelog(unfichangelog, filteredrevs):
241 cl = copy.copy(unfichangelog)
244 cl = copy.copy(unfichangelog)
242 cl.filteredrevs = filteredrevs
245 cl.filteredrevs = filteredrevs
243
246
244 class filteredchangelog(filteredchangelogmixin, cl.__class__):
247 class filteredchangelog(filteredchangelogmixin, cl.__class__):
245 pass
248 pass
246
249
247 cl.__class__ = filteredchangelog
250 cl.__class__ = filteredchangelog
248
251
249 return cl
252 return cl
250
253
251
254
252 class filteredchangelogmixin(object):
255 class filteredchangelogmixin(object):
253 def tiprev(self):
256 def tiprev(self):
254 """filtered version of revlog.tiprev"""
257 """filtered version of revlog.tiprev"""
255 for i in pycompat.xrange(len(self) - 1, -2, -1):
258 for i in pycompat.xrange(len(self) - 1, -2, -1):
256 if i not in self.filteredrevs:
259 if i not in self.filteredrevs:
257 return i
260 return i
258
261
259 def __contains__(self, rev):
262 def __contains__(self, rev):
260 """filtered version of revlog.__contains__"""
263 """filtered version of revlog.__contains__"""
261 return 0 <= rev < len(self) and rev not in self.filteredrevs
264 return 0 <= rev < len(self) and rev not in self.filteredrevs
262
265
263 def __iter__(self):
266 def __iter__(self):
264 """filtered version of revlog.__iter__"""
267 """filtered version of revlog.__iter__"""
265
268
266 def filterediter():
269 def filterediter():
267 for i in pycompat.xrange(len(self)):
270 for i in pycompat.xrange(len(self)):
268 if i not in self.filteredrevs:
271 if i not in self.filteredrevs:
269 yield i
272 yield i
270
273
271 return filterediter()
274 return filterediter()
272
275
273 def revs(self, start=0, stop=None):
276 def revs(self, start=0, stop=None):
274 """filtered version of revlog.revs"""
277 """filtered version of revlog.revs"""
275 for i in super(filteredchangelogmixin, self).revs(start, stop):
278 for i in super(filteredchangelogmixin, self).revs(start, stop):
276 if i not in self.filteredrevs:
279 if i not in self.filteredrevs:
277 yield i
280 yield i
278
281
279 def _checknofilteredinrevs(self, revs):
282 def _checknofilteredinrevs(self, revs):
280 """raise the appropriate error if 'revs' contains a filtered revision
283 """raise the appropriate error if 'revs' contains a filtered revision
281
284
282 This returns a version of 'revs' to be used thereafter by the caller.
285 This returns a version of 'revs' to be used thereafter by the caller.
283 In particular, if revs is an iterator, it is converted into a set.
286 In particular, if revs is an iterator, it is converted into a set.
284 """
287 """
285 safehasattr = util.safehasattr
288 safehasattr = util.safehasattr
286 if safehasattr(revs, '__next__'):
289 if safehasattr(revs, '__next__'):
287 # Note that inspect.isgenerator() is not true for iterators,
290 # Note that inspect.isgenerator() is not true for iterators,
288 revs = set(revs)
291 revs = set(revs)
289
292
290 filteredrevs = self.filteredrevs
293 filteredrevs = self.filteredrevs
291 if safehasattr(revs, 'first'): # smartset
294 if safehasattr(revs, 'first'): # smartset
292 offenders = revs & filteredrevs
295 offenders = revs & filteredrevs
293 else:
296 else:
294 offenders = filteredrevs.intersection(revs)
297 offenders = filteredrevs.intersection(revs)
295
298
296 for rev in offenders:
299 for rev in offenders:
297 raise error.FilteredIndexError(rev)
300 raise error.FilteredIndexError(rev)
298 return revs
301 return revs
299
302
300 def headrevs(self, revs=None):
303 def headrevs(self, revs=None):
301 if revs is None:
304 if revs is None:
302 try:
305 try:
303 return self.index.headrevsfiltered(self.filteredrevs)
306 return self.index.headrevsfiltered(self.filteredrevs)
304 # AttributeError covers non-c-extension environments and
307 # AttributeError covers non-c-extension environments and
305 # old c extensions without filter handling.
308 # old c extensions without filter handling.
306 except AttributeError:
309 except AttributeError:
307 return self._headrevs()
310 return self._headrevs()
308
311
309 revs = self._checknofilteredinrevs(revs)
312 revs = self._checknofilteredinrevs(revs)
310 return super(filteredchangelogmixin, self).headrevs(revs)
313 return super(filteredchangelogmixin, self).headrevs(revs)
311
314
312 def strip(self, *args, **kwargs):
315 def strip(self, *args, **kwargs):
313 # XXX make something better than assert
316 # XXX make something better than assert
314 # We can't expect proper strip behavior if we are filtered.
317 # We can't expect proper strip behavior if we are filtered.
315 assert not self.filteredrevs
318 assert not self.filteredrevs
316 super(filteredchangelogmixin, self).strip(*args, **kwargs)
319 super(filteredchangelogmixin, self).strip(*args, **kwargs)
317
320
318 def rev(self, node):
321 def rev(self, node):
319 """filtered version of revlog.rev"""
322 """filtered version of revlog.rev"""
320 r = super(filteredchangelogmixin, self).rev(node)
323 r = super(filteredchangelogmixin, self).rev(node)
321 if r in self.filteredrevs:
324 if r in self.filteredrevs:
322 raise error.FilteredLookupError(
325 raise error.FilteredLookupError(
323 hex(node), self.indexfile, _(b'filtered node')
326 hex(node), self.indexfile, _(b'filtered node')
324 )
327 )
325 return r
328 return r
326
329
327 def node(self, rev):
330 def node(self, rev):
328 """filtered version of revlog.node"""
331 """filtered version of revlog.node"""
329 if rev in self.filteredrevs:
332 if rev in self.filteredrevs:
330 raise error.FilteredIndexError(rev)
333 raise error.FilteredIndexError(rev)
331 return super(filteredchangelogmixin, self).node(rev)
334 return super(filteredchangelogmixin, self).node(rev)
332
335
333 def linkrev(self, rev):
336 def linkrev(self, rev):
334 """filtered version of revlog.linkrev"""
337 """filtered version of revlog.linkrev"""
335 if rev in self.filteredrevs:
338 if rev in self.filteredrevs:
336 raise error.FilteredIndexError(rev)
339 raise error.FilteredIndexError(rev)
337 return super(filteredchangelogmixin, self).linkrev(rev)
340 return super(filteredchangelogmixin, self).linkrev(rev)
338
341
339 def parentrevs(self, rev):
342 def parentrevs(self, rev):
340 """filtered version of revlog.parentrevs"""
343 """filtered version of revlog.parentrevs"""
341 if rev in self.filteredrevs:
344 if rev in self.filteredrevs:
342 raise error.FilteredIndexError(rev)
345 raise error.FilteredIndexError(rev)
343 return super(filteredchangelogmixin, self).parentrevs(rev)
346 return super(filteredchangelogmixin, self).parentrevs(rev)
344
347
345 def flags(self, rev):
348 def flags(self, rev):
346 """filtered version of revlog.flags"""
349 """filtered version of revlog.flags"""
347 if rev in self.filteredrevs:
350 if rev in self.filteredrevs:
348 raise error.FilteredIndexError(rev)
351 raise error.FilteredIndexError(rev)
349 return super(filteredchangelogmixin, self).flags(rev)
352 return super(filteredchangelogmixin, self).flags(rev)
350
353
351
354
352 class repoview(object):
355 class repoview(object):
353 """Provide a read/write view of a repo through a filtered changelog
356 """Provide a read/write view of a repo through a filtered changelog
354
357
355 This object is used to access a filtered version of a repository without
358 This object is used to access a filtered version of a repository without
356 altering the original repository object itself. We can not alter the
359 altering the original repository object itself. We can not alter the
357 original object for two main reasons:
360 original object for two main reasons:
358 - It prevents the use of a repo with multiple filters at the same time. In
361 - It prevents the use of a repo with multiple filters at the same time. In
359 particular when multiple threads are involved.
362 particular when multiple threads are involved.
360 - It makes scope of the filtering harder to control.
363 - It makes scope of the filtering harder to control.
361
364
362 This object behaves very closely to the original repository. All attribute
365 This object behaves very closely to the original repository. All attribute
363 operations are done on the original repository:
366 operations are done on the original repository:
364 - An access to `repoview.someattr` actually returns `repo.someattr`,
367 - An access to `repoview.someattr` actually returns `repo.someattr`,
365 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
368 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
366 - A deletion of `repoview.someattr` actually drops `someattr`
369 - A deletion of `repoview.someattr` actually drops `someattr`
367 from `repo.__dict__`.
370 from `repo.__dict__`.
368
371
369 The only exception is the `changelog` property. It is overridden to return
372 The only exception is the `changelog` property. It is overridden to return
370 a (surface) copy of `repo.changelog` with some revisions filtered. The
373 a (surface) copy of `repo.changelog` with some revisions filtered. The
371 `filtername` attribute of the view control the revisions that need to be
374 `filtername` attribute of the view control the revisions that need to be
372 filtered. (the fact the changelog is copied is an implementation detail).
375 filtered. (the fact the changelog is copied is an implementation detail).
373
376
374 Unlike attributes, this object intercepts all method calls. This means that
377 Unlike attributes, this object intercepts all method calls. This means that
375 all methods are run on the `repoview` object with the filtered `changelog`
378 all methods are run on the `repoview` object with the filtered `changelog`
376 property. For this purpose the simple `repoview` class must be mixed with
379 property. For this purpose the simple `repoview` class must be mixed with
377 the actual class of the repository. This ensures that the resulting
380 the actual class of the repository. This ensures that the resulting
378 `repoview` object have the very same methods than the repo object. This
381 `repoview` object have the very same methods than the repo object. This
379 leads to the property below.
382 leads to the property below.
380
383
381 repoview.method() --> repo.__class__.method(repoview)
384 repoview.method() --> repo.__class__.method(repoview)
382
385
383 The inheritance has to be done dynamically because `repo` can be of any
386 The inheritance has to be done dynamically because `repo` can be of any
384 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
387 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
385 """
388 """
386
389
387 def __init__(self, repo, filtername, visibilityexceptions=None):
390 def __init__(self, repo, filtername, visibilityexceptions=None):
388 object.__setattr__(self, '_unfilteredrepo', repo)
391 object.__setattr__(self, '_unfilteredrepo', repo)
389 object.__setattr__(self, 'filtername', filtername)
392 object.__setattr__(self, 'filtername', filtername)
390 object.__setattr__(self, '_clcachekey', None)
393 object.__setattr__(self, '_clcachekey', None)
391 object.__setattr__(self, '_clcache', None)
394 object.__setattr__(self, '_clcache', None)
392 # revs which are exceptions and must not be hidden
395 # revs which are exceptions and must not be hidden
393 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
396 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
394
397
395 # not a propertycache on purpose we shall implement a proper cache later
398 # not a propertycache on purpose we shall implement a proper cache later
396 @property
399 @property
397 def changelog(self):
400 def changelog(self):
398 """return a filtered version of the changeset
401 """return a filtered version of the changeset
399
402
400 this changelog must not be used for writing"""
403 this changelog must not be used for writing"""
401 # some cache may be implemented later
404 # some cache may be implemented later
402 unfi = self._unfilteredrepo
405 unfi = self._unfilteredrepo
403 unfichangelog = unfi.changelog
406 unfichangelog = unfi.changelog
404 # bypass call to changelog.method
407 # bypass call to changelog.method
405 unfiindex = unfichangelog.index
408 unfiindex = unfichangelog.index
406 unfilen = len(unfiindex)
409 unfilen = len(unfiindex)
407 unfinode = unfiindex[unfilen - 1][7]
410 unfinode = unfiindex[unfilen - 1][7]
408 with util.timedcm('repo filter for %s', self.filtername):
411 with util.timedcm('repo filter for %s', self.filtername):
409 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
412 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
410 cl = self._clcache
413 cl = self._clcache
411 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
414 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
412 # if cl.index is not unfiindex, unfi.changelog would be
415 # if cl.index is not unfiindex, unfi.changelog would be
413 # recreated, and our clcache refers to garbage object
416 # recreated, and our clcache refers to garbage object
414 if cl is not None and (
417 if cl is not None and (
415 cl.index is not unfiindex or newkey != self._clcachekey
418 cl.index is not unfiindex or newkey != self._clcachekey
416 ):
419 ):
417 cl = None
420 cl = None
418 # could have been made None by the previous if
421 # could have been made None by the previous if
419 if cl is None:
422 if cl is None:
420 # Only filter if there's something to filter
423 # Only filter if there's something to filter
421 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
424 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
422 object.__setattr__(self, '_clcache', cl)
425 object.__setattr__(self, '_clcache', cl)
423 object.__setattr__(self, '_clcachekey', newkey)
426 object.__setattr__(self, '_clcachekey', newkey)
424 return cl
427 return cl
425
428
426 def unfiltered(self):
429 def unfiltered(self):
427 """Return an unfiltered version of a repo"""
430 """Return an unfiltered version of a repo"""
428 return self._unfilteredrepo
431 return self._unfilteredrepo
429
432
430 def filtered(self, name, visibilityexceptions=None):
433 def filtered(self, name, visibilityexceptions=None):
431 """Return a filtered version of a repository"""
434 """Return a filtered version of a repository"""
432 if name == self.filtername and not visibilityexceptions:
435 if name == self.filtername and not visibilityexceptions:
433 return self
436 return self
434 return self.unfiltered().filtered(name, visibilityexceptions)
437 return self.unfiltered().filtered(name, visibilityexceptions)
435
438
436 def __repr__(self):
439 def __repr__(self):
437 return '<%s:%s %r>' % (
440 return '<%s:%s %r>' % (
438 self.__class__.__name__,
441 self.__class__.__name__,
439 pycompat.sysstr(self.filtername),
442 pycompat.sysstr(self.filtername),
440 self.unfiltered(),
443 self.unfiltered(),
441 )
444 )
442
445
443 # everything access are forwarded to the proxied repo
446 # everything access are forwarded to the proxied repo
444 def __getattr__(self, attr):
447 def __getattr__(self, attr):
445 return getattr(self._unfilteredrepo, attr)
448 return getattr(self._unfilteredrepo, attr)
446
449
447 def __setattr__(self, attr, value):
450 def __setattr__(self, attr, value):
448 return setattr(self._unfilteredrepo, attr, value)
451 return setattr(self._unfilteredrepo, attr, value)
449
452
450 def __delattr__(self, attr):
453 def __delattr__(self, attr):
451 return delattr(self._unfilteredrepo, attr)
454 return delattr(self._unfilteredrepo, attr)
452
455
453
456
454 # Python <3.4 easily leaks types via __mro__. See
457 # Python <3.4 easily leaks types via __mro__. See
455 # https://bugs.python.org/issue17950. We cache dynamically created types
458 # https://bugs.python.org/issue17950. We cache dynamically created types
456 # so they won't be leaked on every invocation of repo.filtered().
459 # so they won't be leaked on every invocation of repo.filtered().
457 _filteredrepotypes = weakref.WeakKeyDictionary()
460 _filteredrepotypes = weakref.WeakKeyDictionary()
458
461
459
462
460 def newtype(base):
463 def newtype(base):
461 """Create a new type with the repoview mixin and the given base class"""
464 """Create a new type with the repoview mixin and the given base class"""
462 if base not in _filteredrepotypes:
465 if base not in _filteredrepotypes:
463
466
464 class filteredrepo(repoview, base):
467 class filteredrepo(repoview, base):
465 pass
468 pass
466
469
467 _filteredrepotypes[base] = filteredrepo
470 _filteredrepotypes[base] = filteredrepo
468 return _filteredrepotypes[base]
471 return _filteredrepotypes[base]
General Comments 0
You need to be logged in to leave comments. Login now