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