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