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