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