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