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