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