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