34 assertions and lead to crashes.""" |
33 assertions and lead to crashes.""" |
35 obsoletes = obsolete.getrevs(repo, 'obsolete') |
34 obsoletes = obsolete.getrevs(repo, 'obsolete') |
36 internals = repo._phasecache.getrevset(repo, phases.localhiddenphases) |
35 internals = repo._phasecache.getrevset(repo, phases.localhiddenphases) |
37 internals = frozenset(internals) |
36 internals = frozenset(internals) |
38 return obsoletes | internals |
37 return obsoletes | internals |
|
38 |
39 |
39 |
40 def pinnedrevs(repo): |
40 def pinnedrevs(repo): |
41 """revisions blocking hidden changesets from being filtered |
41 """revisions blocking hidden changesets from being filtered |
42 """ |
42 """ |
43 |
43 |
70 for p in pfunc(stack.pop()): |
70 for p in pfunc(stack.pop()): |
71 if p != nullrev and p in hidden: |
71 if p != nullrev and p in hidden: |
72 hidden.remove(p) |
72 hidden.remove(p) |
73 stack.append(p) |
73 stack.append(p) |
74 |
74 |
|
75 |
75 def computehidden(repo, visibilityexceptions=None): |
76 def computehidden(repo, visibilityexceptions=None): |
76 """compute the set of hidden revision to filter |
77 """compute the set of hidden revision to filter |
77 |
78 |
78 During most operation hidden should be filtered.""" |
79 During most operation hidden should be filtered.""" |
79 assert not repo.changelog.filteredrevs |
80 assert not repo.changelog.filteredrevs |
88 |
89 |
89 visible = mutable - hidden |
90 visible = mutable - hidden |
90 _revealancestors(pfunc, hidden, visible) |
91 _revealancestors(pfunc, hidden, visible) |
91 return frozenset(hidden) |
92 return frozenset(hidden) |
92 |
93 |
|
94 |
93 def computesecret(repo, visibilityexceptions=None): |
95 def computesecret(repo, visibilityexceptions=None): |
94 """compute the set of revision that can never be exposed through hgweb |
96 """compute the set of revision that can never be exposed through hgweb |
95 |
97 |
96 Changeset in the secret phase (or above) should stay unaccessible.""" |
98 Changeset in the secret phase (or above) should stay unaccessible.""" |
97 assert not repo.changelog.filteredrevs |
99 assert not repo.changelog.filteredrevs |
98 secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases) |
100 secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases) |
99 return frozenset(secrets) |
101 return frozenset(secrets) |
|
102 |
100 |
103 |
101 def computeunserved(repo, visibilityexceptions=None): |
104 def computeunserved(repo, visibilityexceptions=None): |
102 """compute the set of revision that should be filtered when used a server |
105 """compute the set of revision that should be filtered when used a server |
103 |
106 |
104 Secret and hidden changeset should not pretend to be here.""" |
107 Secret and hidden changeset should not pretend to be here.""" |
109 if secrets: |
112 if secrets: |
110 return frozenset(hiddens | secrets) |
113 return frozenset(hiddens | secrets) |
111 else: |
114 else: |
112 return hiddens |
115 return hiddens |
113 |
116 |
|
117 |
114 def computemutable(repo, visibilityexceptions=None): |
118 def computemutable(repo, visibilityexceptions=None): |
115 assert not repo.changelog.filteredrevs |
119 assert not repo.changelog.filteredrevs |
116 # fast check to avoid revset call on huge repo |
120 # fast check to avoid revset call on huge repo |
117 if any(repo._phasecache.phaseroots[1:]): |
121 if any(repo._phasecache.phaseroots[1:]): |
118 getphase = repo._phasecache.phase |
122 getphase = repo._phasecache.phase |
119 maymutable = filterrevs(repo, 'base') |
123 maymutable = filterrevs(repo, 'base') |
120 return frozenset(r for r in maymutable if getphase(repo, r)) |
124 return frozenset(r for r in maymutable if getphase(repo, r)) |
121 return frozenset() |
125 return frozenset() |
|
126 |
122 |
127 |
123 def computeimpactable(repo, visibilityexceptions=None): |
128 def computeimpactable(repo, visibilityexceptions=None): |
124 """Everything impactable by mutable revision |
129 """Everything impactable by mutable revision |
125 |
130 |
126 The immutable filter still have some chance to get invalidated. This will |
131 The immutable filter still have some chance to get invalidated. This will |
143 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots)) |
148 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots)) |
144 # protect from nullrev root |
149 # protect from nullrev root |
145 firstmutable = max(0, firstmutable) |
150 firstmutable = max(0, firstmutable) |
146 return frozenset(pycompat.xrange(firstmutable, len(cl))) |
151 return frozenset(pycompat.xrange(firstmutable, len(cl))) |
147 |
152 |
|
153 |
148 # function to compute filtered set |
154 # function to compute filtered set |
149 # |
155 # |
150 # When adding a new filter you MUST update the table at: |
156 # When adding a new filter you MUST update the table at: |
151 # mercurial.utils.repoviewutil.subsettable |
157 # mercurial.utils.repoviewutil.subsettable |
152 # Otherwise your filter will have to recompute all its branches cache |
158 # Otherwise your filter will have to recompute all its branches cache |
153 # from scratch (very slow). |
159 # from scratch (very slow). |
154 filtertable = {'visible': computehidden, |
160 filtertable = { |
155 'visible-hidden': computehidden, |
161 'visible': computehidden, |
156 'served.hidden': computesecret, |
162 'visible-hidden': computehidden, |
157 'served': computeunserved, |
163 'served.hidden': computesecret, |
158 'immutable': computemutable, |
164 'served': computeunserved, |
159 'base': computeimpactable} |
165 'immutable': computemutable, |
|
166 'base': computeimpactable, |
|
167 } |
160 |
168 |
161 _basefiltername = list(filtertable) |
169 _basefiltername = list(filtertable) |
|
170 |
162 |
171 |
163 def extrafilter(ui): |
172 def extrafilter(ui): |
164 """initialize extra filter and return its id |
173 """initialize extra filter and return its id |
165 |
174 |
166 If extra filtering is configured, we make sure the associated filtered view |
175 If extra filtering is configured, we make sure the associated filtered view |
176 |
185 |
177 subsettable = repoviewutil.subsettable |
186 subsettable = repoviewutil.subsettable |
178 |
187 |
179 if combine('base') not in filtertable: |
188 if combine('base') not in filtertable: |
180 for name in _basefiltername: |
189 for name in _basefiltername: |
|
190 |
181 def extrafilteredrevs(repo, *args, **kwargs): |
191 def extrafilteredrevs(repo, *args, **kwargs): |
182 baserevs = filtertable[name](repo, *args, **kwargs) |
192 baserevs = filtertable[name](repo, *args, **kwargs) |
183 extrarevs = frozenset(repo.revs(frevs)) |
193 extrarevs = frozenset(repo.revs(frevs)) |
184 return baserevs | extrarevs |
194 return baserevs | extrarevs |
|
195 |
185 filtertable[combine(name)] = extrafilteredrevs |
196 filtertable[combine(name)] = extrafilteredrevs |
186 if name in subsettable: |
197 if name in subsettable: |
187 subsettable[combine(name)] = combine(subsettable[name]) |
198 subsettable[combine(name)] = combine(subsettable[name]) |
188 return fid |
199 return fid |
|
200 |
189 |
201 |
190 def filterrevs(repo, filtername, visibilityexceptions=None): |
202 def filterrevs(repo, filtername, visibilityexceptions=None): |
191 """returns set of filtered revision for this filter name |
203 """returns set of filtered revision for this filter name |
192 |
204 |
193 visibilityexceptions is a set of revs which must are exceptions for |
205 visibilityexceptions is a set of revs which must are exceptions for |
198 if visibilityexceptions: |
210 if visibilityexceptions: |
199 return func(repo.unfiltered, visibilityexceptions) |
211 return func(repo.unfiltered, visibilityexceptions) |
200 repo.filteredrevcache[filtername] = func(repo.unfiltered()) |
212 repo.filteredrevcache[filtername] = func(repo.unfiltered()) |
201 return repo.filteredrevcache[filtername] |
213 return repo.filteredrevcache[filtername] |
202 |
214 |
|
215 |
203 class repoview(object): |
216 class repoview(object): |
204 """Provide a read/write view of a repo through a filtered changelog |
217 """Provide a read/write view of a repo through a filtered changelog |
205 |
218 |
206 This object is used to access a filtered version of a repository without |
219 This object is used to access a filtered version of a repository without |
207 altering the original repository object itself. We can not alter the |
220 altering the original repository object itself. We can not alter the |
239 object.__setattr__(self, r'_unfilteredrepo', repo) |
252 object.__setattr__(self, r'_unfilteredrepo', repo) |
240 object.__setattr__(self, r'filtername', filtername) |
253 object.__setattr__(self, r'filtername', filtername) |
241 object.__setattr__(self, r'_clcachekey', None) |
254 object.__setattr__(self, r'_clcachekey', None) |
242 object.__setattr__(self, r'_clcache', None) |
255 object.__setattr__(self, r'_clcache', None) |
243 # revs which are exceptions and must not be hidden |
256 # revs which are exceptions and must not be hidden |
244 object.__setattr__(self, r'_visibilityexceptions', |
257 object.__setattr__(self, r'_visibilityexceptions', visibilityexceptions) |
245 visibilityexceptions) |
|
246 |
258 |
247 # not a propertycache on purpose we shall implement a proper cache later |
259 # not a propertycache on purpose we shall implement a proper cache later |
248 @property |
260 @property |
249 def changelog(self): |
261 def changelog(self): |
250 """return a filtered version of the changeset |
262 """return a filtered version of the changeset |
261 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions) |
273 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions) |
262 cl = self._clcache |
274 cl = self._clcache |
263 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed) |
275 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed) |
264 # if cl.index is not unfiindex, unfi.changelog would be |
276 # if cl.index is not unfiindex, unfi.changelog would be |
265 # recreated, and our clcache refers to garbage object |
277 # recreated, and our clcache refers to garbage object |
266 if (cl is not None and |
278 if cl is not None and ( |
267 (cl.index is not unfiindex or newkey != self._clcachekey)): |
279 cl.index is not unfiindex or newkey != self._clcachekey |
|
280 ): |
268 cl = None |
281 cl = None |
269 # could have been made None by the previous if |
282 # could have been made None by the previous if |
270 if cl is None: |
283 if cl is None: |
271 cl = copy.copy(unfichangelog) |
284 cl = copy.copy(unfichangelog) |
272 cl.filteredrevs = revs |
285 cl.filteredrevs = revs |
283 if name == self.filtername and not visibilityexceptions: |
296 if name == self.filtername and not visibilityexceptions: |
284 return self |
297 return self |
285 return self.unfiltered().filtered(name, visibilityexceptions) |
298 return self.unfiltered().filtered(name, visibilityexceptions) |
286 |
299 |
287 def __repr__(self): |
300 def __repr__(self): |
288 return r'<%s:%s %r>' % (self.__class__.__name__, |
301 return r'<%s:%s %r>' % ( |
289 pycompat.sysstr(self.filtername), |
302 self.__class__.__name__, |
290 self.unfiltered()) |
303 pycompat.sysstr(self.filtername), |
|
304 self.unfiltered(), |
|
305 ) |
291 |
306 |
292 # everything access are forwarded to the proxied repo |
307 # everything access are forwarded to the proxied repo |
293 def __getattr__(self, attr): |
308 def __getattr__(self, attr): |
294 return getattr(self._unfilteredrepo, attr) |
309 return getattr(self._unfilteredrepo, attr) |
295 |
310 |
296 def __setattr__(self, attr, value): |
311 def __setattr__(self, attr, value): |
297 return setattr(self._unfilteredrepo, attr, value) |
312 return setattr(self._unfilteredrepo, attr, value) |
298 |
313 |
299 def __delattr__(self, attr): |
314 def __delattr__(self, attr): |
300 return delattr(self._unfilteredrepo, attr) |
315 return delattr(self._unfilteredrepo, attr) |
|
316 |
301 |
317 |
302 # Python <3.4 easily leaks types via __mro__. See |
318 # Python <3.4 easily leaks types via __mro__. See |
303 # https://bugs.python.org/issue17950. We cache dynamically created types |
319 # https://bugs.python.org/issue17950. We cache dynamically created types |
304 # so they won't be leaked on every invocation of repo.filtered(). |
320 # so they won't be leaked on every invocation of repo.filtered(). |
305 _filteredrepotypes = weakref.WeakKeyDictionary() |
321 _filteredrepotypes = weakref.WeakKeyDictionary() |
306 |
322 |
|
323 |
307 def newtype(base): |
324 def newtype(base): |
308 """Create a new type with the repoview mixin and the given base class""" |
325 """Create a new type with the repoview mixin and the given base class""" |
309 if base not in _filteredrepotypes: |
326 if base not in _filteredrepotypes: |
|
327 |
310 class filteredrepo(repoview, base): |
328 class filteredrepo(repoview, base): |
311 pass |
329 pass |
|
330 |
312 _filteredrepotypes[base] = filteredrepo |
331 _filteredrepotypes[base] = filteredrepo |
313 return _filteredrepotypes[base] |
332 return _filteredrepotypes[base] |