comparison mercurial/changelog.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 041f042afcc5
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
11 from .node import ( 11 from .node import (
12 bin, 12 bin,
13 hex, 13 hex,
14 nullid, 14 nullid,
15 ) 15 )
16 from .thirdparty import ( 16 from .thirdparty import attr
17 attr,
18 )
19 17
20 from . import ( 18 from . import (
21 encoding, 19 encoding,
22 error, 20 error,
23 pycompat, 21 pycompat,
28 dateutil, 26 dateutil,
29 stringutil, 27 stringutil,
30 ) 28 )
31 29
32 _defaultextra = {'branch': 'default'} 30 _defaultextra = {'branch': 'default'}
31
33 32
34 def _string_escape(text): 33 def _string_escape(text):
35 """ 34 """
36 >>> from .pycompat import bytechr as chr 35 >>> from .pycompat import bytechr as chr
37 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)} 36 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
44 """ 43 """
45 # subset of the string_escape codec 44 # subset of the string_escape codec
46 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r') 45 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
47 return text.replace('\0', '\\0') 46 return text.replace('\0', '\\0')
48 47
48
49 def _string_unescape(text): 49 def _string_unescape(text):
50 if '\\0' in text: 50 if '\\0' in text:
51 # fix up \0 without getting into trouble with \\0 51 # fix up \0 without getting into trouble with \\0
52 text = text.replace('\\\\', '\\\\\n') 52 text = text.replace('\\\\', '\\\\\n')
53 text = text.replace('\\0', '\0') 53 text = text.replace('\\0', '\0')
54 text = text.replace('\n', '') 54 text = text.replace('\n', '')
55 return stringutil.unescapestr(text) 55 return stringutil.unescapestr(text)
56
56 57
57 def decodeextra(text): 58 def decodeextra(text):
58 """ 59 """
59 >>> from .pycompat import bytechr as chr 60 >>> from .pycompat import bytechr as chr
60 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'}) 61 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
70 if l: 71 if l:
71 k, v = _string_unescape(l).split(':', 1) 72 k, v = _string_unescape(l).split(':', 1)
72 extra[k] = v 73 extra[k] = v
73 return extra 74 return extra
74 75
76
75 def encodeextra(d): 77 def encodeextra(d):
76 # keys must be sorted to produce a deterministic changelog entry 78 # keys must be sorted to produce a deterministic changelog entry
77 items = [ 79 items = [
78 _string_escape('%s:%s' % (k, pycompat.bytestr(d[k]))) 80 _string_escape('%s:%s' % (k, pycompat.bytestr(d[k]))) for k in sorted(d)
79 for k in sorted(d)
80 ] 81 ]
81 return "\0".join(items) 82 return "\0".join(items)
83
82 84
83 def encodecopies(files, copies): 85 def encodecopies(files, copies):
84 items = [] 86 items = []
85 for i, dst in enumerate(files): 87 for i, dst in enumerate(files):
86 if dst in copies: 88 if dst in copies:
87 items.append('%d\0%s' % (i, copies[dst])) 89 items.append('%d\0%s' % (i, copies[dst]))
88 if len(items) != len(copies): 90 if len(items) != len(copies):
89 raise error.ProgrammingError('some copy targets missing from file list') 91 raise error.ProgrammingError('some copy targets missing from file list')
90 return "\n".join(items) 92 return "\n".join(items)
93
91 94
92 def decodecopies(files, data): 95 def decodecopies(files, data):
93 try: 96 try:
94 copies = {} 97 copies = {}
95 if not data: 98 if not data:
103 except (ValueError, IndexError): 106 except (ValueError, IndexError):
104 # Perhaps someone had chosen the same key name (e.g. "p1copies") and 107 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
105 # used different syntax for the value. 108 # used different syntax for the value.
106 return None 109 return None
107 110
111
108 def encodefileindices(files, subset): 112 def encodefileindices(files, subset):
109 subset = set(subset) 113 subset = set(subset)
110 indices = [] 114 indices = []
111 for i, f in enumerate(files): 115 for i, f in enumerate(files):
112 if f in subset: 116 if f in subset:
113 indices.append('%d' % i) 117 indices.append('%d' % i)
114 return '\n'.join(indices) 118 return '\n'.join(indices)
119
115 120
116 def decodefileindices(files, data): 121 def decodefileindices(files, data):
117 try: 122 try:
118 subset = [] 123 subset = []
119 if not data: 124 if not data:
127 except (ValueError, IndexError): 132 except (ValueError, IndexError):
128 # Perhaps someone had chosen the same key name (e.g. "added") and 133 # Perhaps someone had chosen the same key name (e.g. "added") and
129 # used different syntax for the value. 134 # used different syntax for the value.
130 return None 135 return None
131 136
137
132 def stripdesc(desc): 138 def stripdesc(desc):
133 """strip trailing whitespace and leading and trailing empty lines""" 139 """strip trailing whitespace and leading and trailing empty lines"""
134 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n') 140 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
135 141
142
136 class appender(object): 143 class appender(object):
137 '''the changelog index must be updated last on disk, so we use this class 144 '''the changelog index must be updated last on disk, so we use this class
138 to delay writes to it''' 145 to delay writes to it'''
146
139 def __init__(self, vfs, name, mode, buf): 147 def __init__(self, vfs, name, mode, buf):
140 self.data = buf 148 self.data = buf
141 fp = vfs(name, mode) 149 fp = vfs(name, mode)
142 self.fp = fp 150 self.fp = fp
143 self.offset = fp.tell() 151 self.offset = fp.tell()
144 self.size = vfs.fstat(fp).st_size 152 self.size = vfs.fstat(fp).st_size
145 self._end = self.size 153 self._end = self.size
146 154
147 def end(self): 155 def end(self):
148 return self._end 156 return self._end
157
149 def tell(self): 158 def tell(self):
150 return self.offset 159 return self.offset
160
151 def flush(self): 161 def flush(self):
152 pass 162 pass
153 163
154 @property 164 @property
155 def closed(self): 165 def closed(self):
180 count -= len(s) 190 count -= len(s)
181 if count != 0: 191 if count != 0:
182 doff = self.offset - self.size 192 doff = self.offset - self.size
183 self.data.insert(0, "".join(self.data)) 193 self.data.insert(0, "".join(self.data))
184 del self.data[1:] 194 del self.data[1:]
185 s = self.data[0][doff:doff + count] 195 s = self.data[0][doff : doff + count]
186 self.offset += len(s) 196 self.offset += len(s)
187 ret += s 197 ret += s
188 return ret 198 return ret
189 199
190 def write(self, s): 200 def write(self, s):
197 return self 207 return self
198 208
199 def __exit__(self, *args): 209 def __exit__(self, *args):
200 return self.fp.__exit__(*args) 210 return self.fp.__exit__(*args)
201 211
212
202 def _divertopener(opener, target): 213 def _divertopener(opener, target):
203 """build an opener that writes in 'target.a' instead of 'target'""" 214 """build an opener that writes in 'target.a' instead of 'target'"""
215
204 def _divert(name, mode='r', checkambig=False): 216 def _divert(name, mode='r', checkambig=False):
205 if name != target: 217 if name != target:
206 return opener(name, mode) 218 return opener(name, mode)
207 return opener(name + ".a", mode) 219 return opener(name + ".a", mode)
220
208 return _divert 221 return _divert
222
209 223
210 def _delayopener(opener, target, buf): 224 def _delayopener(opener, target, buf):
211 """build an opener that stores chunks in 'buf' instead of 'target'""" 225 """build an opener that stores chunks in 'buf' instead of 'target'"""
226
212 def _delay(name, mode='r', checkambig=False): 227 def _delay(name, mode='r', checkambig=False):
213 if name != target: 228 if name != target:
214 return opener(name, mode) 229 return opener(name, mode)
215 return appender(opener, name, mode, buf) 230 return appender(opener, name, mode, buf)
231
216 return _delay 232 return _delay
233
217 234
218 @attr.s 235 @attr.s
219 class _changelogrevision(object): 236 class _changelogrevision(object):
220 # Extensions might modify _defaultextra, so let the constructor below pass 237 # Extensions might modify _defaultextra, so let the constructor below pass
221 # it in 238 # it in
228 filesremoved = attr.ib(default=None) 245 filesremoved = attr.ib(default=None)
229 p1copies = attr.ib(default=None) 246 p1copies = attr.ib(default=None)
230 p2copies = attr.ib(default=None) 247 p2copies = attr.ib(default=None)
231 description = attr.ib(default='') 248 description = attr.ib(default='')
232 249
250
233 class changelogrevision(object): 251 class changelogrevision(object):
234 """Holds results of a parsed changelog revision. 252 """Holds results of a parsed changelog revision.
235 253
236 Changelog revisions consist of multiple pieces of data, including 254 Changelog revisions consist of multiple pieces of data, including
237 the manifest node, user, and date. This object exposes a view into 255 the manifest node, user, and date. This object exposes a view into
266 nl2 = text.index('\n', nl1 + 1) 284 nl2 = text.index('\n', nl1 + 1)
267 nl3 = text.index('\n', nl2 + 1) 285 nl3 = text.index('\n', nl2 + 1)
268 286
269 # The list of files may be empty. Which means nl3 is the first of the 287 # The list of files may be empty. Which means nl3 is the first of the
270 # double newline that precedes the description. 288 # double newline that precedes the description.
271 if text[nl3 + 1:nl3 + 2] == '\n': 289 if text[nl3 + 1 : nl3 + 2] == '\n':
272 doublenl = nl3 290 doublenl = nl3
273 else: 291 else:
274 doublenl = text.index('\n\n', nl3 + 1) 292 doublenl = text.index('\n\n', nl3 + 1)
275 293
276 self._offsets = (nl1, nl2, nl3, doublenl) 294 self._offsets = (nl1, nl2, nl3, doublenl)
278 296
279 return self 297 return self
280 298
281 @property 299 @property
282 def manifest(self): 300 def manifest(self):
283 return bin(self._text[0:self._offsets[0]]) 301 return bin(self._text[0 : self._offsets[0]])
284 302
285 @property 303 @property
286 def user(self): 304 def user(self):
287 off = self._offsets 305 off = self._offsets
288 return encoding.tolocal(self._text[off[0] + 1:off[1]]) 306 return encoding.tolocal(self._text[off[0] + 1 : off[1]])
289 307
290 @property 308 @property
291 def _rawdate(self): 309 def _rawdate(self):
292 off = self._offsets 310 off = self._offsets
293 dateextra = self._text[off[1] + 1:off[2]] 311 dateextra = self._text[off[1] + 1 : off[2]]
294 return dateextra.split(' ', 2)[0:2] 312 return dateextra.split(' ', 2)[0:2]
295 313
296 @property 314 @property
297 def _rawextra(self): 315 def _rawextra(self):
298 off = self._offsets 316 off = self._offsets
299 dateextra = self._text[off[1] + 1:off[2]] 317 dateextra = self._text[off[1] + 1 : off[2]]
300 fields = dateextra.split(' ', 2) 318 fields = dateextra.split(' ', 2)
301 if len(fields) != 3: 319 if len(fields) != 3:
302 return None 320 return None
303 321
304 return fields[2] 322 return fields[2]
327 def files(self): 345 def files(self):
328 off = self._offsets 346 off = self._offsets
329 if off[2] == off[3]: 347 if off[2] == off[3]:
330 return [] 348 return []
331 349
332 return self._text[off[2] + 1:off[3]].split('\n') 350 return self._text[off[2] + 1 : off[3]].split('\n')
333 351
334 @property 352 @property
335 def filesadded(self): 353 def filesadded(self):
336 rawindices = self.extra.get('filesadded') 354 rawindices = self.extra.get('filesadded')
337 return rawindices and decodefileindices(self.files, rawindices) 355 return rawindices and decodefileindices(self.files, rawindices)
351 rawcopies = self.extra.get('p2copies') 369 rawcopies = self.extra.get('p2copies')
352 return rawcopies and decodecopies(self.files, rawcopies) 370 return rawcopies and decodecopies(self.files, rawcopies)
353 371
354 @property 372 @property
355 def description(self): 373 def description(self):
356 return encoding.tolocal(self._text[self._offsets[3] + 2:]) 374 return encoding.tolocal(self._text[self._offsets[3] + 2 :])
375
357 376
358 class changelog(revlog.revlog): 377 class changelog(revlog.revlog):
359 def __init__(self, opener, trypending=False): 378 def __init__(self, opener, trypending=False):
360 """Load a changelog revlog using an opener. 379 """Load a changelog revlog using an opener.
361 380
370 indexfile = '00changelog.i.a' 389 indexfile = '00changelog.i.a'
371 else: 390 else:
372 indexfile = '00changelog.i' 391 indexfile = '00changelog.i'
373 392
374 datafile = '00changelog.d' 393 datafile = '00changelog.d'
375 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile, 394 revlog.revlog.__init__(
376 checkambig=True, mmaplargeindex=True) 395 self,
396 opener,
397 indexfile,
398 datafile=datafile,
399 checkambig=True,
400 mmaplargeindex=True,
401 )
377 402
378 if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1): 403 if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1):
379 # changelogs don't benefit from generaldelta. 404 # changelogs don't benefit from generaldelta.
380 405
381 self.version &= ~revlog.FLAG_GENERALDELTA 406 self.version &= ~revlog.FLAG_GENERALDELTA
392 self._divert = False 417 self._divert = False
393 self.filteredrevs = frozenset() 418 self.filteredrevs = frozenset()
394 self._copiesstorage = opener.options.get('copies-storage') 419 self._copiesstorage = opener.options.get('copies-storage')
395 420
396 def tiprev(self): 421 def tiprev(self):
397 for i in pycompat.xrange(len(self) -1, -2, -1): 422 for i in pycompat.xrange(len(self) - 1, -2, -1):
398 if i not in self.filteredrevs: 423 if i not in self.filteredrevs:
399 return i 424 return i
400 425
401 def tip(self): 426 def tip(self):
402 """filtered version of revlog.tip""" 427 """filtered version of revlog.tip"""
403 return self.node(self.tiprev()) 428 return self.node(self.tiprev())
404 429
405 def __contains__(self, rev): 430 def __contains__(self, rev):
406 """filtered version of revlog.__contains__""" 431 """filtered version of revlog.__contains__"""
407 return (0 <= rev < len(self) 432 return 0 <= rev < len(self) and rev not in self.filteredrevs
408 and rev not in self.filteredrevs)
409 433
410 def __iter__(self): 434 def __iter__(self):
411 """filtered version of revlog.__iter__""" 435 """filtered version of revlog.__iter__"""
412 if len(self.filteredrevs) == 0: 436 if len(self.filteredrevs) == 0:
413 return revlog.revlog.__iter__(self) 437 return revlog.revlog.__iter__(self)
467 491
468 def rev(self, node): 492 def rev(self, node):
469 """filtered version of revlog.rev""" 493 """filtered version of revlog.rev"""
470 r = super(changelog, self).rev(node) 494 r = super(changelog, self).rev(node)
471 if r in self.filteredrevs: 495 if r in self.filteredrevs:
472 raise error.FilteredLookupError(hex(node), self.indexfile, 496 raise error.FilteredLookupError(
473 _('filtered node')) 497 hex(node), self.indexfile, _('filtered node')
498 )
474 return r 499 return r
475 500
476 def node(self, rev): 501 def node(self, rev):
477 """filtered version of revlog.node""" 502 """filtered version of revlog.node"""
478 if rev in self.filteredrevs: 503 if rev in self.filteredrevs:
506 if self._realopener.exists(self.indexfile + '.a'): 531 if self._realopener.exists(self.indexfile + '.a'):
507 self._realopener.unlink(self.indexfile + '.a') 532 self._realopener.unlink(self.indexfile + '.a')
508 self.opener = _divertopener(self._realopener, self.indexfile) 533 self.opener = _divertopener(self._realopener, self.indexfile)
509 else: 534 else:
510 self._delaybuf = [] 535 self._delaybuf = []
511 self.opener = _delayopener(self._realopener, self.indexfile, 536 self.opener = _delayopener(
512 self._delaybuf) 537 self._realopener, self.indexfile, self._delaybuf
538 )
513 self._delayed = True 539 self._delayed = True
514 tr.addpending('cl-%i' % id(self), self._writepending) 540 tr.addpending('cl-%i' % id(self), self._writepending)
515 tr.addfinalize('cl-%i' % id(self), self._finalize) 541 tr.addfinalize('cl-%i' % id(self), self._finalize)
516 542
517 def _finalize(self, tr): 543 def _finalize(self, tr):
577 Unless you need to access all fields, consider calling 603 Unless you need to access all fields, consider calling
578 ``changelogrevision`` instead, as it is faster for partial object 604 ``changelogrevision`` instead, as it is faster for partial object
579 access. 605 access.
580 """ 606 """
581 c = changelogrevision(self.revision(node)) 607 c = changelogrevision(self.revision(node))
582 return ( 608 return (c.manifest, c.user, c.date, c.files, c.description, c.extra)
583 c.manifest,
584 c.user,
585 c.date,
586 c.files,
587 c.description,
588 c.extra
589 )
590 609
591 def changelogrevision(self, nodeorrev): 610 def changelogrevision(self, nodeorrev):
592 """Obtain a ``changelogrevision`` for a node or revision.""" 611 """Obtain a ``changelogrevision`` for a node or revision."""
593 return changelogrevision(self.revision(nodeorrev)) 612 return changelogrevision(self.revision(nodeorrev))
594 613
601 return [] 620 return []
602 last = text.index("\n\n") 621 last = text.index("\n\n")
603 l = text[:last].split('\n') 622 l = text[:last].split('\n')
604 return l[3:] 623 return l[3:]
605 624
606 def add(self, manifest, files, desc, transaction, p1, p2, 625 def add(
607 user, date=None, extra=None, p1copies=None, p2copies=None, 626 self,
608 filesadded=None, filesremoved=None): 627 manifest,
628 files,
629 desc,
630 transaction,
631 p1,
632 p2,
633 user,
634 date=None,
635 extra=None,
636 p1copies=None,
637 p2copies=None,
638 filesadded=None,
639 filesremoved=None,
640 ):
609 # Convert to UTF-8 encoded bytestrings as the very first 641 # Convert to UTF-8 encoded bytestrings as the very first
610 # thing: calling any method on a localstr object will turn it 642 # thing: calling any method on a localstr object will turn it
611 # into a str object and the cached UTF-8 string is thus lost. 643 # into a str object and the cached UTF-8 string is thus lost.
612 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc) 644 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
613 645
616 # revision text contain two "\n\n" sequences -> corrupt 648 # revision text contain two "\n\n" sequences -> corrupt
617 # repository since read cannot unpack the revision. 649 # repository since read cannot unpack the revision.
618 if not user: 650 if not user:
619 raise error.StorageError(_("empty username")) 651 raise error.StorageError(_("empty username"))
620 if "\n" in user: 652 if "\n" in user:
621 raise error.StorageError(_("username %r contains a newline") 653 raise error.StorageError(
622 % pycompat.bytestr(user)) 654 _("username %r contains a newline") % pycompat.bytestr(user)
655 )
623 656
624 desc = stripdesc(desc) 657 desc = stripdesc(desc)
625 658
626 if date: 659 if date:
627 parseddate = "%d %d" % dateutil.parsedate(date) 660 parseddate = "%d %d" % dateutil.parsedate(date)
630 if extra: 663 if extra:
631 branch = extra.get("branch") 664 branch = extra.get("branch")
632 if branch in ("default", ""): 665 if branch in ("default", ""):
633 del extra["branch"] 666 del extra["branch"]
634 elif branch in (".", "null", "tip"): 667 elif branch in (".", "null", "tip"):
635 raise error.StorageError(_('the name \'%s\' is reserved') 668 raise error.StorageError(
636 % branch) 669 _('the name \'%s\' is reserved') % branch
670 )
637 sortedfiles = sorted(files) 671 sortedfiles = sorted(files)
638 if extra is not None: 672 if extra is not None:
639 for name in ('p1copies', 'p2copies', 'filesadded', 'filesremoved'): 673 for name in ('p1copies', 'p2copies', 'filesadded', 'filesremoved'):
640 extra.pop(name, None) 674 extra.pop(name, None)
641 if p1copies is not None: 675 if p1copies is not None: