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