##// END OF EJS Templates
changelog: remove redundant parentheses...
Gregory Szorc -
r28282:d698c11b default
parent child Browse files
Show More
@@ -1,417 +1,417 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 __future__ import absolute_import
9 9
10 10 from .i18n import _
11 11 from .node import (
12 12 bin,
13 13 hex,
14 14 nullid,
15 15 )
16 16
17 17 from . import (
18 18 encoding,
19 19 error,
20 20 revlog,
21 21 util,
22 22 )
23 23
24 24 _defaultextra = {'branch': 'default'}
25 25
26 26 def _string_escape(text):
27 27 """
28 28 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
29 29 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
30 30 >>> s
31 31 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
32 32 >>> res = _string_escape(s)
33 33 >>> s == res.decode('string_escape')
34 34 True
35 35 """
36 36 # subset of the string_escape codec
37 37 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
38 38 return text.replace('\0', '\\0')
39 39
40 40 def decodeextra(text):
41 41 """
42 42 >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
43 43 ... ).iteritems())
44 44 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
45 45 >>> sorted(decodeextra(encodeextra({'foo': 'bar',
46 46 ... 'baz': chr(92) + chr(0) + '2'})
47 47 ... ).iteritems())
48 48 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
49 49 """
50 50 extra = _defaultextra.copy()
51 51 for l in text.split('\0'):
52 52 if l:
53 53 if '\\0' in l:
54 54 # fix up \0 without getting into trouble with \\0
55 55 l = l.replace('\\\\', '\\\\\n')
56 56 l = l.replace('\\0', '\0')
57 57 l = l.replace('\n', '')
58 58 k, v = l.decode('string_escape').split(':', 1)
59 59 extra[k] = v
60 60 return extra
61 61
62 62 def encodeextra(d):
63 63 # keys must be sorted to produce a deterministic changelog entry
64 64 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
65 65 return "\0".join(items)
66 66
67 67 def stripdesc(desc):
68 68 """strip trailing whitespace and leading and trailing empty lines"""
69 69 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
70 70
71 71 class appender(object):
72 72 '''the changelog index must be updated last on disk, so we use this class
73 73 to delay writes to it'''
74 74 def __init__(self, vfs, name, mode, buf):
75 75 self.data = buf
76 76 fp = vfs(name, mode)
77 77 self.fp = fp
78 78 self.offset = fp.tell()
79 79 self.size = vfs.fstat(fp).st_size
80 80
81 81 def end(self):
82 82 return self.size + len("".join(self.data))
83 83 def tell(self):
84 84 return self.offset
85 85 def flush(self):
86 86 pass
87 87 def close(self):
88 88 self.fp.close()
89 89
90 90 def seek(self, offset, whence=0):
91 91 '''virtual file offset spans real file and data'''
92 92 if whence == 0:
93 93 self.offset = offset
94 94 elif whence == 1:
95 95 self.offset += offset
96 96 elif whence == 2:
97 97 self.offset = self.end() + offset
98 98 if self.offset < self.size:
99 99 self.fp.seek(self.offset)
100 100
101 101 def read(self, count=-1):
102 102 '''only trick here is reads that span real file and data'''
103 103 ret = ""
104 104 if self.offset < self.size:
105 105 s = self.fp.read(count)
106 106 ret = s
107 107 self.offset += len(s)
108 108 if count > 0:
109 109 count -= len(s)
110 110 if count != 0:
111 111 doff = self.offset - self.size
112 112 self.data.insert(0, "".join(self.data))
113 113 del self.data[1:]
114 114 s = self.data[0][doff:doff + count]
115 115 self.offset += len(s)
116 116 ret += s
117 117 return ret
118 118
119 119 def write(self, s):
120 120 self.data.append(str(s))
121 121 self.offset += len(s)
122 122
123 123 def _divertopener(opener, target):
124 124 """build an opener that writes in 'target.a' instead of 'target'"""
125 125 def _divert(name, mode='r'):
126 126 if name != target:
127 127 return opener(name, mode)
128 128 return opener(name + ".a", mode)
129 129 return _divert
130 130
131 131 def _delayopener(opener, target, buf):
132 132 """build an opener that stores chunks in 'buf' instead of 'target'"""
133 133 def _delay(name, mode='r'):
134 134 if name != target:
135 135 return opener(name, mode)
136 136 return appender(opener, name, mode, buf)
137 137 return _delay
138 138
139 139 class changelog(revlog.revlog):
140 140 def __init__(self, opener):
141 141 revlog.revlog.__init__(self, opener, "00changelog.i")
142 142 if self._initempty:
143 143 # changelogs don't benefit from generaldelta
144 144 self.version &= ~revlog.REVLOGGENERALDELTA
145 145 self._generaldelta = False
146 146 self._realopener = opener
147 147 self._delayed = False
148 148 self._delaybuf = None
149 149 self._divert = False
150 150 self.filteredrevs = frozenset()
151 151
152 152 def tip(self):
153 153 """filtered version of revlog.tip"""
154 154 for i in xrange(len(self) -1, -2, -1):
155 155 if i not in self.filteredrevs:
156 156 return self.node(i)
157 157
158 158 def __contains__(self, rev):
159 159 """filtered version of revlog.__contains__"""
160 160 return (0 <= rev < len(self)
161 161 and rev not in self.filteredrevs)
162 162
163 163 def __iter__(self):
164 164 """filtered version of revlog.__iter__"""
165 165 if len(self.filteredrevs) == 0:
166 166 return revlog.revlog.__iter__(self)
167 167
168 168 def filterediter():
169 169 for i in xrange(len(self)):
170 170 if i not in self.filteredrevs:
171 171 yield i
172 172
173 173 return filterediter()
174 174
175 175 def revs(self, start=0, stop=None):
176 176 """filtered version of revlog.revs"""
177 177 for i in super(changelog, self).revs(start, stop):
178 178 if i not in self.filteredrevs:
179 179 yield i
180 180
181 181 @util.propertycache
182 182 def nodemap(self):
183 183 # XXX need filtering too
184 184 self.rev(self.node(0))
185 185 return self._nodecache
186 186
187 187 def reachableroots(self, minroot, heads, roots, includepath=False):
188 188 return self.index.reachableroots2(minroot, heads, roots, includepath)
189 189
190 190 def headrevs(self):
191 191 if self.filteredrevs:
192 192 try:
193 193 return self.index.headrevsfiltered(self.filteredrevs)
194 194 # AttributeError covers non-c-extension environments and
195 195 # old c extensions without filter handling.
196 196 except AttributeError:
197 197 return self._headrevs()
198 198
199 199 return super(changelog, self).headrevs()
200 200
201 201 def strip(self, *args, **kwargs):
202 202 # XXX make something better than assert
203 203 # We can't expect proper strip behavior if we are filtered.
204 204 assert not self.filteredrevs
205 205 super(changelog, self).strip(*args, **kwargs)
206 206
207 207 def rev(self, node):
208 208 """filtered version of revlog.rev"""
209 209 r = super(changelog, self).rev(node)
210 210 if r in self.filteredrevs:
211 211 raise error.FilteredLookupError(hex(node), self.indexfile,
212 212 _('filtered node'))
213 213 return r
214 214
215 215 def node(self, rev):
216 216 """filtered version of revlog.node"""
217 217 if rev in self.filteredrevs:
218 218 raise error.FilteredIndexError(rev)
219 219 return super(changelog, self).node(rev)
220 220
221 221 def linkrev(self, rev):
222 222 """filtered version of revlog.linkrev"""
223 223 if rev in self.filteredrevs:
224 224 raise error.FilteredIndexError(rev)
225 225 return super(changelog, self).linkrev(rev)
226 226
227 227 def parentrevs(self, rev):
228 228 """filtered version of revlog.parentrevs"""
229 229 if rev in self.filteredrevs:
230 230 raise error.FilteredIndexError(rev)
231 231 return super(changelog, self).parentrevs(rev)
232 232
233 233 def flags(self, rev):
234 234 """filtered version of revlog.flags"""
235 235 if rev in self.filteredrevs:
236 236 raise error.FilteredIndexError(rev)
237 237 return super(changelog, self).flags(rev)
238 238
239 239 def delayupdate(self, tr):
240 240 "delay visibility of index updates to other readers"
241 241
242 242 if not self._delayed:
243 243 if len(self) == 0:
244 244 self._divert = True
245 245 if self._realopener.exists(self.indexfile + '.a'):
246 246 self._realopener.unlink(self.indexfile + '.a')
247 247 self.opener = _divertopener(self._realopener, self.indexfile)
248 248 else:
249 249 self._delaybuf = []
250 250 self.opener = _delayopener(self._realopener, self.indexfile,
251 251 self._delaybuf)
252 252 self._delayed = True
253 253 tr.addpending('cl-%i' % id(self), self._writepending)
254 254 tr.addfinalize('cl-%i' % id(self), self._finalize)
255 255
256 256 def _finalize(self, tr):
257 257 "finalize index updates"
258 258 self._delayed = False
259 259 self.opener = self._realopener
260 260 # move redirected index data back into place
261 261 if self._divert:
262 262 assert not self._delaybuf
263 263 tmpname = self.indexfile + ".a"
264 264 nfile = self.opener.open(tmpname)
265 265 nfile.close()
266 266 self.opener.rename(tmpname, self.indexfile)
267 267 elif self._delaybuf:
268 268 fp = self.opener(self.indexfile, 'a')
269 269 fp.write("".join(self._delaybuf))
270 270 fp.close()
271 271 self._delaybuf = None
272 272 self._divert = False
273 273 # split when we're done
274 274 self.checkinlinesize(tr)
275 275
276 276 def readpending(self, file):
277 277 """read index data from a "pending" file
278 278
279 279 During a transaction, the actual changeset data is already stored in the
280 280 main file, but not yet finalized in the on-disk index. Instead, a
281 281 "pending" index is written by the transaction logic. If this function
282 282 is running, we are likely in a subprocess invoked in a hook. The
283 283 subprocess is informed that it is within a transaction and needs to
284 284 access its content.
285 285
286 286 This function will read all the index data out of the pending file and
287 287 overwrite the main index."""
288 288
289 289 if not self.opener.exists(file):
290 290 return # no pending data for changelog
291 291 r = revlog.revlog(self.opener, file)
292 292 self.index = r.index
293 293 self.nodemap = r.nodemap
294 294 self._nodecache = r._nodecache
295 295 self._chunkcache = r._chunkcache
296 296
297 297 def _writepending(self, tr):
298 298 "create a file containing the unfinalized state for pretxnchangegroup"
299 299 if self._delaybuf:
300 300 # make a temporary copy of the index
301 301 fp1 = self._realopener(self.indexfile)
302 302 pendingfilename = self.indexfile + ".a"
303 303 # register as a temp file to ensure cleanup on failure
304 304 tr.registertmp(pendingfilename)
305 305 # write existing data
306 306 fp2 = self._realopener(pendingfilename, "w")
307 307 fp2.write(fp1.read())
308 308 # add pending data
309 309 fp2.write("".join(self._delaybuf))
310 310 fp2.close()
311 311 # switch modes so finalize can simply rename
312 312 self._delaybuf = None
313 313 self._divert = True
314 314 self.opener = _divertopener(self._realopener, self.indexfile)
315 315
316 316 if self._divert:
317 317 return True
318 318
319 319 return False
320 320
321 321 def checkinlinesize(self, tr, fp=None):
322 322 if not self._delayed:
323 323 revlog.revlog.checkinlinesize(self, tr, fp)
324 324
325 325 def read(self, node):
326 326 """
327 327 format used:
328 328 nodeid\n : manifest node in ascii
329 329 user\n : user, no \n or \r allowed
330 330 time tz extra\n : date (time is int or float, timezone is int)
331 331 : extra is metadata, encoded and separated by '\0'
332 332 : older versions ignore it
333 333 files\n\n : files modified by the cset, no \n or \r allowed
334 334 (.*) : comment (free text, ideally utf-8)
335 335
336 336 changelog v0 doesn't use extra
337 337 """
338 338 text = self.revision(node)
339 339 if not text:
340 return (nullid, "", (0, 0), [], "", _defaultextra)
340 return nullid, "", (0, 0), [], "", _defaultextra
341 341 last = text.index("\n\n")
342 342 desc = encoding.tolocal(text[last + 2:])
343 343 l = text[:last].split('\n')
344 344 manifest = bin(l[0])
345 345 user = encoding.tolocal(l[1])
346 346
347 347 tdata = l[2].split(' ', 2)
348 348 if len(tdata) != 3:
349 349 time = float(tdata[0])
350 350 try:
351 351 # various tools did silly things with the time zone field.
352 352 timezone = int(tdata[1])
353 353 except ValueError:
354 354 timezone = 0
355 355 extra = _defaultextra
356 356 else:
357 357 time, timezone = float(tdata[0]), int(tdata[1])
358 358 extra = decodeextra(tdata[2])
359 359
360 360 files = l[3:]
361 return (manifest, user, (time, timezone), files, desc, extra)
361 return manifest, user, (time, timezone), files, desc, extra
362 362
363 363 def readfiles(self, node):
364 364 """
365 365 short version of read that only returns the files modified by the cset
366 366 """
367 367 text = self.revision(node)
368 368 if not text:
369 369 return []
370 370 last = text.index("\n\n")
371 371 l = text[:last].split('\n')
372 372 return l[3:]
373 373
374 374 def add(self, manifest, files, desc, transaction, p1, p2,
375 375 user, date=None, extra=None):
376 376 # Convert to UTF-8 encoded bytestrings as the very first
377 377 # thing: calling any method on a localstr object will turn it
378 378 # into a str object and the cached UTF-8 string is thus lost.
379 379 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
380 380
381 381 user = user.strip()
382 382 # An empty username or a username with a "\n" will make the
383 383 # revision text contain two "\n\n" sequences -> corrupt
384 384 # repository since read cannot unpack the revision.
385 385 if not user:
386 386 raise error.RevlogError(_("empty username"))
387 387 if "\n" in user:
388 388 raise error.RevlogError(_("username %s contains a newline")
389 389 % repr(user))
390 390
391 391 desc = stripdesc(desc)
392 392
393 393 if date:
394 394 parseddate = "%d %d" % util.parsedate(date)
395 395 else:
396 396 parseddate = "%d %d" % util.makedate()
397 397 if extra:
398 398 branch = extra.get("branch")
399 399 if branch in ("default", ""):
400 400 del extra["branch"]
401 401 elif branch in (".", "null", "tip"):
402 402 raise error.RevlogError(_('the name \'%s\' is reserved')
403 403 % branch)
404 404 if extra:
405 405 extra = encodeextra(extra)
406 406 parseddate = "%s %s" % (parseddate, extra)
407 407 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
408 408 text = "\n".join(l)
409 409 return self.addrevision(text, transaction, len(self), p1, p2)
410 410
411 411 def branchinfo(self, rev):
412 412 """return the branch name and open/close state of a revision
413 413
414 414 This function exists because creating a changectx object
415 415 just to access this is costly."""
416 416 extra = self.read(rev)[5]
417 417 return encoding.tolocal(extra.get("branch")), 'close' in extra
General Comments 0
You need to be logged in to leave comments. Login now