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