##// END OF EJS Templates
repoview: fix the filter created by `extrafilter`...
marmoute -
r51665:1680ef94 stable
parent child Browse files
Show More
@@ -1,486 +1,488 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
9
10 import copy
10 import copy
11 import weakref
11 import weakref
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 hex,
15 hex,
16 nullrev,
16 nullrev,
17 )
17 )
18 from .pycompat import (
18 from .pycompat import (
19 delattr,
19 delattr,
20 getattr,
20 getattr,
21 setattr,
21 setattr,
22 )
22 )
23 from . import (
23 from . import (
24 error,
24 error,
25 obsolete,
25 obsolete,
26 phases,
26 phases,
27 pycompat,
27 pycompat,
28 tags as tagsmod,
28 tags as tagsmod,
29 util,
29 util,
30 )
30 )
31 from .utils import repoviewutil
31 from .utils import repoviewutil
32
32
33
33
34 def hideablerevs(repo):
34 def hideablerevs(repo):
35 """Revision candidates to be hidden
35 """Revision candidates to be hidden
36
36
37 This is a standalone function to allow extensions to wrap it.
37 This is a standalone function to allow extensions to wrap it.
38
38
39 Because we use the set of immutable changesets as a fallback subset in
39 Because we use the set of immutable changesets as a fallback subset in
40 branchmap (see mercurial.utils.repoviewutils.subsettable), you cannot set
40 branchmap (see mercurial.utils.repoviewutils.subsettable), you cannot set
41 "public" changesets as "hideable". Doing so would break multiple code
41 "public" changesets as "hideable". Doing so would break multiple code
42 assertions and lead to crashes."""
42 assertions and lead to crashes."""
43 obsoletes = obsolete.getrevs(repo, b'obsolete')
43 obsoletes = obsolete.getrevs(repo, b'obsolete')
44 internals = repo._phasecache.getrevset(repo, phases.localhiddenphases)
44 internals = repo._phasecache.getrevset(repo, phases.localhiddenphases)
45 internals = frozenset(internals)
45 internals = frozenset(internals)
46 return obsoletes | internals
46 return obsoletes | internals
47
47
48
48
49 def pinnedrevs(repo):
49 def pinnedrevs(repo):
50 """revisions blocking hidden changesets from being filtered"""
50 """revisions blocking hidden changesets from being filtered"""
51
51
52 cl = repo.changelog
52 cl = repo.changelog
53 pinned = set()
53 pinned = set()
54 pinned.update([par.rev() for par in repo[None].parents()])
54 pinned.update([par.rev() for par in repo[None].parents()])
55 pinned.update([cl.rev(bm) for bm in repo._bookmarks.values()])
55 pinned.update([cl.rev(bm) for bm in repo._bookmarks.values()])
56
56
57 tags = {}
57 tags = {}
58 tagsmod.readlocaltags(repo.ui, repo, tags, {})
58 tagsmod.readlocaltags(repo.ui, repo, tags, {})
59 if tags:
59 if tags:
60 rev = cl.index.get_rev
60 rev = cl.index.get_rev
61 pinned.update(rev(t[0]) for t in tags.values())
61 pinned.update(rev(t[0]) for t in tags.values())
62 pinned.discard(None)
62 pinned.discard(None)
63
63
64 # Avoid cycle: mercurial.filemerge -> mercurial.templater ->
64 # Avoid cycle: mercurial.filemerge -> mercurial.templater ->
65 # mercurial.templatefuncs -> mercurial.revset -> mercurial.repoview ->
65 # mercurial.templatefuncs -> mercurial.revset -> mercurial.repoview ->
66 # mercurial.mergestate -> mercurial.filemerge
66 # mercurial.mergestate -> mercurial.filemerge
67 from . import mergestate
67 from . import mergestate
68
68
69 ms = mergestate.mergestate.read(repo)
69 ms = mergestate.mergestate.read(repo)
70 if ms.active() and ms.unresolvedcount():
70 if ms.active() and ms.unresolvedcount():
71 for node in (ms.local, ms.other):
71 for node in (ms.local, ms.other):
72 rev = cl.index.get_rev(node)
72 rev = cl.index.get_rev(node)
73 if rev is not None:
73 if rev is not None:
74 pinned.add(rev)
74 pinned.add(rev)
75
75
76 return pinned
76 return pinned
77
77
78
78
79 def _revealancestors(pfunc, hidden, revs):
79 def _revealancestors(pfunc, hidden, revs):
80 """reveals contiguous chains of hidden ancestors of 'revs' by removing them
80 """reveals contiguous chains of hidden ancestors of 'revs' by removing them
81 from 'hidden'
81 from 'hidden'
82
82
83 - pfunc(r): a funtion returning parent of 'r',
83 - pfunc(r): a funtion returning parent of 'r',
84 - hidden: the (preliminary) hidden revisions, to be updated
84 - hidden: the (preliminary) hidden revisions, to be updated
85 - revs: iterable of revnum,
85 - revs: iterable of revnum,
86
86
87 (Ancestors are revealed exclusively, i.e. the elements in 'revs' are
87 (Ancestors are revealed exclusively, i.e. the elements in 'revs' are
88 *not* revealed)
88 *not* revealed)
89 """
89 """
90 stack = list(revs)
90 stack = list(revs)
91 while stack:
91 while stack:
92 for p in pfunc(stack.pop()):
92 for p in pfunc(stack.pop()):
93 if p != nullrev and p in hidden:
93 if p != nullrev and p in hidden:
94 hidden.remove(p)
94 hidden.remove(p)
95 stack.append(p)
95 stack.append(p)
96
96
97
97
98 def computehidden(repo, visibilityexceptions=None):
98 def computehidden(repo, visibilityexceptions=None):
99 """compute the set of hidden revision to filter
99 """compute the set of hidden revision to filter
100
100
101 During most operation hidden should be filtered."""
101 During most operation hidden should be filtered."""
102 assert not repo.changelog.filteredrevs
102 assert not repo.changelog.filteredrevs
103
103
104 hidden = hideablerevs(repo)
104 hidden = hideablerevs(repo)
105 if hidden:
105 if hidden:
106 hidden = set(hidden - pinnedrevs(repo))
106 hidden = set(hidden - pinnedrevs(repo))
107 if visibilityexceptions:
107 if visibilityexceptions:
108 hidden -= visibilityexceptions
108 hidden -= visibilityexceptions
109 pfunc = repo.changelog.parentrevs
109 pfunc = repo.changelog.parentrevs
110 mutable = repo._phasecache.getrevset(repo, phases.mutablephases)
110 mutable = repo._phasecache.getrevset(repo, phases.mutablephases)
111
111
112 visible = mutable - hidden
112 visible = mutable - hidden
113 _revealancestors(pfunc, hidden, visible)
113 _revealancestors(pfunc, hidden, visible)
114 return frozenset(hidden)
114 return frozenset(hidden)
115
115
116
116
117 def computesecret(repo, visibilityexceptions=None):
117 def computesecret(repo, visibilityexceptions=None):
118 """compute the set of revision that can never be exposed through hgweb
118 """compute the set of revision that can never be exposed through hgweb
119
119
120 Changeset in the secret phase (or above) should stay unaccessible."""
120 Changeset in the secret phase (or above) should stay unaccessible."""
121 assert not repo.changelog.filteredrevs
121 assert not repo.changelog.filteredrevs
122 secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases)
122 secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases)
123 return frozenset(secrets)
123 return frozenset(secrets)
124
124
125
125
126 def computeunserved(repo, visibilityexceptions=None):
126 def computeunserved(repo, visibilityexceptions=None):
127 """compute the set of revision that should be filtered when used a server
127 """compute the set of revision that should be filtered when used a server
128
128
129 Secret and hidden changeset should not pretend to be here."""
129 Secret and hidden changeset should not pretend to be here."""
130 assert not repo.changelog.filteredrevs
130 assert not repo.changelog.filteredrevs
131 # fast path in simple case to avoid impact of non optimised code
131 # fast path in simple case to avoid impact of non optimised code
132 hiddens = filterrevs(repo, b'visible')
132 hiddens = filterrevs(repo, b'visible')
133 secrets = filterrevs(repo, b'served.hidden')
133 secrets = filterrevs(repo, b'served.hidden')
134 if secrets:
134 if secrets:
135 return frozenset(hiddens | secrets)
135 return frozenset(hiddens | secrets)
136 else:
136 else:
137 return hiddens
137 return hiddens
138
138
139
139
140 def computemutable(repo, visibilityexceptions=None):
140 def computemutable(repo, visibilityexceptions=None):
141 assert not repo.changelog.filteredrevs
141 assert not repo.changelog.filteredrevs
142 # fast check to avoid revset call on huge repo
142 # fast check to avoid revset call on huge repo
143 if repo._phasecache.hasnonpublicphases(repo):
143 if repo._phasecache.hasnonpublicphases(repo):
144 return frozenset(repo._phasecache.getrevset(repo, phases.mutablephases))
144 return frozenset(repo._phasecache.getrevset(repo, phases.mutablephases))
145 return frozenset()
145 return frozenset()
146
146
147
147
148 def computeimpactable(repo, visibilityexceptions=None):
148 def computeimpactable(repo, visibilityexceptions=None):
149 """Everything impactable by mutable revision
149 """Everything impactable by mutable revision
150
150
151 The immutable filter still have some chance to get invalidated. This will
151 The immutable filter still have some chance to get invalidated. This will
152 happen when:
152 happen when:
153
153
154 - you garbage collect hidden changeset,
154 - you garbage collect hidden changeset,
155 - public phase is moved backward,
155 - public phase is moved backward,
156 - something is changed in the filtering (this could be fixed)
156 - something is changed in the filtering (this could be fixed)
157
157
158 This filter out any mutable changeset and any public changeset that may be
158 This filter out any mutable changeset and any public changeset that may be
159 impacted by something happening to a mutable revision.
159 impacted by something happening to a mutable revision.
160
160
161 This is achieved by filtered everything with a revision number equal or
161 This is achieved by filtered everything with a revision number equal or
162 higher than the first mutable changeset is filtered."""
162 higher than the first mutable changeset is filtered."""
163 assert not repo.changelog.filteredrevs
163 assert not repo.changelog.filteredrevs
164 cl = repo.changelog
164 cl = repo.changelog
165 firstmutable = len(cl)
165 firstmutable = len(cl)
166 roots = repo._phasecache.nonpublicphaseroots(repo)
166 roots = repo._phasecache.nonpublicphaseroots(repo)
167 if roots:
167 if roots:
168 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
168 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
169 # protect from nullrev root
169 # protect from nullrev root
170 firstmutable = max(0, firstmutable)
170 firstmutable = max(0, firstmutable)
171 return frozenset(range(firstmutable, len(cl)))
171 return frozenset(range(firstmutable, len(cl)))
172
172
173
173
174 # function to compute filtered set
174 # function to compute filtered set
175 #
175 #
176 # When adding a new filter you MUST update the table at:
176 # When adding a new filter you MUST update the table at:
177 # mercurial.utils.repoviewutil.subsettable
177 # mercurial.utils.repoviewutil.subsettable
178 # Otherwise your filter will have to recompute all its branches cache
178 # Otherwise your filter will have to recompute all its branches cache
179 # from scratch (very slow).
179 # from scratch (very slow).
180 filtertable = {
180 filtertable = {
181 b'visible': computehidden,
181 b'visible': computehidden,
182 b'visible-hidden': computehidden,
182 b'visible-hidden': computehidden,
183 b'served.hidden': computesecret,
183 b'served.hidden': computesecret,
184 b'served': computeunserved,
184 b'served': computeunserved,
185 b'immutable': computemutable,
185 b'immutable': computemutable,
186 b'base': computeimpactable,
186 b'base': computeimpactable,
187 }
187 }
188
188
189 # set of filter level that will include the working copy parent no matter what.
189 # set of filter level that will include the working copy parent no matter what.
190 filter_has_wc = {b'visible', b'visible-hidden'}
190 filter_has_wc = {b'visible', b'visible-hidden'}
191
191
192 _basefiltername = list(filtertable)
192 _basefiltername = list(filtertable)
193
193
194
194
195 def extrafilter(ui):
195 def extrafilter(ui):
196 """initialize extra filter and return its id
196 """initialize extra filter and return its id
197
197
198 If extra filtering is configured, we make sure the associated filtered view
198 If extra filtering is configured, we make sure the associated filtered view
199 are declared and return the associated id.
199 are declared and return the associated id.
200 """
200 """
201 frevs = ui.config(b'experimental', b'extra-filter-revs')
201 frevs = ui.config(b'experimental', b'extra-filter-revs')
202 if frevs is None:
202 if frevs is None:
203 return None
203 return None
204
204
205 fid = pycompat.sysbytes(util.DIGESTS[b'sha1'](frevs).hexdigest())[:12]
205 fid = pycompat.sysbytes(util.DIGESTS[b'sha1'](frevs).hexdigest())[:12]
206
206
207 combine = lambda fname: fname + b'%' + fid
207 combine = lambda fname: fname + b'%' + fid
208
208
209 subsettable = repoviewutil.subsettable
209 subsettable = repoviewutil.subsettable
210
210
211 if combine(b'base') not in filtertable:
211 if combine(b'base') not in filtertable:
212 for name in _basefiltername:
212 for base_name in _basefiltername:
213
213
214 def extrafilteredrevs(repo, *args, **kwargs):
214 def extrafilteredrevs(repo, *args, name=base_name, **kwargs):
215 baserevs = filtertable[name](repo, *args, **kwargs)
215 baserevs = filtertable[name](repo, *args, **kwargs)
216 extrarevs = frozenset(repo.revs(frevs))
216 extrarevs = frozenset(repo.revs(frevs))
217 return baserevs | extrarevs
217 return baserevs | extrarevs
218
218
219 filtertable[combine(name)] = extrafilteredrevs
219 filtertable[combine(base_name)] = extrafilteredrevs
220 if name in subsettable:
220 if base_name in subsettable:
221 subsettable[combine(name)] = combine(subsettable[name])
221 subsettable[combine(base_name)] = combine(
222 subsettable[base_name]
223 )
222 return fid
224 return fid
223
225
224
226
225 def filterrevs(repo, filtername, visibilityexceptions=None):
227 def filterrevs(repo, filtername, visibilityexceptions=None):
226 """returns set of filtered revision for this filter name
228 """returns set of filtered revision for this filter name
227
229
228 visibilityexceptions is a set of revs which must are exceptions for
230 visibilityexceptions is a set of revs which must are exceptions for
229 hidden-state and must be visible. They are dynamic and hence we should not
231 hidden-state and must be visible. They are dynamic and hence we should not
230 cache it's result"""
232 cache it's result"""
231 if filtername not in repo.filteredrevcache:
233 if filtername not in repo.filteredrevcache:
232 if repo.ui.configbool(b'devel', b'debug.repo-filters'):
234 if repo.ui.configbool(b'devel', b'debug.repo-filters'):
233 msg = b'computing revision filter for "%s"'
235 msg = b'computing revision filter for "%s"'
234 msg %= filtername
236 msg %= filtername
235 if repo.ui.tracebackflag and repo.ui.debugflag:
237 if repo.ui.tracebackflag and repo.ui.debugflag:
236 # XXX use ui.write_err
238 # XXX use ui.write_err
237 util.debugstacktrace(
239 util.debugstacktrace(
238 msg,
240 msg,
239 f=repo.ui._fout,
241 f=repo.ui._fout,
240 otherf=repo.ui._ferr,
242 otherf=repo.ui._ferr,
241 prefix=b'debug.filters: ',
243 prefix=b'debug.filters: ',
242 )
244 )
243 else:
245 else:
244 repo.ui.debug(b'debug.filters: %s\n' % msg)
246 repo.ui.debug(b'debug.filters: %s\n' % msg)
245 func = filtertable[filtername]
247 func = filtertable[filtername]
246 if visibilityexceptions:
248 if visibilityexceptions:
247 return func(repo.unfiltered, visibilityexceptions)
249 return func(repo.unfiltered, visibilityexceptions)
248 repo.filteredrevcache[filtername] = func(repo.unfiltered())
250 repo.filteredrevcache[filtername] = func(repo.unfiltered())
249 return repo.filteredrevcache[filtername]
251 return repo.filteredrevcache[filtername]
250
252
251
253
252 def wrapchangelog(unfichangelog, filteredrevs):
254 def wrapchangelog(unfichangelog, filteredrevs):
253 cl = copy.copy(unfichangelog)
255 cl = copy.copy(unfichangelog)
254 cl.filteredrevs = filteredrevs
256 cl.filteredrevs = filteredrevs
255
257
256 class filteredchangelog(filteredchangelogmixin, cl.__class__):
258 class filteredchangelog(filteredchangelogmixin, cl.__class__):
257 pass
259 pass
258
260
259 cl.__class__ = filteredchangelog
261 cl.__class__ = filteredchangelog
260
262
261 return cl
263 return cl
262
264
263
265
264 class filteredchangelogmixin:
266 class filteredchangelogmixin:
265 def tiprev(self):
267 def tiprev(self):
266 """filtered version of revlog.tiprev"""
268 """filtered version of revlog.tiprev"""
267 for i in range(len(self) - 1, -2, -1):
269 for i in range(len(self) - 1, -2, -1):
268 if i not in self.filteredrevs:
270 if i not in self.filteredrevs:
269 return i
271 return i
270
272
271 def __contains__(self, rev):
273 def __contains__(self, rev):
272 """filtered version of revlog.__contains__"""
274 """filtered version of revlog.__contains__"""
273 return 0 <= rev < len(self) and rev not in self.filteredrevs
275 return 0 <= rev < len(self) and rev not in self.filteredrevs
274
276
275 def __iter__(self):
277 def __iter__(self):
276 """filtered version of revlog.__iter__"""
278 """filtered version of revlog.__iter__"""
277
279
278 def filterediter():
280 def filterediter():
279 for i in range(len(self)):
281 for i in range(len(self)):
280 if i not in self.filteredrevs:
282 if i not in self.filteredrevs:
281 yield i
283 yield i
282
284
283 return filterediter()
285 return filterediter()
284
286
285 def revs(self, start=0, stop=None):
287 def revs(self, start=0, stop=None):
286 """filtered version of revlog.revs"""
288 """filtered version of revlog.revs"""
287 for i in super(filteredchangelogmixin, self).revs(start, stop):
289 for i in super(filteredchangelogmixin, self).revs(start, stop):
288 if i not in self.filteredrevs:
290 if i not in self.filteredrevs:
289 yield i
291 yield i
290
292
291 def _checknofilteredinrevs(self, revs):
293 def _checknofilteredinrevs(self, revs):
292 """raise the appropriate error if 'revs' contains a filtered revision
294 """raise the appropriate error if 'revs' contains a filtered revision
293
295
294 This returns a version of 'revs' to be used thereafter by the caller.
296 This returns a version of 'revs' to be used thereafter by the caller.
295 In particular, if revs is an iterator, it is converted into a set.
297 In particular, if revs is an iterator, it is converted into a set.
296 """
298 """
297 safehasattr = util.safehasattr
299 safehasattr = util.safehasattr
298 if safehasattr(revs, '__next__'):
300 if safehasattr(revs, '__next__'):
299 # Note that inspect.isgenerator() is not true for iterators,
301 # Note that inspect.isgenerator() is not true for iterators,
300 revs = set(revs)
302 revs = set(revs)
301
303
302 filteredrevs = self.filteredrevs
304 filteredrevs = self.filteredrevs
303 if safehasattr(revs, 'first'): # smartset
305 if safehasattr(revs, 'first'): # smartset
304 offenders = revs & filteredrevs
306 offenders = revs & filteredrevs
305 else:
307 else:
306 offenders = filteredrevs.intersection(revs)
308 offenders = filteredrevs.intersection(revs)
307
309
308 for rev in offenders:
310 for rev in offenders:
309 raise error.FilteredIndexError(rev)
311 raise error.FilteredIndexError(rev)
310 return revs
312 return revs
311
313
312 def headrevs(self, revs=None):
314 def headrevs(self, revs=None):
313 if revs is None:
315 if revs is None:
314 try:
316 try:
315 return self.index.headrevsfiltered(self.filteredrevs)
317 return self.index.headrevsfiltered(self.filteredrevs)
316 # AttributeError covers non-c-extension environments and
318 # AttributeError covers non-c-extension environments and
317 # old c extensions without filter handling.
319 # old c extensions without filter handling.
318 except AttributeError:
320 except AttributeError:
319 return self._headrevs()
321 return self._headrevs()
320
322
321 revs = self._checknofilteredinrevs(revs)
323 revs = self._checknofilteredinrevs(revs)
322 return super(filteredchangelogmixin, self).headrevs(revs)
324 return super(filteredchangelogmixin, self).headrevs(revs)
323
325
324 def strip(self, *args, **kwargs):
326 def strip(self, *args, **kwargs):
325 # XXX make something better than assert
327 # XXX make something better than assert
326 # We can't expect proper strip behavior if we are filtered.
328 # We can't expect proper strip behavior if we are filtered.
327 assert not self.filteredrevs
329 assert not self.filteredrevs
328 super(filteredchangelogmixin, self).strip(*args, **kwargs)
330 super(filteredchangelogmixin, self).strip(*args, **kwargs)
329
331
330 def rev(self, node):
332 def rev(self, node):
331 """filtered version of revlog.rev"""
333 """filtered version of revlog.rev"""
332 r = super(filteredchangelogmixin, self).rev(node)
334 r = super(filteredchangelogmixin, self).rev(node)
333 if r in self.filteredrevs:
335 if r in self.filteredrevs:
334 raise error.FilteredLookupError(
336 raise error.FilteredLookupError(
335 hex(node), self.display_id, _(b'filtered node')
337 hex(node), self.display_id, _(b'filtered node')
336 )
338 )
337 return r
339 return r
338
340
339 def node(self, rev):
341 def node(self, rev):
340 """filtered version of revlog.node"""
342 """filtered version of revlog.node"""
341 if rev in self.filteredrevs:
343 if rev in self.filteredrevs:
342 raise error.FilteredIndexError(rev)
344 raise error.FilteredIndexError(rev)
343 return super(filteredchangelogmixin, self).node(rev)
345 return super(filteredchangelogmixin, self).node(rev)
344
346
345 def linkrev(self, rev):
347 def linkrev(self, rev):
346 """filtered version of revlog.linkrev"""
348 """filtered version of revlog.linkrev"""
347 if rev in self.filteredrevs:
349 if rev in self.filteredrevs:
348 raise error.FilteredIndexError(rev)
350 raise error.FilteredIndexError(rev)
349 return super(filteredchangelogmixin, self).linkrev(rev)
351 return super(filteredchangelogmixin, self).linkrev(rev)
350
352
351 def parentrevs(self, rev):
353 def parentrevs(self, rev):
352 """filtered version of revlog.parentrevs"""
354 """filtered version of revlog.parentrevs"""
353 if rev in self.filteredrevs:
355 if rev in self.filteredrevs:
354 raise error.FilteredIndexError(rev)
356 raise error.FilteredIndexError(rev)
355 return super(filteredchangelogmixin, self).parentrevs(rev)
357 return super(filteredchangelogmixin, self).parentrevs(rev)
356
358
357 def flags(self, rev):
359 def flags(self, rev):
358 """filtered version of revlog.flags"""
360 """filtered version of revlog.flags"""
359 if rev in self.filteredrevs:
361 if rev in self.filteredrevs:
360 raise error.FilteredIndexError(rev)
362 raise error.FilteredIndexError(rev)
361 return super(filteredchangelogmixin, self).flags(rev)
363 return super(filteredchangelogmixin, self).flags(rev)
362
364
363
365
364 class repoview:
366 class repoview:
365 """Provide a read/write view of a repo through a filtered changelog
367 """Provide a read/write view of a repo through a filtered changelog
366
368
367 This object is used to access a filtered version of a repository without
369 This object is used to access a filtered version of a repository without
368 altering the original repository object itself. We can not alter the
370 altering the original repository object itself. We can not alter the
369 original object for two main reasons:
371 original object for two main reasons:
370 - It prevents the use of a repo with multiple filters at the same time. In
372 - It prevents the use of a repo with multiple filters at the same time. In
371 particular when multiple threads are involved.
373 particular when multiple threads are involved.
372 - It makes scope of the filtering harder to control.
374 - It makes scope of the filtering harder to control.
373
375
374 This object behaves very closely to the original repository. All attribute
376 This object behaves very closely to the original repository. All attribute
375 operations are done on the original repository:
377 operations are done on the original repository:
376 - An access to `repoview.someattr` actually returns `repo.someattr`,
378 - An access to `repoview.someattr` actually returns `repo.someattr`,
377 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
379 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
378 - A deletion of `repoview.someattr` actually drops `someattr`
380 - A deletion of `repoview.someattr` actually drops `someattr`
379 from `repo.__dict__`.
381 from `repo.__dict__`.
380
382
381 The only exception is the `changelog` property. It is overridden to return
383 The only exception is the `changelog` property. It is overridden to return
382 a (surface) copy of `repo.changelog` with some revisions filtered. The
384 a (surface) copy of `repo.changelog` with some revisions filtered. The
383 `filtername` attribute of the view control the revisions that need to be
385 `filtername` attribute of the view control the revisions that need to be
384 filtered. (the fact the changelog is copied is an implementation detail).
386 filtered. (the fact the changelog is copied is an implementation detail).
385
387
386 Unlike attributes, this object intercepts all method calls. This means that
388 Unlike attributes, this object intercepts all method calls. This means that
387 all methods are run on the `repoview` object with the filtered `changelog`
389 all methods are run on the `repoview` object with the filtered `changelog`
388 property. For this purpose the simple `repoview` class must be mixed with
390 property. For this purpose the simple `repoview` class must be mixed with
389 the actual class of the repository. This ensures that the resulting
391 the actual class of the repository. This ensures that the resulting
390 `repoview` object have the very same methods than the repo object. This
392 `repoview` object have the very same methods than the repo object. This
391 leads to the property below.
393 leads to the property below.
392
394
393 repoview.method() --> repo.__class__.method(repoview)
395 repoview.method() --> repo.__class__.method(repoview)
394
396
395 The inheritance has to be done dynamically because `repo` can be of any
397 The inheritance has to be done dynamically because `repo` can be of any
396 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
398 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
397 """
399 """
398
400
399 def __init__(self, repo, filtername, visibilityexceptions=None):
401 def __init__(self, repo, filtername, visibilityexceptions=None):
400 object.__setattr__(self, '_unfilteredrepo', repo)
402 object.__setattr__(self, '_unfilteredrepo', repo)
401 object.__setattr__(self, 'filtername', filtername)
403 object.__setattr__(self, 'filtername', filtername)
402 object.__setattr__(self, '_clcachekey', None)
404 object.__setattr__(self, '_clcachekey', None)
403 object.__setattr__(self, '_clcache', None)
405 object.__setattr__(self, '_clcache', None)
404 # revs which are exceptions and must not be hidden
406 # revs which are exceptions and must not be hidden
405 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
407 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
406
408
407 # not a propertycache on purpose we shall implement a proper cache later
409 # not a propertycache on purpose we shall implement a proper cache later
408 @property
410 @property
409 def changelog(self):
411 def changelog(self):
410 """return a filtered version of the changeset
412 """return a filtered version of the changeset
411
413
412 this changelog must not be used for writing"""
414 this changelog must not be used for writing"""
413 # some cache may be implemented later
415 # some cache may be implemented later
414 unfi = self._unfilteredrepo
416 unfi = self._unfilteredrepo
415 unfichangelog = unfi.changelog
417 unfichangelog = unfi.changelog
416 # bypass call to changelog.method
418 # bypass call to changelog.method
417 unfiindex = unfichangelog.index
419 unfiindex = unfichangelog.index
418 unfilen = len(unfiindex)
420 unfilen = len(unfiindex)
419 unfinode = unfiindex[unfilen - 1][7]
421 unfinode = unfiindex[unfilen - 1][7]
420 with util.timedcm('repo filter for %s', self.filtername):
422 with util.timedcm('repo filter for %s', self.filtername):
421 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
423 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
422 cl = self._clcache
424 cl = self._clcache
423 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
425 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
424 # if cl.index is not unfiindex, unfi.changelog would be
426 # if cl.index is not unfiindex, unfi.changelog would be
425 # recreated, and our clcache refers to garbage object
427 # recreated, and our clcache refers to garbage object
426 if cl is not None and (
428 if cl is not None and (
427 cl.index is not unfiindex or newkey != self._clcachekey
429 cl.index is not unfiindex or newkey != self._clcachekey
428 ):
430 ):
429 cl = None
431 cl = None
430 # could have been made None by the previous if
432 # could have been made None by the previous if
431 if cl is None:
433 if cl is None:
432 # Only filter if there's something to filter
434 # Only filter if there's something to filter
433 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
435 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
434 object.__setattr__(self, '_clcache', cl)
436 object.__setattr__(self, '_clcache', cl)
435 object.__setattr__(self, '_clcachekey', newkey)
437 object.__setattr__(self, '_clcachekey', newkey)
436 return cl
438 return cl
437
439
438 def unfiltered(self):
440 def unfiltered(self):
439 """Return an unfiltered version of a repo"""
441 """Return an unfiltered version of a repo"""
440 return self._unfilteredrepo
442 return self._unfilteredrepo
441
443
442 def filtered(self, name, visibilityexceptions=None):
444 def filtered(self, name, visibilityexceptions=None):
443 """Return a filtered version of a repository"""
445 """Return a filtered version of a repository"""
444 if name == self.filtername and not visibilityexceptions:
446 if name == self.filtername and not visibilityexceptions:
445 return self
447 return self
446 return self.unfiltered().filtered(name, visibilityexceptions)
448 return self.unfiltered().filtered(name, visibilityexceptions)
447
449
448 def __repr__(self):
450 def __repr__(self):
449 return '<%s:%s %r>' % (
451 return '<%s:%s %r>' % (
450 self.__class__.__name__,
452 self.__class__.__name__,
451 pycompat.sysstr(self.filtername),
453 pycompat.sysstr(self.filtername),
452 self.unfiltered(),
454 self.unfiltered(),
453 )
455 )
454
456
455 # everything access are forwarded to the proxied repo
457 # everything access are forwarded to the proxied repo
456 def __getattr__(self, attr):
458 def __getattr__(self, attr):
457 return getattr(self._unfilteredrepo, attr)
459 return getattr(self._unfilteredrepo, attr)
458
460
459 def __setattr__(self, attr, value):
461 def __setattr__(self, attr, value):
460 return setattr(self._unfilteredrepo, attr, value)
462 return setattr(self._unfilteredrepo, attr, value)
461
463
462 def __delattr__(self, attr):
464 def __delattr__(self, attr):
463 return delattr(self._unfilteredrepo, attr)
465 return delattr(self._unfilteredrepo, attr)
464
466
465
467
466 # Dynamically created classes introduce memory cycles via __mro__. See
468 # Dynamically created classes introduce memory cycles via __mro__. See
467 # https://bugs.python.org/issue17950.
469 # https://bugs.python.org/issue17950.
468 # This need of the garbage collector can turn into memory leak in
470 # This need of the garbage collector can turn into memory leak in
469 # Python <3.4, which is the first version released with PEP 442.
471 # Python <3.4, which is the first version released with PEP 442.
470 _filteredrepotypes = weakref.WeakKeyDictionary()
472 _filteredrepotypes = weakref.WeakKeyDictionary()
471
473
472
474
473 def newtype(base):
475 def newtype(base):
474 """Create a new type with the repoview mixin and the given base class"""
476 """Create a new type with the repoview mixin and the given base class"""
475 ref = _filteredrepotypes.get(base)
477 ref = _filteredrepotypes.get(base)
476 if ref is not None:
478 if ref is not None:
477 cls = ref()
479 cls = ref()
478 if cls is not None:
480 if cls is not None:
479 return cls
481 return cls
480
482
481 class filteredrepo(repoview, base):
483 class filteredrepo(repoview, base):
482 pass
484 pass
483
485
484 _filteredrepotypes[base] = weakref.ref(filteredrepo)
486 _filteredrepotypes[base] = weakref.ref(filteredrepo)
485 # do not reread from weakref to be 100% sure not to return None
487 # do not reread from weakref to be 100% sure not to return None
486 return filteredrepo
488 return filteredrepo
@@ -1,120 +1,120 b''
1 $ hg init test
1 $ hg init test
2 $ cd test
2 $ cd test
3 $ hg debugbuilddag '+2'
3 $ hg debugbuilddag '+2'
4 $ hg phase --public 0
4 $ hg phase --public 0
5
5
6 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
6 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
7 $ cat hg.pid >> $DAEMON_PIDS
7 $ cat hg.pid >> $DAEMON_PIDS
8 $ cd ..
8 $ cd ..
9 $ hg init test2
9 $ hg init test2
10 $ cd test2
10 $ cd test2
11 $ hg incoming http://foo:xyzzy@localhost:$HGPORT/ -T '{desc}\n'
11 $ hg incoming http://foo:xyzzy@localhost:$HGPORT/ -T '{desc}\n'
12 comparing with http://foo:***@localhost:$HGPORT/
12 comparing with http://foo:***@localhost:$HGPORT/
13 r0
13 r0
14 r1
14 r1
15 $ killdaemons.py
15 $ killdaemons.py
16
16
17 $ cd ..
17 $ cd ..
18 $ hg -R test --config server.view=immutable serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
18 $ hg -R test --config server.view=immutable serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
19 $ cat hg.pid >> $DAEMON_PIDS
19 $ cat hg.pid >> $DAEMON_PIDS
20 $ hg -R test2 incoming http://foo:xyzzy@localhost:$HGPORT/ -T '{desc}\n'
20 $ hg -R test2 incoming http://foo:xyzzy@localhost:$HGPORT/ -T '{desc}\n'
21 comparing with http://foo:***@localhost:$HGPORT/
21 comparing with http://foo:***@localhost:$HGPORT/
22 r0
22 r0
23
23
24 Check same result using `experimental.extra-filter-revs`
24 Check same result using `experimental.extra-filter-revs`
25
25
26 $ hg -R test --config experimental.extra-filter-revs='not public()' serve -p $HGPORT1 -d --pid-file=hg2.pid -E errors.log
26 $ hg -R test --config experimental.extra-filter-revs='not public()' serve -p $HGPORT1 -d --pid-file=hg2.pid -E errors.log
27 $ cat hg2.pid >> $DAEMON_PIDS
27 $ cat hg2.pid >> $DAEMON_PIDS
28 $ hg -R test2 incoming http://foo:xyzzy@localhost:$HGPORT1/
28 $ hg -R test2 incoming http://foo:xyzzy@localhost:$HGPORT1/
29 comparing with http://foo:***@localhost:$HGPORT1/
29 comparing with http://foo:***@localhost:$HGPORT1/
30 changeset: 0:1ea73414a91b
30 changeset: 0:1ea73414a91b
31 tag: tip
31 tag: tip
32 user: debugbuilddag
32 user: debugbuilddag
33 date: Thu Jan 01 00:00:00 1970 +0000
33 date: Thu Jan 01 00:00:00 1970 +0000
34 summary: r0
34 summary: r0
35
35
36 $ hg -R test --config experimental.extra-filter-revs='not public()' debugupdatecache
36 $ hg -R test --config experimental.extra-filter-revs='not public()' debugupdatecache
37 $ ls -1 test/.hg/cache/
37 $ ls -1 test/.hg/cache/
38 branch2-base%89c45d2fa07e
38 branch2-base%89c45d2fa07e
39 branch2-immutable%89c45d2fa07e
39 branch2-immutable%89c45d2fa07e
40 branch2-served
40 branch2-served
41 branch2-served%89c45d2fa07e
41 branch2-served%89c45d2fa07e
42 branch2-served.hidden%89c45d2fa07e
42 branch2-served.hidden%89c45d2fa07e
43 branch2-visible%89c45d2fa07e
43 branch2-visible%89c45d2fa07e
44 branch2-visible-hidden%89c45d2fa07e
44 branch2-visible-hidden%89c45d2fa07e
45 hgtagsfnodes1
45 hgtagsfnodes1
46 rbc-names-v1
46 rbc-names-v1
47 rbc-revs-v1
47 rbc-revs-v1
48 tags2
48 tags2
49 tags2-served%89c45d2fa07e
49 tags2-served%89c45d2fa07e
50
50
51 cleanup
51 cleanup
52
52
53 $ cat errors.log
53 $ cat errors.log
54 $ killdaemons.py
54 $ killdaemons.py
55
55
56 Check the behavior is other filtered revision exists
56 Check the behavior is other filtered revision exists
57 ----------------------------------------------------
57 ----------------------------------------------------
58
58
59 add more content and complexity to the repository too
59 add more content and complexity to the repository too
60
60
61 $ hg -R test debugbuilddag '+6:branchpoint.:left+4*branchpoint.:right+5' --from-existing
61 $ hg -R test debugbuilddag '+6:branchpoint.:left+4*branchpoint.:right+5' --from-existing
62 $ hg -R test phase --public 'desc("re:^r11$")'
62 $ hg -R test phase --public 'desc("re:^r11$")'
63 $ hg -R test phase --secret --force 'desc("re:^r9$")'
63 $ hg -R test phase --secret --force 'desc("re:^r9$")'
64 $ hg -R test log -G -T '{desc} {phase}\n'
64 $ hg -R test log -G -T '{desc} {phase}\n'
65 o r17 draft
65 o r17 draft
66 |
66 |
67 o r16 draft
67 o r16 draft
68 |
68 |
69 o r15 draft
69 o r15 draft
70 |
70 |
71 o r14 draft
71 o r14 draft
72 |
72 |
73 o r13 draft
73 o r13 draft
74 |
74 |
75 o r12 draft
75 o r12 draft
76 |
76 |
77 o r11 public
77 o r11 public
78 |
78 |
79 | o r10 secret
79 | o r10 secret
80 | |
80 | |
81 | o r9 secret
81 | o r9 secret
82 | |
82 | |
83 | o r8 draft
83 | o r8 draft
84 | |
84 | |
85 | o r7 draft
85 | o r7 draft
86 | |
86 | |
87 | o r6 draft
87 | o r6 draft
88 |/
88 |/
89 o r5 public
89 o r5 public
90 |
90 |
91 o r4 public
91 o r4 public
92 |
92 |
93 o r3 public
93 o r3 public
94 |
94 |
95 o r2 public
95 o r2 public
96 |
96 |
97 o r1 public
97 o r1 public
98 |
98 |
99 o r0 public
99 o r0 public
100
100
101 $ hg -R test --config experimental.extra-filter-revs='(desc("re:^r13$") + desc("re:^r10$"))::' serve -p $HGPORT1 -d --pid-file=hg2.pid -E errors.log
101 $ hg -R test --config experimental.extra-filter-revs='(desc("re:^r13$") + desc("re:^r10$"))::' serve -p $HGPORT1 -d --pid-file=hg2.pid -E errors.log
102 $ cat hg2.pid >> $DAEMON_PIDS
102 $ cat hg2.pid >> $DAEMON_PIDS
103 $ hg -R test2 incoming http://foo:xyzzy@localhost:$HGPORT1/ -T '{desc}\n'
103 $ hg -R test2 incoming http://foo:xyzzy@localhost:$HGPORT1/ -T '{desc}\n'
104 comparing with http://foo:***@localhost:$HGPORT1/
104 comparing with http://foo:***@localhost:$HGPORT1/
105 r0
105 r0
106 r1
106 r1
107 r2
107 r2
108 r3
108 r3
109 r4
109 r4
110 r5
110 r5
111 r6 (missing-correct-output !)
111 r6
112 r7 (missing-correct-output !)
112 r7
113 r8 (missing-correct-output !)
113 r8
114 r11 (missing-correct-output !)
114 r11
115 r12 (missing-correct-output !)
115 r12
116
116
117 cleanups
117 cleanups
118
118
119 $ cat errors.log
119 $ cat errors.log
120 $ killdaemons.py
120 $ killdaemons.py
General Comments 0
You need to be logged in to leave comments. Login now