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