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