##// END OF EJS Templates
repoview: ignore unwritable hidden cache...
Matt Mackall -
r29040:a4dc5fe7 stable
parent child Browse files
Show More
@@ -1,361 +1,360 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 heapq
12 import heapq
13 import struct
13 import struct
14
14
15 from .node import nullrev
15 from .node import nullrev
16 from . import (
16 from . import (
17 error,
17 error,
18 obsolete,
18 obsolete,
19 phases,
19 phases,
20 tags as tagsmod,
20 tags as tagsmod,
21 util,
21 util,
22 )
22 )
23
23
24 def hideablerevs(repo):
24 def hideablerevs(repo):
25 """Revision candidates to be hidden
25 """Revision candidates to be hidden
26
26
27 This is a standalone function to allow extensions to wrap it.
27 This is a standalone function to allow extensions to wrap it.
28
28
29 Because we use the set of immutable changesets as a fallback subset in
29 Because we use the set of immutable changesets as a fallback subset in
30 branchmap (see mercurial.branchmap.subsettable), you cannot set "public"
30 branchmap (see mercurial.branchmap.subsettable), you cannot set "public"
31 changesets as "hideable". Doing so would break multiple code assertions and
31 changesets as "hideable". Doing so would break multiple code assertions and
32 lead to crashes."""
32 lead to crashes."""
33 return obsolete.getrevs(repo, 'obsolete')
33 return obsolete.getrevs(repo, 'obsolete')
34
34
35 def _getstatichidden(repo):
35 def _getstatichidden(repo):
36 """Revision to be hidden (disregarding dynamic blocker)
36 """Revision to be hidden (disregarding dynamic blocker)
37
37
38 To keep a consistent graph, we cannot hide any revisions with
38 To keep a consistent graph, we cannot hide any revisions with
39 non-hidden descendants. This function computes the set of
39 non-hidden descendants. This function computes the set of
40 revisions that could be hidden while keeping the graph consistent.
40 revisions that could be hidden while keeping the graph consistent.
41
41
42 A second pass will be done to apply "dynamic blocker" like bookmarks or
42 A second pass will be done to apply "dynamic blocker" like bookmarks or
43 working directory parents.
43 working directory parents.
44
44
45 """
45 """
46 assert not repo.changelog.filteredrevs
46 assert not repo.changelog.filteredrevs
47 hidden = set(hideablerevs(repo))
47 hidden = set(hideablerevs(repo))
48 if hidden:
48 if hidden:
49 getphase = repo._phasecache.phase
49 getphase = repo._phasecache.phase
50 getparentrevs = repo.changelog.parentrevs
50 getparentrevs = repo.changelog.parentrevs
51 # Skip heads which are public (guaranteed to not be hidden)
51 # Skip heads which are public (guaranteed to not be hidden)
52 heap = [-r for r in repo.changelog.headrevs() if getphase(repo, r)]
52 heap = [-r for r in repo.changelog.headrevs() if getphase(repo, r)]
53 heapq.heapify(heap)
53 heapq.heapify(heap)
54 heappop = heapq.heappop
54 heappop = heapq.heappop
55 heappush = heapq.heappush
55 heappush = heapq.heappush
56 seen = set() # no need to init it with heads, they have no children
56 seen = set() # no need to init it with heads, they have no children
57 while heap:
57 while heap:
58 rev = -heappop(heap)
58 rev = -heappop(heap)
59 # All children have been processed so at that point, if no children
59 # All children have been processed so at that point, if no children
60 # removed 'rev' from the 'hidden' set, 'rev' is going to be hidden.
60 # removed 'rev' from the 'hidden' set, 'rev' is going to be hidden.
61 blocker = rev not in hidden
61 blocker = rev not in hidden
62 for parent in getparentrevs(rev):
62 for parent in getparentrevs(rev):
63 if parent == nullrev:
63 if parent == nullrev:
64 continue
64 continue
65 if blocker:
65 if blocker:
66 # If visible, ensure parent will be visible too
66 # If visible, ensure parent will be visible too
67 hidden.discard(parent)
67 hidden.discard(parent)
68 # - Avoid adding the same revision twice
68 # - Avoid adding the same revision twice
69 # - Skip nodes which are public (guaranteed to not be hidden)
69 # - Skip nodes which are public (guaranteed to not be hidden)
70 pre = len(seen)
70 pre = len(seen)
71 seen.add(parent)
71 seen.add(parent)
72 if pre < len(seen) and getphase(repo, rev):
72 if pre < len(seen) and getphase(repo, rev):
73 heappush(heap, -parent)
73 heappush(heap, -parent)
74 return hidden
74 return hidden
75
75
76 def _getdynamicblockers(repo):
76 def _getdynamicblockers(repo):
77 """Non-cacheable revisions blocking hidden changesets from being filtered.
77 """Non-cacheable revisions blocking hidden changesets from being filtered.
78
78
79 Get revisions that will block hidden changesets and are likely to change,
79 Get revisions that will block hidden changesets and are likely to change,
80 but unlikely to create hidden blockers. They won't be cached, so be careful
80 but unlikely to create hidden blockers. They won't be cached, so be careful
81 with adding additional computation."""
81 with adding additional computation."""
82
82
83 cl = repo.changelog
83 cl = repo.changelog
84 blockers = set()
84 blockers = set()
85 blockers.update([par.rev() for par in repo[None].parents()])
85 blockers.update([par.rev() for par in repo[None].parents()])
86 blockers.update([cl.rev(bm) for bm in repo._bookmarks.values()])
86 blockers.update([cl.rev(bm) for bm in repo._bookmarks.values()])
87
87
88 tags = {}
88 tags = {}
89 tagsmod.readlocaltags(repo.ui, repo, tags, {})
89 tagsmod.readlocaltags(repo.ui, repo, tags, {})
90 if tags:
90 if tags:
91 rev, nodemap = cl.rev, cl.nodemap
91 rev, nodemap = cl.rev, cl.nodemap
92 blockers.update(rev(t[0]) for t in tags.values() if t[0] in nodemap)
92 blockers.update(rev(t[0]) for t in tags.values() if t[0] in nodemap)
93 return blockers
93 return blockers
94
94
95 cacheversion = 1
95 cacheversion = 1
96 cachefile = 'cache/hidden'
96 cachefile = 'cache/hidden'
97
97
98 def cachehash(repo, hideable):
98 def cachehash(repo, hideable):
99 """return sha1 hash of repository data to identify a valid cache.
99 """return sha1 hash of repository data to identify a valid cache.
100
100
101 We calculate a sha1 of repo heads and the content of the obsstore and write
101 We calculate a sha1 of repo heads and the content of the obsstore and write
102 it to the cache. Upon reading we can easily validate by checking the hash
102 it to the cache. Upon reading we can easily validate by checking the hash
103 against the stored one and discard the cache in case the hashes don't match.
103 against the stored one and discard the cache in case the hashes don't match.
104 """
104 """
105 h = util.sha1()
105 h = util.sha1()
106 h.update(''.join(repo.heads()))
106 h.update(''.join(repo.heads()))
107 h.update(str(hash(frozenset(hideable))))
107 h.update(str(hash(frozenset(hideable))))
108 return h.digest()
108 return h.digest()
109
109
110 def _writehiddencache(cachefile, cachehash, hidden):
110 def _writehiddencache(cachefile, cachehash, hidden):
111 """write hidden data to a cache file"""
111 """write hidden data to a cache file"""
112 data = struct.pack('>%ii' % len(hidden), *sorted(hidden))
112 data = struct.pack('>%ii' % len(hidden), *sorted(hidden))
113 cachefile.write(struct.pack(">H", cacheversion))
113 cachefile.write(struct.pack(">H", cacheversion))
114 cachefile.write(cachehash)
114 cachefile.write(cachehash)
115 cachefile.write(data)
115 cachefile.write(data)
116
116
117 def trywritehiddencache(repo, hideable, hidden):
117 def trywritehiddencache(repo, hideable, hidden):
118 """write cache of hidden changesets to disk
118 """write cache of hidden changesets to disk
119
119
120 Will not write the cache if a wlock cannot be obtained lazily.
120 Will not write the cache if a wlock cannot be obtained lazily.
121 The cache consists of a head of 22byte:
121 The cache consists of a head of 22byte:
122 2 byte version number of the cache
122 2 byte version number of the cache
123 20 byte sha1 to validate the cache
123 20 byte sha1 to validate the cache
124 n*4 byte hidden revs
124 n*4 byte hidden revs
125 """
125 """
126 wlock = fh = None
126 wlock = fh = None
127 try:
127 try:
128 wlock = repo.wlock(wait=False)
128 wlock = repo.wlock(wait=False)
129 # write cache to file
129 # write cache to file
130 newhash = cachehash(repo, hideable)
130 newhash = cachehash(repo, hideable)
131 fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True)
131 fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True)
132 _writehiddencache(fh, newhash, hidden)
132 _writehiddencache(fh, newhash, hidden)
133 fh.close()
133 except (IOError, OSError):
134 except (IOError, OSError):
134 repo.ui.debug('error writing hidden changesets cache\n')
135 repo.ui.debug('error writing hidden changesets cache\n')
135 except error.LockHeld:
136 except error.LockHeld:
136 repo.ui.debug('cannot obtain lock to write hidden changesets cache\n')
137 repo.ui.debug('cannot obtain lock to write hidden changesets cache\n')
137 finally:
138 finally:
138 if fh:
139 fh.close()
140 if wlock:
139 if wlock:
141 wlock.release()
140 wlock.release()
142
141
143 def tryreadcache(repo, hideable):
142 def tryreadcache(repo, hideable):
144 """read a cache if the cache exists and is valid, otherwise returns None."""
143 """read a cache if the cache exists and is valid, otherwise returns None."""
145 hidden = fh = None
144 hidden = fh = None
146 try:
145 try:
147 if repo.vfs.exists(cachefile):
146 if repo.vfs.exists(cachefile):
148 fh = repo.vfs.open(cachefile, 'rb')
147 fh = repo.vfs.open(cachefile, 'rb')
149 version, = struct.unpack(">H", fh.read(2))
148 version, = struct.unpack(">H", fh.read(2))
150 oldhash = fh.read(20)
149 oldhash = fh.read(20)
151 newhash = cachehash(repo, hideable)
150 newhash = cachehash(repo, hideable)
152 if (cacheversion, oldhash) == (version, newhash):
151 if (cacheversion, oldhash) == (version, newhash):
153 # cache is valid, so we can start reading the hidden revs
152 # cache is valid, so we can start reading the hidden revs
154 data = fh.read()
153 data = fh.read()
155 count = len(data) / 4
154 count = len(data) / 4
156 hidden = frozenset(struct.unpack('>%ii' % count, data))
155 hidden = frozenset(struct.unpack('>%ii' % count, data))
157 return hidden
156 return hidden
158 except struct.error:
157 except struct.error:
159 repo.ui.debug('corrupted hidden cache\n')
158 repo.ui.debug('corrupted hidden cache\n')
160 # No need to fix the content as it will get rewritten
159 # No need to fix the content as it will get rewritten
161 return None
160 return None
162 except (IOError, OSError):
161 except (IOError, OSError):
163 repo.ui.debug('cannot read hidden cache\n')
162 repo.ui.debug('cannot read hidden cache\n')
164 return None
163 return None
165 finally:
164 finally:
166 if fh:
165 if fh:
167 fh.close()
166 fh.close()
168
167
169 def computehidden(repo):
168 def computehidden(repo):
170 """compute the set of hidden revision to filter
169 """compute the set of hidden revision to filter
171
170
172 During most operation hidden should be filtered."""
171 During most operation hidden should be filtered."""
173 assert not repo.changelog.filteredrevs
172 assert not repo.changelog.filteredrevs
174
173
175 hidden = frozenset()
174 hidden = frozenset()
176 hideable = hideablerevs(repo)
175 hideable = hideablerevs(repo)
177 if hideable:
176 if hideable:
178 cl = repo.changelog
177 cl = repo.changelog
179 hidden = tryreadcache(repo, hideable)
178 hidden = tryreadcache(repo, hideable)
180 if hidden is None:
179 if hidden is None:
181 hidden = frozenset(_getstatichidden(repo))
180 hidden = frozenset(_getstatichidden(repo))
182 trywritehiddencache(repo, hideable, hidden)
181 trywritehiddencache(repo, hideable, hidden)
183
182
184 # check if we have wd parents, bookmarks or tags pointing to hidden
183 # check if we have wd parents, bookmarks or tags pointing to hidden
185 # changesets and remove those.
184 # changesets and remove those.
186 dynamic = hidden & _getdynamicblockers(repo)
185 dynamic = hidden & _getdynamicblockers(repo)
187 if dynamic:
186 if dynamic:
188 blocked = cl.ancestors(dynamic, inclusive=True)
187 blocked = cl.ancestors(dynamic, inclusive=True)
189 hidden = frozenset(r for r in hidden if r not in blocked)
188 hidden = frozenset(r for r in hidden if r not in blocked)
190 return hidden
189 return hidden
191
190
192 def computeunserved(repo):
191 def computeunserved(repo):
193 """compute the set of revision that should be filtered when used a server
192 """compute the set of revision that should be filtered when used a server
194
193
195 Secret and hidden changeset should not pretend to be here."""
194 Secret and hidden changeset should not pretend to be here."""
196 assert not repo.changelog.filteredrevs
195 assert not repo.changelog.filteredrevs
197 # fast path in simple case to avoid impact of non optimised code
196 # fast path in simple case to avoid impact of non optimised code
198 hiddens = filterrevs(repo, 'visible')
197 hiddens = filterrevs(repo, 'visible')
199 if phases.hassecret(repo):
198 if phases.hassecret(repo):
200 cl = repo.changelog
199 cl = repo.changelog
201 secret = phases.secret
200 secret = phases.secret
202 getphase = repo._phasecache.phase
201 getphase = repo._phasecache.phase
203 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
202 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
204 revs = cl.revs(start=first)
203 revs = cl.revs(start=first)
205 secrets = set(r for r in revs if getphase(repo, r) >= secret)
204 secrets = set(r for r in revs if getphase(repo, r) >= secret)
206 return frozenset(hiddens | secrets)
205 return frozenset(hiddens | secrets)
207 else:
206 else:
208 return hiddens
207 return hiddens
209
208
210 def computemutable(repo):
209 def computemutable(repo):
211 """compute the set of revision that should be filtered when used a server
210 """compute the set of revision that should be filtered when used a server
212
211
213 Secret and hidden changeset should not pretend to be here."""
212 Secret and hidden changeset should not pretend to be here."""
214 assert not repo.changelog.filteredrevs
213 assert not repo.changelog.filteredrevs
215 # fast check to avoid revset call on huge repo
214 # fast check to avoid revset call on huge repo
216 if any(repo._phasecache.phaseroots[1:]):
215 if any(repo._phasecache.phaseroots[1:]):
217 getphase = repo._phasecache.phase
216 getphase = repo._phasecache.phase
218 maymutable = filterrevs(repo, 'base')
217 maymutable = filterrevs(repo, 'base')
219 return frozenset(r for r in maymutable if getphase(repo, r))
218 return frozenset(r for r in maymutable if getphase(repo, r))
220 return frozenset()
219 return frozenset()
221
220
222 def computeimpactable(repo):
221 def computeimpactable(repo):
223 """Everything impactable by mutable revision
222 """Everything impactable by mutable revision
224
223
225 The immutable filter still have some chance to get invalidated. This will
224 The immutable filter still have some chance to get invalidated. This will
226 happen when:
225 happen when:
227
226
228 - you garbage collect hidden changeset,
227 - you garbage collect hidden changeset,
229 - public phase is moved backward,
228 - public phase is moved backward,
230 - something is changed in the filtering (this could be fixed)
229 - something is changed in the filtering (this could be fixed)
231
230
232 This filter out any mutable changeset and any public changeset that may be
231 This filter out any mutable changeset and any public changeset that may be
233 impacted by something happening to a mutable revision.
232 impacted by something happening to a mutable revision.
234
233
235 This is achieved by filtered everything with a revision number egal or
234 This is achieved by filtered everything with a revision number egal or
236 higher than the first mutable changeset is filtered."""
235 higher than the first mutable changeset is filtered."""
237 assert not repo.changelog.filteredrevs
236 assert not repo.changelog.filteredrevs
238 cl = repo.changelog
237 cl = repo.changelog
239 firstmutable = len(cl)
238 firstmutable = len(cl)
240 for roots in repo._phasecache.phaseroots[1:]:
239 for roots in repo._phasecache.phaseroots[1:]:
241 if roots:
240 if roots:
242 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
241 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
243 # protect from nullrev root
242 # protect from nullrev root
244 firstmutable = max(0, firstmutable)
243 firstmutable = max(0, firstmutable)
245 return frozenset(xrange(firstmutable, len(cl)))
244 return frozenset(xrange(firstmutable, len(cl)))
246
245
247 # function to compute filtered set
246 # function to compute filtered set
248 #
247 #
249 # When adding a new filter you MUST update the table at:
248 # When adding a new filter you MUST update the table at:
250 # mercurial.branchmap.subsettable
249 # mercurial.branchmap.subsettable
251 # Otherwise your filter will have to recompute all its branches cache
250 # Otherwise your filter will have to recompute all its branches cache
252 # from scratch (very slow).
251 # from scratch (very slow).
253 filtertable = {'visible': computehidden,
252 filtertable = {'visible': computehidden,
254 'served': computeunserved,
253 'served': computeunserved,
255 'immutable': computemutable,
254 'immutable': computemutable,
256 'base': computeimpactable}
255 'base': computeimpactable}
257
256
258 def filterrevs(repo, filtername):
257 def filterrevs(repo, filtername):
259 """returns set of filtered revision for this filter name"""
258 """returns set of filtered revision for this filter name"""
260 if filtername not in repo.filteredrevcache:
259 if filtername not in repo.filteredrevcache:
261 func = filtertable[filtername]
260 func = filtertable[filtername]
262 repo.filteredrevcache[filtername] = func(repo.unfiltered())
261 repo.filteredrevcache[filtername] = func(repo.unfiltered())
263 return repo.filteredrevcache[filtername]
262 return repo.filteredrevcache[filtername]
264
263
265 class repoview(object):
264 class repoview(object):
266 """Provide a read/write view of a repo through a filtered changelog
265 """Provide a read/write view of a repo through a filtered changelog
267
266
268 This object is used to access a filtered version of a repository without
267 This object is used to access a filtered version of a repository without
269 altering the original repository object itself. We can not alter the
268 altering the original repository object itself. We can not alter the
270 original object for two main reasons:
269 original object for two main reasons:
271 - It prevents the use of a repo with multiple filters at the same time. In
270 - It prevents the use of a repo with multiple filters at the same time. In
272 particular when multiple threads are involved.
271 particular when multiple threads are involved.
273 - It makes scope of the filtering harder to control.
272 - It makes scope of the filtering harder to control.
274
273
275 This object behaves very closely to the original repository. All attribute
274 This object behaves very closely to the original repository. All attribute
276 operations are done on the original repository:
275 operations are done on the original repository:
277 - An access to `repoview.someattr` actually returns `repo.someattr`,
276 - An access to `repoview.someattr` actually returns `repo.someattr`,
278 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
277 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
279 - A deletion of `repoview.someattr` actually drops `someattr`
278 - A deletion of `repoview.someattr` actually drops `someattr`
280 from `repo.__dict__`.
279 from `repo.__dict__`.
281
280
282 The only exception is the `changelog` property. It is overridden to return
281 The only exception is the `changelog` property. It is overridden to return
283 a (surface) copy of `repo.changelog` with some revisions filtered. The
282 a (surface) copy of `repo.changelog` with some revisions filtered. The
284 `filtername` attribute of the view control the revisions that need to be
283 `filtername` attribute of the view control the revisions that need to be
285 filtered. (the fact the changelog is copied is an implementation detail).
284 filtered. (the fact the changelog is copied is an implementation detail).
286
285
287 Unlike attributes, this object intercepts all method calls. This means that
286 Unlike attributes, this object intercepts all method calls. This means that
288 all methods are run on the `repoview` object with the filtered `changelog`
287 all methods are run on the `repoview` object with the filtered `changelog`
289 property. For this purpose the simple `repoview` class must be mixed with
288 property. For this purpose the simple `repoview` class must be mixed with
290 the actual class of the repository. This ensures that the resulting
289 the actual class of the repository. This ensures that the resulting
291 `repoview` object have the very same methods than the repo object. This
290 `repoview` object have the very same methods than the repo object. This
292 leads to the property below.
291 leads to the property below.
293
292
294 repoview.method() --> repo.__class__.method(repoview)
293 repoview.method() --> repo.__class__.method(repoview)
295
294
296 The inheritance has to be done dynamically because `repo` can be of any
295 The inheritance has to be done dynamically because `repo` can be of any
297 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
296 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
298 """
297 """
299
298
300 def __init__(self, repo, filtername):
299 def __init__(self, repo, filtername):
301 object.__setattr__(self, '_unfilteredrepo', repo)
300 object.__setattr__(self, '_unfilteredrepo', repo)
302 object.__setattr__(self, 'filtername', filtername)
301 object.__setattr__(self, 'filtername', filtername)
303 object.__setattr__(self, '_clcachekey', None)
302 object.__setattr__(self, '_clcachekey', None)
304 object.__setattr__(self, '_clcache', None)
303 object.__setattr__(self, '_clcache', None)
305
304
306 # not a propertycache on purpose we shall implement a proper cache later
305 # not a propertycache on purpose we shall implement a proper cache later
307 @property
306 @property
308 def changelog(self):
307 def changelog(self):
309 """return a filtered version of the changeset
308 """return a filtered version of the changeset
310
309
311 this changelog must not be used for writing"""
310 this changelog must not be used for writing"""
312 # some cache may be implemented later
311 # some cache may be implemented later
313 unfi = self._unfilteredrepo
312 unfi = self._unfilteredrepo
314 unfichangelog = unfi.changelog
313 unfichangelog = unfi.changelog
315 # bypass call to changelog.method
314 # bypass call to changelog.method
316 unfiindex = unfichangelog.index
315 unfiindex = unfichangelog.index
317 unfilen = len(unfiindex) - 1
316 unfilen = len(unfiindex) - 1
318 unfinode = unfiindex[unfilen - 1][7]
317 unfinode = unfiindex[unfilen - 1][7]
319
318
320 revs = filterrevs(unfi, self.filtername)
319 revs = filterrevs(unfi, self.filtername)
321 cl = self._clcache
320 cl = self._clcache
322 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
321 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
323 # if cl.index is not unfiindex, unfi.changelog would be
322 # if cl.index is not unfiindex, unfi.changelog would be
324 # recreated, and our clcache refers to garbage object
323 # recreated, and our clcache refers to garbage object
325 if (cl is not None and
324 if (cl is not None and
326 (cl.index is not unfiindex or newkey != self._clcachekey)):
325 (cl.index is not unfiindex or newkey != self._clcachekey)):
327 cl = None
326 cl = None
328 # could have been made None by the previous if
327 # could have been made None by the previous if
329 if cl is None:
328 if cl is None:
330 cl = copy.copy(unfichangelog)
329 cl = copy.copy(unfichangelog)
331 cl.filteredrevs = revs
330 cl.filteredrevs = revs
332 object.__setattr__(self, '_clcache', cl)
331 object.__setattr__(self, '_clcache', cl)
333 object.__setattr__(self, '_clcachekey', newkey)
332 object.__setattr__(self, '_clcachekey', newkey)
334 return cl
333 return cl
335
334
336 def unfiltered(self):
335 def unfiltered(self):
337 """Return an unfiltered version of a repo"""
336 """Return an unfiltered version of a repo"""
338 return self._unfilteredrepo
337 return self._unfilteredrepo
339
338
340 def filtered(self, name):
339 def filtered(self, name):
341 """Return a filtered version of a repository"""
340 """Return a filtered version of a repository"""
342 if name == self.filtername:
341 if name == self.filtername:
343 return self
342 return self
344 return self.unfiltered().filtered(name)
343 return self.unfiltered().filtered(name)
345
344
346 # everything access are forwarded to the proxied repo
345 # everything access are forwarded to the proxied repo
347 def __getattr__(self, attr):
346 def __getattr__(self, attr):
348 return getattr(self._unfilteredrepo, attr)
347 return getattr(self._unfilteredrepo, attr)
349
348
350 def __setattr__(self, attr, value):
349 def __setattr__(self, attr, value):
351 return setattr(self._unfilteredrepo, attr, value)
350 return setattr(self._unfilteredrepo, attr, value)
352
351
353 def __delattr__(self, attr):
352 def __delattr__(self, attr):
354 return delattr(self._unfilteredrepo, attr)
353 return delattr(self._unfilteredrepo, attr)
355
354
356 # The `requirements` attribute is initialized during __init__. But
355 # The `requirements` attribute is initialized during __init__. But
357 # __getattr__ won't be called as it also exists on the class. We need
356 # __getattr__ won't be called as it also exists on the class. We need
358 # explicit forwarding to main repo here
357 # explicit forwarding to main repo here
359 @property
358 @property
360 def requirements(self):
359 def requirements(self):
361 return self._unfilteredrepo.requirements
360 return self._unfilteredrepo.requirements
General Comments 0
You need to be logged in to leave comments. Login now