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