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