##// END OF EJS Templates
repoview: split _gethiddenblockers...
David Soria Parra -
r22149:16ef2c48 default
parent child Browse files
Show More
@@ -1,231 +1,250 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 import copy
9 import copy
10 import phases
10 import phases
11 import util
11 import util
12 import obsolete
12 import obsolete
13 import tags as tagsmod
13 import tags as tagsmod
14
14
15
15
16 def hideablerevs(repo):
16 def hideablerevs(repo):
17 """Revisions candidates to be hidden
17 """Revisions candidates to be hidden
18
18
19 This is a standalone function to help extensions to wrap it."""
19 This is a standalone function to help extensions to wrap it."""
20 return obsolete.getrevs(repo, 'obsolete')
20 return obsolete.getrevs(repo, 'obsolete')
21
21
22 def _gethiddenblockers(repo):
22 def _getstaticblockers(repo):
23 """Get revisions that will block hidden changesets from being filtered
23 """Cacheable revisions blocking hidden changesets from being filtered.
24
24
25 Additional non-cached hidden blockers are computed in _getdynamicblockers.
25 This is a standalone function to help extensions to wrap it."""
26 This is a standalone function to help extensions to wrap it."""
26 assert not repo.changelog.filteredrevs
27 assert not repo.changelog.filteredrevs
27 hideable = hideablerevs(repo)
28 hideable = hideablerevs(repo)
28 blockers = []
29 blockers = set()
29 if hideable:
30 if hideable:
30 # We use cl to avoid recursive lookup from repo[xxx]
31 # We use cl to avoid recursive lookup from repo[xxx]
31 cl = repo.changelog
32 cl = repo.changelog
32 firsthideable = min(hideable)
33 firsthideable = min(hideable)
33 revs = cl.revs(start=firsthideable)
34 revs = cl.revs(start=firsthideable)
34 tofilter = repo.revs(
35 tofilter = repo.revs(
35 '(%ld) and children(%ld)', list(revs), list(hideable))
36 '(%ld) and children(%ld)', list(revs), list(hideable))
36 blockers = set([r for r in tofilter if r not in hideable])
37 blockers.update([r for r in tofilter if r not in hideable])
37 for par in repo[None].parents():
38 return blockers
38 blockers.add(par.rev())
39
39 for bm in repo._bookmarks.values():
40 def _getdynamicblockers(repo):
40 blockers.add(cl.rev(bm))
41 """Non-cacheable revisions blocking hidden changesets from being filtered.
41 tags = {}
42
42 tagsmod.readlocaltags(repo.ui, repo, tags, {})
43 Get revisions that will block hidden changesets and are likely to change,
43 if tags:
44 but unlikely to create hidden blockers. They won't be cached, so be careful
44 rev, nodemap = cl.rev, cl.nodemap
45 with adding additional computation."""
45 blockers.update(rev(t[0]) for t in tags.values() if t[0] in nodemap)
46
47 cl = repo.changelog
48 blockers = set()
49 blockers.update([par.rev() for par in repo[None].parents()])
50 blockers.update([cl.rev(bm) for bm in repo._bookmarks.values()])
51
52 tags = {}
53 tagsmod.readlocaltags(repo.ui, repo, tags, {})
54 if tags:
55 rev, nodemap = cl.rev, cl.nodemap
56 blockers.update(rev(t[0]) for t in tags.values() if t[0] in nodemap)
46 return blockers
57 return blockers
47
58
48 def computehidden(repo):
59 def computehidden(repo):
49 """compute the set of hidden revision to filter
60 """compute the set of hidden revision to filter
50
61
51 During most operation hidden should be filtered."""
62 During most operation hidden should be filtered."""
52 assert not repo.changelog.filteredrevs
63 assert not repo.changelog.filteredrevs
64 hidden = frozenset()
53 hideable = hideablerevs(repo)
65 hideable = hideablerevs(repo)
54 if hideable:
66 if hideable:
55 cl = repo.changelog
67 cl = repo.changelog
56 blocked = cl.ancestors(_gethiddenblockers(repo), inclusive=True)
68 blocked = cl.ancestors(_getstaticblockers(repo), inclusive=True)
57 return frozenset(r for r in hideable if r not in blocked)
69 hidden = frozenset(r for r in hideable if r not in blocked)
58 return frozenset()
70
71 # check if we have wd parents, bookmarks or tags pointing to hidden
72 # changesets and remove those.
73 dynamic = hidden & _getdynamicblockers(repo)
74 if dynamic:
75 blocked = cl.ancestors(dynamic, inclusive=True)
76 hidden = frozenset(r for r in hidden if r not in blocked)
77 return hidden
59
78
60 def computeunserved(repo):
79 def computeunserved(repo):
61 """compute the set of revision that should be filtered when used a server
80 """compute the set of revision that should be filtered when used a server
62
81
63 Secret and hidden changeset should not pretend to be here."""
82 Secret and hidden changeset should not pretend to be here."""
64 assert not repo.changelog.filteredrevs
83 assert not repo.changelog.filteredrevs
65 # fast path in simple case to avoid impact of non optimised code
84 # fast path in simple case to avoid impact of non optimised code
66 hiddens = filterrevs(repo, 'visible')
85 hiddens = filterrevs(repo, 'visible')
67 if phases.hassecret(repo):
86 if phases.hassecret(repo):
68 cl = repo.changelog
87 cl = repo.changelog
69 secret = phases.secret
88 secret = phases.secret
70 getphase = repo._phasecache.phase
89 getphase = repo._phasecache.phase
71 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
90 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
72 revs = cl.revs(start=first)
91 revs = cl.revs(start=first)
73 secrets = set(r for r in revs if getphase(repo, r) >= secret)
92 secrets = set(r for r in revs if getphase(repo, r) >= secret)
74 return frozenset(hiddens | secrets)
93 return frozenset(hiddens | secrets)
75 else:
94 else:
76 return hiddens
95 return hiddens
77
96
78 def computemutable(repo):
97 def computemutable(repo):
79 """compute the set of revision that should be filtered when used a server
98 """compute the set of revision that should be filtered when used a server
80
99
81 Secret and hidden changeset should not pretend to be here."""
100 Secret and hidden changeset should not pretend to be here."""
82 assert not repo.changelog.filteredrevs
101 assert not repo.changelog.filteredrevs
83 # fast check to avoid revset call on huge repo
102 # fast check to avoid revset call on huge repo
84 if util.any(repo._phasecache.phaseroots[1:]):
103 if util.any(repo._phasecache.phaseroots[1:]):
85 getphase = repo._phasecache.phase
104 getphase = repo._phasecache.phase
86 maymutable = filterrevs(repo, 'base')
105 maymutable = filterrevs(repo, 'base')
87 return frozenset(r for r in maymutable if getphase(repo, r))
106 return frozenset(r for r in maymutable if getphase(repo, r))
88 return frozenset()
107 return frozenset()
89
108
90 def computeimpactable(repo):
109 def computeimpactable(repo):
91 """Everything impactable by mutable revision
110 """Everything impactable by mutable revision
92
111
93 The immutable filter still have some chance to get invalidated. This will
112 The immutable filter still have some chance to get invalidated. This will
94 happen when:
113 happen when:
95
114
96 - you garbage collect hidden changeset,
115 - you garbage collect hidden changeset,
97 - public phase is moved backward,
116 - public phase is moved backward,
98 - something is changed in the filtering (this could be fixed)
117 - something is changed in the filtering (this could be fixed)
99
118
100 This filter out any mutable changeset and any public changeset that may be
119 This filter out any mutable changeset and any public changeset that may be
101 impacted by something happening to a mutable revision.
120 impacted by something happening to a mutable revision.
102
121
103 This is achieved by filtered everything with a revision number egal or
122 This is achieved by filtered everything with a revision number egal or
104 higher than the first mutable changeset is filtered."""
123 higher than the first mutable changeset is filtered."""
105 assert not repo.changelog.filteredrevs
124 assert not repo.changelog.filteredrevs
106 cl = repo.changelog
125 cl = repo.changelog
107 firstmutable = len(cl)
126 firstmutable = len(cl)
108 for roots in repo._phasecache.phaseroots[1:]:
127 for roots in repo._phasecache.phaseroots[1:]:
109 if roots:
128 if roots:
110 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
129 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
111 # protect from nullrev root
130 # protect from nullrev root
112 firstmutable = max(0, firstmutable)
131 firstmutable = max(0, firstmutable)
113 return frozenset(xrange(firstmutable, len(cl)))
132 return frozenset(xrange(firstmutable, len(cl)))
114
133
115 # function to compute filtered set
134 # function to compute filtered set
116 #
135 #
117 # When adding a new filter you MUST update the table at:
136 # When adding a new filter you MUST update the table at:
118 # mercurial.branchmap.subsettable
137 # mercurial.branchmap.subsettable
119 # Otherwise your filter will have to recompute all its branches cache
138 # Otherwise your filter will have to recompute all its branches cache
120 # from scratch (very slow).
139 # from scratch (very slow).
121 filtertable = {'visible': computehidden,
140 filtertable = {'visible': computehidden,
122 'served': computeunserved,
141 'served': computeunserved,
123 'immutable': computemutable,
142 'immutable': computemutable,
124 'base': computeimpactable}
143 'base': computeimpactable}
125
144
126 def filterrevs(repo, filtername):
145 def filterrevs(repo, filtername):
127 """returns set of filtered revision for this filter name"""
146 """returns set of filtered revision for this filter name"""
128 if filtername not in repo.filteredrevcache:
147 if filtername not in repo.filteredrevcache:
129 func = filtertable[filtername]
148 func = filtertable[filtername]
130 repo.filteredrevcache[filtername] = func(repo.unfiltered())
149 repo.filteredrevcache[filtername] = func(repo.unfiltered())
131 return repo.filteredrevcache[filtername]
150 return repo.filteredrevcache[filtername]
132
151
133 class repoview(object):
152 class repoview(object):
134 """Provide a read/write view of a repo through a filtered changelog
153 """Provide a read/write view of a repo through a filtered changelog
135
154
136 This object is used to access a filtered version of a repository without
155 This object is used to access a filtered version of a repository without
137 altering the original repository object itself. We can not alter the
156 altering the original repository object itself. We can not alter the
138 original object for two main reasons:
157 original object for two main reasons:
139 - It prevents the use of a repo with multiple filters at the same time. In
158 - It prevents the use of a repo with multiple filters at the same time. In
140 particular when multiple threads are involved.
159 particular when multiple threads are involved.
141 - It makes scope of the filtering harder to control.
160 - It makes scope of the filtering harder to control.
142
161
143 This object behaves very closely to the original repository. All attribute
162 This object behaves very closely to the original repository. All attribute
144 operations are done on the original repository:
163 operations are done on the original repository:
145 - An access to `repoview.someattr` actually returns `repo.someattr`,
164 - An access to `repoview.someattr` actually returns `repo.someattr`,
146 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
165 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
147 - A deletion of `repoview.someattr` actually drops `someattr`
166 - A deletion of `repoview.someattr` actually drops `someattr`
148 from `repo.__dict__`.
167 from `repo.__dict__`.
149
168
150 The only exception is the `changelog` property. It is overridden to return
169 The only exception is the `changelog` property. It is overridden to return
151 a (surface) copy of `repo.changelog` with some revisions filtered. The
170 a (surface) copy of `repo.changelog` with some revisions filtered. The
152 `filtername` attribute of the view control the revisions that need to be
171 `filtername` attribute of the view control the revisions that need to be
153 filtered. (the fact the changelog is copied is an implementation detail).
172 filtered. (the fact the changelog is copied is an implementation detail).
154
173
155 Unlike attributes, this object intercepts all method calls. This means that
174 Unlike attributes, this object intercepts all method calls. This means that
156 all methods are run on the `repoview` object with the filtered `changelog`
175 all methods are run on the `repoview` object with the filtered `changelog`
157 property. For this purpose the simple `repoview` class must be mixed with
176 property. For this purpose the simple `repoview` class must be mixed with
158 the actual class of the repository. This ensures that the resulting
177 the actual class of the repository. This ensures that the resulting
159 `repoview` object have the very same methods than the repo object. This
178 `repoview` object have the very same methods than the repo object. This
160 leads to the property below.
179 leads to the property below.
161
180
162 repoview.method() --> repo.__class__.method(repoview)
181 repoview.method() --> repo.__class__.method(repoview)
163
182
164 The inheritance has to be done dynamically because `repo` can be of any
183 The inheritance has to be done dynamically because `repo` can be of any
165 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
184 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
166 """
185 """
167
186
168 def __init__(self, repo, filtername):
187 def __init__(self, repo, filtername):
169 object.__setattr__(self, '_unfilteredrepo', repo)
188 object.__setattr__(self, '_unfilteredrepo', repo)
170 object.__setattr__(self, 'filtername', filtername)
189 object.__setattr__(self, 'filtername', filtername)
171 object.__setattr__(self, '_clcachekey', None)
190 object.__setattr__(self, '_clcachekey', None)
172 object.__setattr__(self, '_clcache', None)
191 object.__setattr__(self, '_clcache', None)
173
192
174 # not a propertycache on purpose we shall implement a proper cache later
193 # not a propertycache on purpose we shall implement a proper cache later
175 @property
194 @property
176 def changelog(self):
195 def changelog(self):
177 """return a filtered version of the changeset
196 """return a filtered version of the changeset
178
197
179 this changelog must not be used for writing"""
198 this changelog must not be used for writing"""
180 # some cache may be implemented later
199 # some cache may be implemented later
181 unfi = self._unfilteredrepo
200 unfi = self._unfilteredrepo
182 unfichangelog = unfi.changelog
201 unfichangelog = unfi.changelog
183 revs = filterrevs(unfi, self.filtername)
202 revs = filterrevs(unfi, self.filtername)
184 cl = self._clcache
203 cl = self._clcache
185 newkey = (len(unfichangelog), unfichangelog.tip(), hash(revs))
204 newkey = (len(unfichangelog), unfichangelog.tip(), hash(revs))
186 if cl is not None:
205 if cl is not None:
187 # we need to check curkey too for some obscure reason.
206 # we need to check curkey too for some obscure reason.
188 # MQ test show a corruption of the underlying repo (in _clcache)
207 # MQ test show a corruption of the underlying repo (in _clcache)
189 # without change in the cachekey.
208 # without change in the cachekey.
190 oldfilter = cl.filteredrevs
209 oldfilter = cl.filteredrevs
191 try:
210 try:
192 cl.filterrevs = () # disable filtering for tip
211 cl.filterrevs = () # disable filtering for tip
193 curkey = (len(cl), cl.tip(), hash(oldfilter))
212 curkey = (len(cl), cl.tip(), hash(oldfilter))
194 finally:
213 finally:
195 cl.filteredrevs = oldfilter
214 cl.filteredrevs = oldfilter
196 if newkey != self._clcachekey or newkey != curkey:
215 if newkey != self._clcachekey or newkey != curkey:
197 cl = None
216 cl = None
198 # could have been made None by the previous if
217 # could have been made None by the previous if
199 if cl is None:
218 if cl is None:
200 cl = copy.copy(unfichangelog)
219 cl = copy.copy(unfichangelog)
201 cl.filteredrevs = revs
220 cl.filteredrevs = revs
202 object.__setattr__(self, '_clcache', cl)
221 object.__setattr__(self, '_clcache', cl)
203 object.__setattr__(self, '_clcachekey', newkey)
222 object.__setattr__(self, '_clcachekey', newkey)
204 return cl
223 return cl
205
224
206 def unfiltered(self):
225 def unfiltered(self):
207 """Return an unfiltered version of a repo"""
226 """Return an unfiltered version of a repo"""
208 return self._unfilteredrepo
227 return self._unfilteredrepo
209
228
210 def filtered(self, name):
229 def filtered(self, name):
211 """Return a filtered version of a repository"""
230 """Return a filtered version of a repository"""
212 if name == self.filtername:
231 if name == self.filtername:
213 return self
232 return self
214 return self.unfiltered().filtered(name)
233 return self.unfiltered().filtered(name)
215
234
216 # everything access are forwarded to the proxied repo
235 # everything access are forwarded to the proxied repo
217 def __getattr__(self, attr):
236 def __getattr__(self, attr):
218 return getattr(self._unfilteredrepo, attr)
237 return getattr(self._unfilteredrepo, attr)
219
238
220 def __setattr__(self, attr, value):
239 def __setattr__(self, attr, value):
221 return setattr(self._unfilteredrepo, attr, value)
240 return setattr(self._unfilteredrepo, attr, value)
222
241
223 def __delattr__(self, attr):
242 def __delattr__(self, attr):
224 return delattr(self._unfilteredrepo, attr)
243 return delattr(self._unfilteredrepo, attr)
225
244
226 # The `requirements` attribute is initialized during __init__. But
245 # The `requirements` attribute is initialized during __init__. But
227 # __getattr__ won't be called as it also exists on the class. We need
246 # __getattr__ won't be called as it also exists on the class. We need
228 # explicit forwarding to main repo here
247 # explicit forwarding to main repo here
229 @property
248 @property
230 def requirements(self):
249 def requirements(self):
231 return self._unfilteredrepo.requirements
250 return self._unfilteredrepo.requirements
General Comments 0
You need to be logged in to leave comments. Login now