Mercurial > hg
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: |