##// END OF EJS Templates
hidden: drop obsolete comment about cacheability...
Martin von Zweigbergk -
r32579:bceb398e default
parent child Browse files
Show More
@@ -1,289 +1,289 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
12
13 from .node import nullrev
13 from .node import nullrev
14 from . import (
14 from . import (
15 obsolete,
15 obsolete,
16 phases,
16 phases,
17 tags as tagsmod,
17 tags as tagsmod,
18 )
18 )
19
19
20 def hideablerevs(repo):
20 def hideablerevs(repo):
21 """Revision candidates to be hidden
21 """Revision candidates to be hidden
22
22
23 This is a standalone function to allow extensions to wrap it.
23 This is a standalone function to allow extensions to wrap it.
24
24
25 Because we use the set of immutable changesets as a fallback subset in
25 Because we use the set of immutable changesets as a fallback subset in
26 branchmap (see mercurial.branchmap.subsettable), you cannot set "public"
26 branchmap (see mercurial.branchmap.subsettable), you cannot set "public"
27 changesets as "hideable". Doing so would break multiple code assertions and
27 changesets as "hideable". Doing so would break multiple code assertions and
28 lead to crashes."""
28 lead to crashes."""
29 return obsolete.getrevs(repo, 'obsolete')
29 return obsolete.getrevs(repo, 'obsolete')
30
30
31 def revealedrevs(repo):
31 def revealedrevs(repo):
32 """non-cacheable revisions blocking hidden changesets from being filtered
32 """revisions blocking hidden changesets from being filtered
33 """
33 """
34
34
35 cl = repo.changelog
35 cl = repo.changelog
36 blockers = set()
36 blockers = set()
37 blockers.update([par.rev() for par in repo[None].parents()])
37 blockers.update([par.rev() for par in repo[None].parents()])
38 blockers.update([cl.rev(bm) for bm in repo._bookmarks.values()])
38 blockers.update([cl.rev(bm) for bm in repo._bookmarks.values()])
39
39
40 tags = {}
40 tags = {}
41 tagsmod.readlocaltags(repo.ui, repo, tags, {})
41 tagsmod.readlocaltags(repo.ui, repo, tags, {})
42 if tags:
42 if tags:
43 rev, nodemap = cl.rev, cl.nodemap
43 rev, nodemap = cl.rev, cl.nodemap
44 blockers.update(rev(t[0]) for t in tags.values() if t[0] in nodemap)
44 blockers.update(rev(t[0]) for t in tags.values() if t[0] in nodemap)
45 return blockers
45 return blockers
46
46
47 def _consistencyblocker(pfunc, hideable, domain):
47 def _consistencyblocker(pfunc, hideable, domain):
48 """return non-hideable changeset blocking hideable one
48 """return non-hideable changeset blocking hideable one
49
49
50 For consistency, we cannot actually hide a changeset if one of it children
50 For consistency, we cannot actually hide a changeset if one of it children
51 are visible, this function find such children.
51 are visible, this function find such children.
52 """
52 """
53 others = domain - hideable
53 others = domain - hideable
54 blockers = set()
54 blockers = set()
55 for r in others:
55 for r in others:
56 for p in pfunc(r):
56 for p in pfunc(r):
57 if p != nullrev and p in hideable:
57 if p != nullrev and p in hideable:
58 blockers.add(r)
58 blockers.add(r)
59 break
59 break
60 return blockers
60 return blockers
61
61
62 def _domainancestors(pfunc, revs, domain):
62 def _domainancestors(pfunc, revs, domain):
63 """return ancestors of 'revs' within 'domain'
63 """return ancestors of 'revs' within 'domain'
64
64
65 - pfunc(r): a funtion returning parent of 'r',
65 - pfunc(r): a funtion returning parent of 'r',
66 - revs: iterable of revnum,
66 - revs: iterable of revnum,
67 - domain: consistent set of revnum.
67 - domain: consistent set of revnum.
68
68
69 The domain must be consistent: no connected subset are the ancestors of
69 The domain must be consistent: no connected subset are the ancestors of
70 another connected subset. In other words, if the parents of a revision are
70 another connected subset. In other words, if the parents of a revision are
71 not in the domains, no other ancestors of that revision. For example, with
71 not in the domains, no other ancestors of that revision. For example, with
72 the following graph:
72 the following graph:
73
73
74 F
74 F
75 |
75 |
76 E
76 E
77 | D
77 | D
78 | |
78 | |
79 | C
79 | C
80 |/
80 |/
81 B
81 B
82 |
82 |
83 A
83 A
84
84
85 If C, D, E and F are in the domain but B is not, A cannot be ((A) is an
85 If C, D, E and F are in the domain but B is not, A cannot be ((A) is an
86 ancestors disconnected subset disconnected of (C+D)).
86 ancestors disconnected subset disconnected of (C+D)).
87
87
88 (Ancestors are returned inclusively)
88 (Ancestors are returned inclusively)
89 """
89 """
90 stack = list(revs)
90 stack = list(revs)
91 ancestors = set(stack)
91 ancestors = set(stack)
92 while stack:
92 while stack:
93 for p in pfunc(stack.pop()):
93 for p in pfunc(stack.pop()):
94 if p != nullrev and p in domain and p not in ancestors:
94 if p != nullrev and p in domain and p not in ancestors:
95 ancestors.add(p)
95 ancestors.add(p)
96 stack.append(p)
96 stack.append(p)
97 return ancestors
97 return ancestors
98
98
99 def computehidden(repo):
99 def computehidden(repo):
100 """compute the set of hidden revision to filter
100 """compute the set of hidden revision to filter
101
101
102 During most operation hidden should be filtered."""
102 During most operation hidden should be filtered."""
103 assert not repo.changelog.filteredrevs
103 assert not repo.changelog.filteredrevs
104
104
105 hidden = hideablerevs(repo)
105 hidden = hideablerevs(repo)
106 if hidden:
106 if hidden:
107 pfunc = repo.changelog.parentrevs
107 pfunc = repo.changelog.parentrevs
108 mutablephases = (phases.draft, phases.secret)
108 mutablephases = (phases.draft, phases.secret)
109 mutable = repo._phasecache.getrevset(repo, mutablephases)
109 mutable = repo._phasecache.getrevset(repo, mutablephases)
110
110
111 blockers = _consistencyblocker(pfunc, hidden, mutable)
111 blockers = _consistencyblocker(pfunc, hidden, mutable)
112
112
113 # check if we have wd parents, bookmarks or tags pointing to hidden
113 # check if we have wd parents, bookmarks or tags pointing to hidden
114 # changesets and remove those.
114 # changesets and remove those.
115 blockers |= (hidden & revealedrevs(repo))
115 blockers |= (hidden & revealedrevs(repo))
116 if blockers:
116 if blockers:
117 hidden = hidden - _domainancestors(pfunc, blockers, mutable)
117 hidden = hidden - _domainancestors(pfunc, blockers, mutable)
118 return frozenset(hidden)
118 return frozenset(hidden)
119
119
120 def computeunserved(repo):
120 def computeunserved(repo):
121 """compute the set of revision that should be filtered when used a server
121 """compute the set of revision that should be filtered when used a server
122
122
123 Secret and hidden changeset should not pretend to be here."""
123 Secret and hidden changeset should not pretend to be here."""
124 assert not repo.changelog.filteredrevs
124 assert not repo.changelog.filteredrevs
125 # fast path in simple case to avoid impact of non optimised code
125 # fast path in simple case to avoid impact of non optimised code
126 hiddens = filterrevs(repo, 'visible')
126 hiddens = filterrevs(repo, 'visible')
127 if phases.hassecret(repo):
127 if phases.hassecret(repo):
128 cl = repo.changelog
128 cl = repo.changelog
129 secret = phases.secret
129 secret = phases.secret
130 getphase = repo._phasecache.phase
130 getphase = repo._phasecache.phase
131 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
131 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
132 revs = cl.revs(start=first)
132 revs = cl.revs(start=first)
133 secrets = set(r for r in revs if getphase(repo, r) >= secret)
133 secrets = set(r for r in revs if getphase(repo, r) >= secret)
134 return frozenset(hiddens | secrets)
134 return frozenset(hiddens | secrets)
135 else:
135 else:
136 return hiddens
136 return hiddens
137
137
138 def computemutable(repo):
138 def computemutable(repo):
139 """compute the set of revision that should be filtered when used a server
139 """compute the set of revision that should be filtered when used a server
140
140
141 Secret and hidden changeset should not pretend to be here."""
141 Secret and hidden changeset should not pretend to be here."""
142 assert not repo.changelog.filteredrevs
142 assert not repo.changelog.filteredrevs
143 # fast check to avoid revset call on huge repo
143 # fast check to avoid revset call on huge repo
144 if any(repo._phasecache.phaseroots[1:]):
144 if any(repo._phasecache.phaseroots[1:]):
145 getphase = repo._phasecache.phase
145 getphase = repo._phasecache.phase
146 maymutable = filterrevs(repo, 'base')
146 maymutable = filterrevs(repo, 'base')
147 return frozenset(r for r in maymutable if getphase(repo, r))
147 return frozenset(r for r in maymutable if getphase(repo, r))
148 return frozenset()
148 return frozenset()
149
149
150 def computeimpactable(repo):
150 def computeimpactable(repo):
151 """Everything impactable by mutable revision
151 """Everything impactable by mutable revision
152
152
153 The immutable filter still have some chance to get invalidated. This will
153 The immutable filter still have some chance to get invalidated. This will
154 happen when:
154 happen when:
155
155
156 - you garbage collect hidden changeset,
156 - you garbage collect hidden changeset,
157 - public phase is moved backward,
157 - public phase is moved backward,
158 - something is changed in the filtering (this could be fixed)
158 - something is changed in the filtering (this could be fixed)
159
159
160 This filter out any mutable changeset and any public changeset that may be
160 This filter out any mutable changeset and any public changeset that may be
161 impacted by something happening to a mutable revision.
161 impacted by something happening to a mutable revision.
162
162
163 This is achieved by filtered everything with a revision number egal or
163 This is achieved by filtered everything with a revision number egal or
164 higher than the first mutable changeset is filtered."""
164 higher than the first mutable changeset is filtered."""
165 assert not repo.changelog.filteredrevs
165 assert not repo.changelog.filteredrevs
166 cl = repo.changelog
166 cl = repo.changelog
167 firstmutable = len(cl)
167 firstmutable = len(cl)
168 for roots in repo._phasecache.phaseroots[1:]:
168 for roots in repo._phasecache.phaseroots[1:]:
169 if roots:
169 if roots:
170 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
170 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
171 # protect from nullrev root
171 # protect from nullrev root
172 firstmutable = max(0, firstmutable)
172 firstmutable = max(0, firstmutable)
173 return frozenset(xrange(firstmutable, len(cl)))
173 return frozenset(xrange(firstmutable, len(cl)))
174
174
175 # function to compute filtered set
175 # function to compute filtered set
176 #
176 #
177 # When adding a new filter you MUST update the table at:
177 # When adding a new filter you MUST update the table at:
178 # mercurial.branchmap.subsettable
178 # mercurial.branchmap.subsettable
179 # Otherwise your filter will have to recompute all its branches cache
179 # Otherwise your filter will have to recompute all its branches cache
180 # from scratch (very slow).
180 # from scratch (very slow).
181 filtertable = {'visible': computehidden,
181 filtertable = {'visible': computehidden,
182 'served': computeunserved,
182 'served': computeunserved,
183 'immutable': computemutable,
183 'immutable': computemutable,
184 'base': computeimpactable}
184 'base': computeimpactable}
185
185
186 def filterrevs(repo, filtername):
186 def filterrevs(repo, filtername):
187 """returns set of filtered revision for this filter name"""
187 """returns set of filtered revision for this filter name"""
188 if filtername not in repo.filteredrevcache:
188 if filtername not in repo.filteredrevcache:
189 func = filtertable[filtername]
189 func = filtertable[filtername]
190 repo.filteredrevcache[filtername] = func(repo.unfiltered())
190 repo.filteredrevcache[filtername] = func(repo.unfiltered())
191 return repo.filteredrevcache[filtername]
191 return repo.filteredrevcache[filtername]
192
192
193 class repoview(object):
193 class repoview(object):
194 """Provide a read/write view of a repo through a filtered changelog
194 """Provide a read/write view of a repo through a filtered changelog
195
195
196 This object is used to access a filtered version of a repository without
196 This object is used to access a filtered version of a repository without
197 altering the original repository object itself. We can not alter the
197 altering the original repository object itself. We can not alter the
198 original object for two main reasons:
198 original object for two main reasons:
199 - It prevents the use of a repo with multiple filters at the same time. In
199 - It prevents the use of a repo with multiple filters at the same time. In
200 particular when multiple threads are involved.
200 particular when multiple threads are involved.
201 - It makes scope of the filtering harder to control.
201 - It makes scope of the filtering harder to control.
202
202
203 This object behaves very closely to the original repository. All attribute
203 This object behaves very closely to the original repository. All attribute
204 operations are done on the original repository:
204 operations are done on the original repository:
205 - An access to `repoview.someattr` actually returns `repo.someattr`,
205 - An access to `repoview.someattr` actually returns `repo.someattr`,
206 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
206 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
207 - A deletion of `repoview.someattr` actually drops `someattr`
207 - A deletion of `repoview.someattr` actually drops `someattr`
208 from `repo.__dict__`.
208 from `repo.__dict__`.
209
209
210 The only exception is the `changelog` property. It is overridden to return
210 The only exception is the `changelog` property. It is overridden to return
211 a (surface) copy of `repo.changelog` with some revisions filtered. The
211 a (surface) copy of `repo.changelog` with some revisions filtered. The
212 `filtername` attribute of the view control the revisions that need to be
212 `filtername` attribute of the view control the revisions that need to be
213 filtered. (the fact the changelog is copied is an implementation detail).
213 filtered. (the fact the changelog is copied is an implementation detail).
214
214
215 Unlike attributes, this object intercepts all method calls. This means that
215 Unlike attributes, this object intercepts all method calls. This means that
216 all methods are run on the `repoview` object with the filtered `changelog`
216 all methods are run on the `repoview` object with the filtered `changelog`
217 property. For this purpose the simple `repoview` class must be mixed with
217 property. For this purpose the simple `repoview` class must be mixed with
218 the actual class of the repository. This ensures that the resulting
218 the actual class of the repository. This ensures that the resulting
219 `repoview` object have the very same methods than the repo object. This
219 `repoview` object have the very same methods than the repo object. This
220 leads to the property below.
220 leads to the property below.
221
221
222 repoview.method() --> repo.__class__.method(repoview)
222 repoview.method() --> repo.__class__.method(repoview)
223
223
224 The inheritance has to be done dynamically because `repo` can be of any
224 The inheritance has to be done dynamically because `repo` can be of any
225 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
225 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
226 """
226 """
227
227
228 def __init__(self, repo, filtername):
228 def __init__(self, repo, filtername):
229 object.__setattr__(self, r'_unfilteredrepo', repo)
229 object.__setattr__(self, r'_unfilteredrepo', repo)
230 object.__setattr__(self, r'filtername', filtername)
230 object.__setattr__(self, r'filtername', filtername)
231 object.__setattr__(self, r'_clcachekey', None)
231 object.__setattr__(self, r'_clcachekey', None)
232 object.__setattr__(self, r'_clcache', None)
232 object.__setattr__(self, r'_clcache', None)
233
233
234 # not a propertycache on purpose we shall implement a proper cache later
234 # not a propertycache on purpose we shall implement a proper cache later
235 @property
235 @property
236 def changelog(self):
236 def changelog(self):
237 """return a filtered version of the changeset
237 """return a filtered version of the changeset
238
238
239 this changelog must not be used for writing"""
239 this changelog must not be used for writing"""
240 # some cache may be implemented later
240 # some cache may be implemented later
241 unfi = self._unfilteredrepo
241 unfi = self._unfilteredrepo
242 unfichangelog = unfi.changelog
242 unfichangelog = unfi.changelog
243 # bypass call to changelog.method
243 # bypass call to changelog.method
244 unfiindex = unfichangelog.index
244 unfiindex = unfichangelog.index
245 unfilen = len(unfiindex) - 1
245 unfilen = len(unfiindex) - 1
246 unfinode = unfiindex[unfilen - 1][7]
246 unfinode = unfiindex[unfilen - 1][7]
247
247
248 revs = filterrevs(unfi, self.filtername)
248 revs = filterrevs(unfi, self.filtername)
249 cl = self._clcache
249 cl = self._clcache
250 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
250 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
251 # if cl.index is not unfiindex, unfi.changelog would be
251 # if cl.index is not unfiindex, unfi.changelog would be
252 # recreated, and our clcache refers to garbage object
252 # recreated, and our clcache refers to garbage object
253 if (cl is not None and
253 if (cl is not None and
254 (cl.index is not unfiindex or newkey != self._clcachekey)):
254 (cl.index is not unfiindex or newkey != self._clcachekey)):
255 cl = None
255 cl = None
256 # could have been made None by the previous if
256 # could have been made None by the previous if
257 if cl is None:
257 if cl is None:
258 cl = copy.copy(unfichangelog)
258 cl = copy.copy(unfichangelog)
259 cl.filteredrevs = revs
259 cl.filteredrevs = revs
260 object.__setattr__(self, r'_clcache', cl)
260 object.__setattr__(self, r'_clcache', cl)
261 object.__setattr__(self, r'_clcachekey', newkey)
261 object.__setattr__(self, r'_clcachekey', newkey)
262 return cl
262 return cl
263
263
264 def unfiltered(self):
264 def unfiltered(self):
265 """Return an unfiltered version of a repo"""
265 """Return an unfiltered version of a repo"""
266 return self._unfilteredrepo
266 return self._unfilteredrepo
267
267
268 def filtered(self, name):
268 def filtered(self, name):
269 """Return a filtered version of a repository"""
269 """Return a filtered version of a repository"""
270 if name == self.filtername:
270 if name == self.filtername:
271 return self
271 return self
272 return self.unfiltered().filtered(name)
272 return self.unfiltered().filtered(name)
273
273
274 # everything access are forwarded to the proxied repo
274 # everything access are forwarded to the proxied repo
275 def __getattr__(self, attr):
275 def __getattr__(self, attr):
276 return getattr(self._unfilteredrepo, attr)
276 return getattr(self._unfilteredrepo, attr)
277
277
278 def __setattr__(self, attr, value):
278 def __setattr__(self, attr, value):
279 return setattr(self._unfilteredrepo, attr, value)
279 return setattr(self._unfilteredrepo, attr, value)
280
280
281 def __delattr__(self, attr):
281 def __delattr__(self, attr):
282 return delattr(self._unfilteredrepo, attr)
282 return delattr(self._unfilteredrepo, attr)
283
283
284 # The `requirements` attribute is initialized during __init__. But
284 # The `requirements` attribute is initialized during __init__. But
285 # __getattr__ won't be called as it also exists on the class. We need
285 # __getattr__ won't be called as it also exists on the class. We need
286 # explicit forwarding to main repo here
286 # explicit forwarding to main repo here
287 @property
287 @property
288 def requirements(self):
288 def requirements(self):
289 return self._unfilteredrepo.requirements
289 return self._unfilteredrepo.requirements
General Comments 0
You need to be logged in to leave comments. Login now