##// END OF EJS Templates
clfilter: use empty frozenset intead of empty tuple...
Pierre-Yves David -
r18231:c0c943ef default
parent child Browse files
Show More
@@ -1,339 +1,339 b''
1 1 # changelog.py - changelog class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import bin, hex, nullid
9 9 from i18n import _
10 10 import util, error, revlog, encoding
11 11
12 12 _defaultextra = {'branch': 'default'}
13 13
14 14 def _string_escape(text):
15 15 """
16 16 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
17 17 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
18 18 >>> s
19 19 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
20 20 >>> res = _string_escape(s)
21 21 >>> s == res.decode('string_escape')
22 22 True
23 23 """
24 24 # subset of the string_escape codec
25 25 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
26 26 return text.replace('\0', '\\0')
27 27
28 28 def decodeextra(text):
29 29 """
30 30 >>> decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'}))
31 31 {'foo': 'bar', 'baz': '\\x002', 'branch': 'default'}
32 32 >>> decodeextra(encodeextra({'foo': 'bar', 'baz': chr(92) + chr(0) + '2'}))
33 33 {'foo': 'bar', 'baz': '\\\\\\x002', 'branch': 'default'}
34 34 """
35 35 extra = _defaultextra.copy()
36 36 for l in text.split('\0'):
37 37 if l:
38 38 if '\\0' in l:
39 39 # fix up \0 without getting into trouble with \\0
40 40 l = l.replace('\\\\', '\\\\\n')
41 41 l = l.replace('\\0', '\0')
42 42 l = l.replace('\n', '')
43 43 k, v = l.decode('string_escape').split(':', 1)
44 44 extra[k] = v
45 45 return extra
46 46
47 47 def encodeextra(d):
48 48 # keys must be sorted to produce a deterministic changelog entry
49 49 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
50 50 return "\0".join(items)
51 51
52 52 def stripdesc(desc):
53 53 """strip trailing whitespace and leading and trailing empty lines"""
54 54 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
55 55
56 56 class appender(object):
57 57 '''the changelog index must be updated last on disk, so we use this class
58 58 to delay writes to it'''
59 59 def __init__(self, fp, buf):
60 60 self.data = buf
61 61 self.fp = fp
62 62 self.offset = fp.tell()
63 63 self.size = util.fstat(fp).st_size
64 64
65 65 def end(self):
66 66 return self.size + len("".join(self.data))
67 67 def tell(self):
68 68 return self.offset
69 69 def flush(self):
70 70 pass
71 71 def close(self):
72 72 self.fp.close()
73 73
74 74 def seek(self, offset, whence=0):
75 75 '''virtual file offset spans real file and data'''
76 76 if whence == 0:
77 77 self.offset = offset
78 78 elif whence == 1:
79 79 self.offset += offset
80 80 elif whence == 2:
81 81 self.offset = self.end() + offset
82 82 if self.offset < self.size:
83 83 self.fp.seek(self.offset)
84 84
85 85 def read(self, count=-1):
86 86 '''only trick here is reads that span real file and data'''
87 87 ret = ""
88 88 if self.offset < self.size:
89 89 s = self.fp.read(count)
90 90 ret = s
91 91 self.offset += len(s)
92 92 if count > 0:
93 93 count -= len(s)
94 94 if count != 0:
95 95 doff = self.offset - self.size
96 96 self.data.insert(0, "".join(self.data))
97 97 del self.data[1:]
98 98 s = self.data[0][doff:doff + count]
99 99 self.offset += len(s)
100 100 ret += s
101 101 return ret
102 102
103 103 def write(self, s):
104 104 self.data.append(str(s))
105 105 self.offset += len(s)
106 106
107 107 def delayopener(opener, target, divert, buf):
108 108 def o(name, mode='r'):
109 109 if name != target:
110 110 return opener(name, mode)
111 111 if divert:
112 112 return opener(name + ".a", mode.replace('a', 'w'))
113 113 # otherwise, divert to memory
114 114 return appender(opener(name, mode), buf)
115 115 return o
116 116
117 117 class changelog(revlog.revlog):
118 118 def __init__(self, opener):
119 119 revlog.revlog.__init__(self, opener, "00changelog.i")
120 120 if self._initempty:
121 121 # changelogs don't benefit from generaldelta
122 122 self.version &= ~revlog.REVLOGGENERALDELTA
123 123 self._generaldelta = False
124 124 self._realopener = opener
125 125 self._delayed = False
126 126 self._divert = False
127 self.filteredrevs = ()
127 self.filteredrevs = frozenset()
128 128
129 129 def tip(self):
130 130 """filtered version of revlog.tip"""
131 131 for i in xrange(len(self) -1, -2, -1):
132 132 if i not in self.filteredrevs:
133 133 return self.node(i)
134 134
135 135 def __iter__(self):
136 136 """filtered version of revlog.__iter__"""
137 137 if len(self.filteredrevs) == 0:
138 138 return revlog.revlog.__iter__(self)
139 139
140 140 def filterediter():
141 141 for i in xrange(len(self)):
142 142 if i not in self.filteredrevs:
143 143 yield i
144 144
145 145 return filterediter()
146 146
147 147 def revs(self, start=0, stop=None):
148 148 """filtered version of revlog.revs"""
149 149 for i in super(changelog, self).revs(start, stop):
150 150 if i not in self.filteredrevs:
151 151 yield i
152 152
153 153 @util.propertycache
154 154 def nodemap(self):
155 155 # XXX need filtering too
156 156 self.rev(self.node(0))
157 157 return self._nodecache
158 158
159 159 def hasnode(self, node):
160 160 """filtered version of revlog.hasnode"""
161 161 try:
162 162 i = self.rev(node)
163 163 return i not in self.filteredrevs
164 164 except KeyError:
165 165 return False
166 166
167 167 def headrevs(self):
168 168 if self.filteredrevs:
169 169 # XXX we should fix and use the C version
170 170 return self._headrevs()
171 171 return super(changelog, self).headrevs()
172 172
173 173 def strip(self, *args, **kwargs):
174 174 # XXX make something better than assert
175 175 # We can't expect proper strip behavior if we are filtered.
176 176 assert not self.filteredrevs
177 177 super(changelog, self).strip(*args, **kwargs)
178 178
179 179 def rev(self, node):
180 180 """filtered version of revlog.rev"""
181 181 r = super(changelog, self).rev(node)
182 182 if r in self.filteredrevs:
183 183 raise error.LookupError(node, self.indexfile, _('no node'))
184 184 return r
185 185
186 186 def node(self, rev):
187 187 """filtered version of revlog.node"""
188 188 if rev in self.filteredrevs:
189 189 raise IndexError(rev)
190 190 return super(changelog, self).node(rev)
191 191
192 192 def linkrev(self, rev):
193 193 """filtered version of revlog.linkrev"""
194 194 if rev in self.filteredrevs:
195 195 raise IndexError(rev)
196 196 return super(changelog, self).linkrev(rev)
197 197
198 198 def parentrevs(self, rev):
199 199 """filtered version of revlog.parentrevs"""
200 200 if rev in self.filteredrevs:
201 201 raise IndexError(rev)
202 202 return super(changelog, self).parentrevs(rev)
203 203
204 204 def flags(self, rev):
205 205 """filtered version of revlog.flags"""
206 206 if rev in self.filteredrevs:
207 207 raise IndexError(rev)
208 208 return super(changelog, self).flags(rev)
209 209
210 210 def delayupdate(self):
211 211 "delay visibility of index updates to other readers"
212 212 self._delayed = True
213 213 self._divert = (len(self) == 0)
214 214 self._delaybuf = []
215 215 self.opener = delayopener(self._realopener, self.indexfile,
216 216 self._divert, self._delaybuf)
217 217
218 218 def finalize(self, tr):
219 219 "finalize index updates"
220 220 self._delayed = False
221 221 self.opener = self._realopener
222 222 # move redirected index data back into place
223 223 if self._divert:
224 224 nfile = self.opener(self.indexfile + ".a")
225 225 n = nfile.name
226 226 nfile.close()
227 227 util.rename(n, n[:-2])
228 228 elif self._delaybuf:
229 229 fp = self.opener(self.indexfile, 'a')
230 230 fp.write("".join(self._delaybuf))
231 231 fp.close()
232 232 self._delaybuf = []
233 233 # split when we're done
234 234 self.checkinlinesize(tr)
235 235
236 236 def readpending(self, file):
237 237 r = revlog.revlog(self.opener, file)
238 238 self.index = r.index
239 239 self.nodemap = r.nodemap
240 240 self._nodecache = r._nodecache
241 241 self._chunkcache = r._chunkcache
242 242
243 243 def writepending(self):
244 244 "create a file containing the unfinalized state for pretxnchangegroup"
245 245 if self._delaybuf:
246 246 # make a temporary copy of the index
247 247 fp1 = self._realopener(self.indexfile)
248 248 fp2 = self._realopener(self.indexfile + ".a", "w")
249 249 fp2.write(fp1.read())
250 250 # add pending data
251 251 fp2.write("".join(self._delaybuf))
252 252 fp2.close()
253 253 # switch modes so finalize can simply rename
254 254 self._delaybuf = []
255 255 self._divert = True
256 256
257 257 if self._divert:
258 258 return True
259 259
260 260 return False
261 261
262 262 def checkinlinesize(self, tr, fp=None):
263 263 if not self._delayed:
264 264 revlog.revlog.checkinlinesize(self, tr, fp)
265 265
266 266 def read(self, node):
267 267 """
268 268 format used:
269 269 nodeid\n : manifest node in ascii
270 270 user\n : user, no \n or \r allowed
271 271 time tz extra\n : date (time is int or float, timezone is int)
272 272 : extra is metadata, encoded and separated by '\0'
273 273 : older versions ignore it
274 274 files\n\n : files modified by the cset, no \n or \r allowed
275 275 (.*) : comment (free text, ideally utf-8)
276 276
277 277 changelog v0 doesn't use extra
278 278 """
279 279 text = self.revision(node)
280 280 if not text:
281 281 return (nullid, "", (0, 0), [], "", _defaultextra)
282 282 last = text.index("\n\n")
283 283 desc = encoding.tolocal(text[last + 2:])
284 284 l = text[:last].split('\n')
285 285 manifest = bin(l[0])
286 286 user = encoding.tolocal(l[1])
287 287
288 288 tdata = l[2].split(' ', 2)
289 289 if len(tdata) != 3:
290 290 time = float(tdata[0])
291 291 try:
292 292 # various tools did silly things with the time zone field.
293 293 timezone = int(tdata[1])
294 294 except ValueError:
295 295 timezone = 0
296 296 extra = _defaultextra
297 297 else:
298 298 time, timezone = float(tdata[0]), int(tdata[1])
299 299 extra = decodeextra(tdata[2])
300 300
301 301 files = l[3:]
302 302 return (manifest, user, (time, timezone), files, desc, extra)
303 303
304 304 def add(self, manifest, files, desc, transaction, p1, p2,
305 305 user, date=None, extra=None):
306 306 # Convert to UTF-8 encoded bytestrings as the very first
307 307 # thing: calling any method on a localstr object will turn it
308 308 # into a str object and the cached UTF-8 string is thus lost.
309 309 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
310 310
311 311 user = user.strip()
312 312 # An empty username or a username with a "\n" will make the
313 313 # revision text contain two "\n\n" sequences -> corrupt
314 314 # repository since read cannot unpack the revision.
315 315 if not user:
316 316 raise error.RevlogError(_("empty username"))
317 317 if "\n" in user:
318 318 raise error.RevlogError(_("username %s contains a newline")
319 319 % repr(user))
320 320
321 321 desc = stripdesc(desc)
322 322
323 323 if date:
324 324 parseddate = "%d %d" % util.parsedate(date)
325 325 else:
326 326 parseddate = "%d %d" % util.makedate()
327 327 if extra:
328 328 branch = extra.get("branch")
329 329 if branch in ("default", ""):
330 330 del extra["branch"]
331 331 elif branch in (".", "null", "tip"):
332 332 raise error.RevlogError(_('the name \'%s\' is reserved')
333 333 % branch)
334 334 if extra:
335 335 extra = encodeextra(extra)
336 336 parseddate = "%s %s" % (parseddate, extra)
337 337 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
338 338 text = "\n".join(l)
339 339 return self.addrevision(text, transaction, len(self), p1, p2)
@@ -1,108 +1,108 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
12 12 def computeunserved(repo):
13 13 """compute the set of revision that should be filtered when used a server
14 14
15 15 Secret and hidden changeset should not pretend to be here."""
16 16 assert not repo.changelog.filteredrevs
17 17 # fast path in simple case to avoid impact of non optimised code
18 18 if phases.hassecret(repo) or repo.obsstore:
19 19 return frozenset(repo.revs('hidden() + secret()'))
20 return ()
20 return frozenset()
21 21
22 22 # function to compute filtered set
23 23 filtertable = {'unserved': computeunserved}
24 24
25 25 def filteredrevs(repo, filtername):
26 26 """returns set of filtered revision for this filter name"""
27 27 if filtername not in repo.filteredrevcache:
28 28 func = filtertable[filtername]
29 29 repo.filteredrevcache[filtername] = func(repo.unfiltered())
30 30 return repo.filteredrevcache[filtername]
31 31
32 32 class repoview(object):
33 33 """Provide a read/write view of a repo through a filtered changelog
34 34
35 35 This object is used to access a filtered version of a repository without
36 36 altering the original repository object itself. We can not alter the
37 37 original object for two main reasons:
38 38 - It prevents the use of a repo with multiple filters at the same time. In
39 39 particular when multiple threads are involved.
40 40 - It makes scope of the filtering harder to control.
41 41
42 42 This object behaves very closely to the original repository. All attribute
43 43 operations are done on the original repository:
44 44 - An access to `repoview.someattr` actually returns `repo.someattr`,
45 45 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
46 46 - A deletion of `repoview.someattr` actually drops `someattr`
47 47 from `repo.__dict__`.
48 48
49 49 The only exception is the `changelog` property. It is overridden to return
50 50 a (surface) copy of `repo.changelog` with some revisions filtered. The
51 51 `filtername` attribute of the view control the revisions that need to be
52 52 filtered. (the fact the changelog is copied is an implementation detail).
53 53
54 54 Unlike attributes, this object intercepts all method calls. This means that
55 55 all methods are run on the `repoview` object with the filtered `changelog`
56 56 property. For this purpose the simple `repoview` class must be mixed with
57 57 the actual class of the repository. This ensures that the resulting
58 58 `repoview` object have the very same methods than the repo object. This
59 59 leads to the property below.
60 60
61 61 repoview.method() --> repo.__class__.method(repoview)
62 62
63 63 The inheritance has to be done dynamically because `repo` can be of any
64 64 subclasses of `localrepo`. Eg: `bundlerepo` or `httprepo`.
65 65 """
66 66
67 67 def __init__(self, repo, filtername):
68 68 object.__setattr__(self, '_unfilteredrepo', repo)
69 69 object.__setattr__(self, 'filtername', filtername)
70 70
71 71 # not a cacheproperty on purpose we shall implement a proper cache later
72 72 @property
73 73 def changelog(self):
74 74 """return a filtered version of the changeset
75 75
76 76 this changelog must not be used for writing"""
77 77 # some cache may be implemented later
78 78 cl = copy.copy(self._unfilteredrepo.changelog)
79 79 cl.filteredrevs = filteredrevs(self._unfilteredrepo, self.filtername)
80 80 return cl
81 81
82 82 def unfiltered(self):
83 83 """Return an unfiltered version of a repo"""
84 84 return self._unfilteredrepo
85 85
86 86 def filtered(self, name):
87 87 """Return a filtered version of a repository"""
88 88 if name == self.filtername:
89 89 return self
90 90 return self.unfiltered().filtered(name)
91 91
92 92 # everything access are forwarded to the proxied repo
93 93 def __getattr__(self, attr):
94 94 return getattr(self._unfilteredrepo, attr)
95 95
96 96 def __setattr__(self, attr, value):
97 97 return setattr(self._unfilteredrepo, attr, value)
98 98
99 99 def __delattr__(self, attr):
100 100 return delattr(self._unfilteredrepo, attr)
101 101
102 102 # The `requirement` attribut is initialiazed during __init__. But
103 103 # __getattr__ won't be called as it also exists on the class. We need
104 104 # explicit forwarding to main repo here
105 105 @property
106 106 def requirements(self):
107 107 return self._unfilteredrepo.requirements
108 108
General Comments 0
You need to be logged in to leave comments. Login now