comparison mercurial/tags.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 4eaf7197a740
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
28 error, 28 error,
29 match as matchmod, 29 match as matchmod,
30 scmutil, 30 scmutil,
31 util, 31 util,
32 ) 32 )
33 from .utils import ( 33 from .utils import stringutil
34 stringutil,
35 )
36 34
37 # Tags computation can be expensive and caches exist to make it fast in 35 # Tags computation can be expensive and caches exist to make it fast in
38 # the common case. 36 # the common case.
39 # 37 #
40 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for 38 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
80 # Tags are written sorted by tag name. 78 # Tags are written sorted by tag name.
81 # 79 #
82 # Tags associated with multiple changesets have an entry for each changeset. 80 # Tags associated with multiple changesets have an entry for each changeset.
83 # The most recent changeset (in terms of revlog ordering for the head 81 # The most recent changeset (in terms of revlog ordering for the head
84 # setting it) for each tag is last. 82 # setting it) for each tag is last.
83
85 84
86 def fnoderevs(ui, repo, revs): 85 def fnoderevs(ui, repo, revs):
87 """return the list of '.hgtags' fnodes used in a set revisions 86 """return the list of '.hgtags' fnodes used in a set revisions
88 87
89 This is returned as list of unique fnodes. We use a list instead of a set 88 This is returned as list of unique fnodes. We use a list instead of a set
93 nodes = [tonode(r) for r in revs] 92 nodes = [tonode(r) for r in revs]
94 fnodes = _getfnodes(ui, repo, nodes) 93 fnodes = _getfnodes(ui, repo, nodes)
95 fnodes = _filterfnodes(fnodes, nodes) 94 fnodes = _filterfnodes(fnodes, nodes)
96 return fnodes 95 return fnodes
97 96
97
98 def _nulltonone(value): 98 def _nulltonone(value):
99 """convert nullid to None 99 """convert nullid to None
100 100
101 For tag value, nullid means "deleted". This small utility function helps 101 For tag value, nullid means "deleted". This small utility function helps
102 translating that to None.""" 102 translating that to None."""
103 if value == nullid: 103 if value == nullid:
104 return None 104 return None
105 return value 105 return value
106
106 107
107 def difftags(ui, repo, oldfnodes, newfnodes): 108 def difftags(ui, repo, oldfnodes, newfnodes):
108 """list differences between tags expressed in two set of file-nodes 109 """list differences between tags expressed in two set of file-nodes
109 110
110 The list contains entries in the form: (tagname, oldvalue, new value). 111 The list contains entries in the form: (tagname, oldvalue, new value).
131 old = _nulltonone(old) 132 old = _nulltonone(old)
132 if old is not None: 133 if old is not None:
133 entries.append((tag, old, None)) 134 entries.append((tag, old, None))
134 entries.sort() 135 entries.sort()
135 return entries 136 return entries
137
136 138
137 def writediff(fp, difflist): 139 def writediff(fp, difflist):
138 """write tags diff information to a file. 140 """write tags diff information to a file.
139 141
140 Data are stored with a line based format: 142 Data are stored with a line based format:
170 fp.write(remove % (old, tag)) 172 fp.write(remove % (old, tag))
171 else: 173 else:
172 fp.write(updateold % (old, tag)) 174 fp.write(updateold % (old, tag))
173 fp.write(updatenew % (new, tag)) 175 fp.write(updatenew % (new, tag))
174 176
177
175 def findglobaltags(ui, repo): 178 def findglobaltags(ui, repo):
176 '''Find global tags in a repo: return a tagsmap 179 '''Find global tags in a repo: return a tagsmap
177 180
178 tagsmap: tag name to (node, hist) 2-tuples. 181 tagsmap: tag name to (node, hist) 2-tuples.
179 182
188 alltags = {} 191 alltags = {}
189 _updatetags(cachetags, alltags) 192 _updatetags(cachetags, alltags)
190 return alltags 193 return alltags
191 194
192 for head in reversed(heads): # oldest to newest 195 for head in reversed(heads): # oldest to newest
193 assert head in repo.changelog.nodemap, ( 196 assert (
194 "tag cache returned bogus head %s" % short(head)) 197 head in repo.changelog.nodemap
198 ), "tag cache returned bogus head %s" % short(head)
195 fnodes = _filterfnodes(tagfnode, reversed(heads)) 199 fnodes = _filterfnodes(tagfnode, reversed(heads))
196 alltags = _tagsfromfnodes(ui, repo, fnodes) 200 alltags = _tagsfromfnodes(ui, repo, fnodes)
197 201
198 # and update the cache (if necessary) 202 # and update the cache (if necessary)
199 if shouldwrite: 203 if shouldwrite:
200 _writetagcache(ui, repo, valid, alltags) 204 _writetagcache(ui, repo, valid, alltags)
201 return alltags 205 return alltags
206
202 207
203 def _filterfnodes(tagfnode, nodes): 208 def _filterfnodes(tagfnode, nodes):
204 """return a list of unique fnodes 209 """return a list of unique fnodes
205 210
206 The order of this list matches the order of "nodes". Preserving this order 211 The order of this list matches the order of "nodes". Preserving this order
213 if fnode and fnode not in seen: 218 if fnode and fnode not in seen:
214 seen.add(fnode) 219 seen.add(fnode)
215 fnodes.append(fnode) 220 fnodes.append(fnode)
216 return fnodes 221 return fnodes
217 222
223
218 def _tagsfromfnodes(ui, repo, fnodes): 224 def _tagsfromfnodes(ui, repo, fnodes):
219 """return a tagsmap from a list of file-node 225 """return a tagsmap from a list of file-node
220 226
221 tagsmap: tag name to (node, hist) 2-tuples. 227 tagsmap: tag name to (node, hist) 2-tuples.
222 228
230 fctx = fctx.filectx(fnode) 236 fctx = fctx.filectx(fnode)
231 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx) 237 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
232 _updatetags(filetags, alltags) 238 _updatetags(filetags, alltags)
233 return alltags 239 return alltags
234 240
241
235 def readlocaltags(ui, repo, alltags, tagtypes): 242 def readlocaltags(ui, repo, alltags, tagtypes):
236 '''Read local tags in repo. Update alltags and tagtypes.''' 243 '''Read local tags in repo. Update alltags and tagtypes.'''
237 try: 244 try:
238 data = repo.vfs.read("localtags") 245 data = repo.vfs.read("localtags")
239 except IOError as inst: 246 except IOError as inst:
242 return 249 return
243 250
244 # localtags is in the local encoding; re-encode to UTF-8 on 251 # localtags is in the local encoding; re-encode to UTF-8 on
245 # input for consistency with the rest of this module. 252 # input for consistency with the rest of this module.
246 filetags = _readtags( 253 filetags = _readtags(
247 ui, repo, data.splitlines(), "localtags", 254 ui, repo, data.splitlines(), "localtags", recode=encoding.fromlocal
248 recode=encoding.fromlocal) 255 )
249 256
250 # remove tags pointing to invalid nodes 257 # remove tags pointing to invalid nodes
251 cl = repo.changelog 258 cl = repo.changelog
252 for t in list(filetags): 259 for t in list(filetags):
253 try: 260 try:
254 cl.rev(filetags[t][0]) 261 cl.rev(filetags[t][0])
255 except (LookupError, ValueError): 262 except (LookupError, ValueError):
256 del filetags[t] 263 del filetags[t]
257 264
258 _updatetags(filetags, alltags, 'local', tagtypes) 265 _updatetags(filetags, alltags, 'local', tagtypes)
266
259 267
260 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False): 268 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
261 '''Read tag definitions from a file (or any source of lines). 269 '''Read tag definitions from a file (or any source of lines).
262 270
263 This function returns two sortdicts with similar information: 271 This function returns two sortdicts with similar information:
312 if name not in bintaghist: 320 if name not in bintaghist:
313 bintaghist[name] = [] 321 bintaghist[name] = []
314 bintaghist[name].append(nodebin) 322 bintaghist[name].append(nodebin)
315 return bintaghist, hextaglines 323 return bintaghist, hextaglines
316 324
325
317 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False): 326 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
318 '''Read tag definitions from a file (or any source of lines). 327 '''Read tag definitions from a file (or any source of lines).
319 328
320 Returns a mapping from tag name to (node, hist). 329 Returns a mapping from tag name to (node, hist).
321 330
322 "node" is the node id from the last line read for that name. "hist" 331 "node" is the node id from the last line read for that name. "hist"
323 is the list of node ids previously associated with it (in file order). 332 is the list of node ids previously associated with it (in file order).
324 All node ids are binary, not hex. 333 All node ids are binary, not hex.
325 ''' 334 '''
326 filetags, nodelines = _readtaghist(ui, repo, lines, fn, recode=recode, 335 filetags, nodelines = _readtaghist(
327 calcnodelines=calcnodelines) 336 ui, repo, lines, fn, recode=recode, calcnodelines=calcnodelines
337 )
328 # util.sortdict().__setitem__ is much slower at replacing then inserting 338 # util.sortdict().__setitem__ is much slower at replacing then inserting
329 # new entries. The difference can matter if there are thousands of tags. 339 # new entries. The difference can matter if there are thousands of tags.
330 # Create a new sortdict to avoid the performance penalty. 340 # Create a new sortdict to avoid the performance penalty.
331 newtags = util.sortdict() 341 newtags = util.sortdict()
332 for tag, taghist in filetags.items(): 342 for tag, taghist in filetags.items():
333 newtags[tag] = (taghist[-1], taghist[:-1]) 343 newtags[tag] = (taghist[-1], taghist[:-1])
334 return newtags 344 return newtags
335 345
346
336 def _updatetags(filetags, alltags, tagtype=None, tagtypes=None): 347 def _updatetags(filetags, alltags, tagtype=None, tagtypes=None):
337 """Incorporate the tag info read from one file into dictionnaries 348 """Incorporate the tag info read from one file into dictionnaries
338 349
339 The first one, 'alltags', is a "tagmaps" (see 'findglobaltags' for details). 350 The first one, 'alltags', is a "tagmaps" (see 'findglobaltags' for details).
340 351
355 # it supersedes us OR 366 # it supersedes us OR
356 # mutual supersedes and it has a higher rank 367 # mutual supersedes and it has a higher rank
357 # otherwise we win because we're tip-most 368 # otherwise we win because we're tip-most
358 anode, ahist = nodehist 369 anode, ahist = nodehist
359 bnode, bhist = alltags[name] 370 bnode, bhist = alltags[name]
360 if (bnode != anode and anode in bhist and 371 if (
361 (bnode not in ahist or len(bhist) > len(ahist))): 372 bnode != anode
373 and anode in bhist
374 and (bnode not in ahist or len(bhist) > len(ahist))
375 ):
362 anode = bnode 376 anode = bnode
363 elif tagtype is not None: 377 elif tagtype is not None:
364 tagtypes[name] = tagtype 378 tagtypes[name] = tagtype
365 ahist.extend([n for n in bhist if n not in ahist]) 379 ahist.extend([n for n in bhist if n not in ahist])
366 alltags[name] = anode, ahist 380 alltags[name] = anode, ahist
381
367 382
368 def _filename(repo): 383 def _filename(repo):
369 """name of a tagcache file for a given repo or repoview""" 384 """name of a tagcache file for a given repo or repoview"""
370 filename = 'tags2' 385 filename = 'tags2'
371 if repo.filtername: 386 if repo.filtername:
372 filename = '%s-%s' % (filename, repo.filtername) 387 filename = '%s-%s' % (filename, repo.filtername)
373 return filename 388 return filename
389
374 390
375 def _readtagcache(ui, repo): 391 def _readtagcache(ui, repo):
376 '''Read the tag cache. 392 '''Read the tag cache.
377 393
378 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite). 394 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
417 433
418 # Case 1 (common): tip is the same, so nothing has changed. 434 # Case 1 (common): tip is the same, so nothing has changed.
419 # (Unchanged tip trivially means no changesets have been added. 435 # (Unchanged tip trivially means no changesets have been added.
420 # But, thanks to localrepository.destroyed(), it also means none 436 # But, thanks to localrepository.destroyed(), it also means none
421 # have been destroyed by strip or rollback.) 437 # have been destroyed by strip or rollback.)
422 if (cacherev == tiprev 438 if (
423 and cachenode == tipnode 439 cacherev == tiprev
424 and cachehash == scmutil.filteredhash(repo, tiprev)): 440 and cachenode == tipnode
441 and cachehash == scmutil.filteredhash(repo, tiprev)
442 ):
425 tags = _readtags(ui, repo, cachelines, cachefile.name) 443 tags = _readtags(ui, repo, cachelines, cachefile.name)
426 cachefile.close() 444 cachefile.close()
427 return (None, None, None, tags, False) 445 return (None, None, None, tags, False)
428 if cachefile: 446 if cachefile:
429 cachefile.close() # ignore rest of file 447 cachefile.close() # ignore rest of file
430 448
431 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev)) 449 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
432 450
433 repoheads = repo.heads() 451 repoheads = repo.heads()
434 # Case 2 (uncommon): empty repo; get out quickly and don't bother 452 # Case 2 (uncommon): empty repo; get out quickly and don't bother
452 if not len(repo.file('.hgtags')): 470 if not len(repo.file('.hgtags')):
453 # No tags have ever been committed, so we can avoid a 471 # No tags have ever been committed, so we can avoid a
454 # potentially expensive search. 472 # potentially expensive search.
455 return ([], {}, valid, None, True) 473 return ([], {}, valid, None, True)
456 474
457
458 # Now we have to lookup the .hgtags filenode for every new head. 475 # Now we have to lookup the .hgtags filenode for every new head.
459 # This is the most expensive part of finding tags, so performance 476 # This is the most expensive part of finding tags, so performance
460 # depends primarily on the size of newheads. Worst case: no cache 477 # depends primarily on the size of newheads. Worst case: no cache
461 # file, so newheads == repoheads. 478 # file, so newheads == repoheads.
462 # Reversed order helps the cache ('repoheads' is in descending order) 479 # Reversed order helps the cache ('repoheads' is in descending order)
463 cachefnode = _getfnodes(ui, repo, reversed(repoheads)) 480 cachefnode = _getfnodes(ui, repo, reversed(repoheads))
464 481
465 # Caller has to iterate over all heads, but can use the filenodes in 482 # Caller has to iterate over all heads, but can use the filenodes in
466 # cachefnode to get to each .hgtags revision quickly. 483 # cachefnode to get to each .hgtags revision quickly.
467 return (repoheads, cachefnode, valid, None, True) 484 return (repoheads, cachefnode, valid, None, True)
485
468 486
469 def _getfnodes(ui, repo, nodes): 487 def _getfnodes(ui, repo, nodes):
470 """return .hgtags fnodes for a list of changeset nodes 488 """return .hgtags fnodes for a list of changeset nodes
471 489
472 Return value is a {node: fnode} mapping. There will be no entry for nodes 490 Return value is a {node: fnode} mapping. There will be no entry for nodes
481 cachefnode[node] = fnode 499 cachefnode[node] = fnode
482 500
483 fnodescache.write() 501 fnodescache.write()
484 502
485 duration = util.timer() - starttime 503 duration = util.timer() - starttime
486 ui.log('tagscache', 504 ui.log(
487 '%d/%d cache hits/lookups in %0.4f seconds\n', 505 'tagscache',
488 fnodescache.hitcount, fnodescache.lookupcount, duration) 506 '%d/%d cache hits/lookups in %0.4f seconds\n',
507 fnodescache.hitcount,
508 fnodescache.lookupcount,
509 duration,
510 )
489 return cachefnode 511 return cachefnode
512
490 513
491 def _writetagcache(ui, repo, valid, cachetags): 514 def _writetagcache(ui, repo, valid, cachetags):
492 filename = _filename(repo) 515 filename = _filename(repo)
493 try: 516 try:
494 cachefile = repo.cachevfs(filename, 'w', atomictemp=True) 517 cachefile = repo.cachevfs(filename, 'w', atomictemp=True)
495 except (OSError, IOError): 518 except (OSError, IOError):
496 return 519 return
497 520
498 ui.log('tagscache', 'writing .hg/cache/%s with %d tags\n', 521 ui.log(
499 filename, len(cachetags)) 522 'tagscache',
523 'writing .hg/cache/%s with %d tags\n',
524 filename,
525 len(cachetags),
526 )
500 527
501 if valid[2]: 528 if valid[2]:
502 cachefile.write('%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2]))) 529 cachefile.write('%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2])))
503 else: 530 else:
504 cachefile.write('%d %s\n' % (valid[0], hex(valid[1]))) 531 cachefile.write('%d %s\n' % (valid[0], hex(valid[1])))
515 try: 542 try:
516 cachefile.close() 543 cachefile.close()
517 except (OSError, IOError): 544 except (OSError, IOError):
518 pass 545 pass
519 546
547
520 def tag(repo, names, node, message, local, user, date, editor=False): 548 def tag(repo, names, node, message, local, user, date, editor=False):
521 '''tag a revision with one or more symbolic names. 549 '''tag a revision with one or more symbolic names.
522 550
523 names is a list of strings or, when adding a single tag, names may be a 551 names is a list of strings or, when adding a single tag, names may be a
524 string. 552 string.
539 date: date tuple to use if committing''' 567 date: date tuple to use if committing'''
540 568
541 if not local: 569 if not local:
542 m = matchmod.exact(['.hgtags']) 570 m = matchmod.exact(['.hgtags'])
543 if any(repo.status(match=m, unknown=True, ignored=True)): 571 if any(repo.status(match=m, unknown=True, ignored=True)):
544 raise error.Abort(_('working copy of .hgtags is changed'), 572 raise error.Abort(
545 hint=_('please commit .hgtags manually')) 573 _('working copy of .hgtags is changed'),
574 hint=_('please commit .hgtags manually'),
575 )
546 576
547 with repo.wlock(): 577 with repo.wlock():
548 repo.tags() # instantiate the cache 578 repo.tags() # instantiate the cache
549 _tag(repo, names, node, message, local, user, date, 579 _tag(repo, names, node, message, local, user, date, editor=editor)
550 editor=editor) 580
551 581
552 def _tag(repo, names, node, message, local, user, date, extra=None, 582 def _tag(
553 editor=False): 583 repo, names, node, message, local, user, date, extra=None, editor=False
584 ):
554 if isinstance(names, bytes): 585 if isinstance(names, bytes):
555 names = (names,) 586 names = (names,)
556 587
557 branches = repo.branchmap() 588 branches = repo.branchmap()
558 for name in names: 589 for name in names:
559 repo.hook('pretag', throw=True, node=hex(node), tag=name, 590 repo.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
560 local=local)
561 if name in branches: 591 if name in branches:
562 repo.ui.warn(_("warning: tag %s conflicts with existing" 592 repo.ui.warn(
563 " branch name\n") % name) 593 _("warning: tag %s conflicts with existing" " branch name\n")
594 % name
595 )
564 596
565 def writetags(fp, names, munge, prevtags): 597 def writetags(fp, names, munge, prevtags):
566 fp.seek(0, io.SEEK_END) 598 fp.seek(0, io.SEEK_END)
567 if prevtags and not prevtags.endswith('\n'): 599 if prevtags and not prevtags.endswith('\n'):
568 fp.write('\n') 600 fp.write('\n')
570 if munge: 602 if munge:
571 m = munge(name) 603 m = munge(name)
572 else: 604 else:
573 m = name 605 m = name
574 606
575 if (repo._tagscache.tagtypes and 607 if repo._tagscache.tagtypes and name in repo._tagscache.tagtypes:
576 name in repo._tagscache.tagtypes):
577 old = repo.tags().get(name, nullid) 608 old = repo.tags().get(name, nullid)
578 fp.write('%s %s\n' % (hex(old), m)) 609 fp.write('%s %s\n' % (hex(old), m))
579 fp.write('%s %s\n' % (hex(node), m)) 610 fp.write('%s %s\n' % (hex(node), m))
580 fp.close() 611 fp.close()
581 612
612 643
613 if '.hgtags' not in repo.dirstate: 644 if '.hgtags' not in repo.dirstate:
614 repo[None].add(['.hgtags']) 645 repo[None].add(['.hgtags'])
615 646
616 m = matchmod.exact(['.hgtags']) 647 m = matchmod.exact(['.hgtags'])
617 tagnode = repo.commit(message, user, date, extra=extra, match=m, 648 tagnode = repo.commit(
618 editor=editor) 649 message, user, date, extra=extra, match=m, editor=editor
650 )
619 651
620 for name in names: 652 for name in names:
621 repo.hook('tag', node=hex(node), tag=name, local=local) 653 repo.hook('tag', node=hex(node), tag=name, local=local)
622 654
623 return tagnode 655 return tagnode
624 656
657
625 _fnodescachefile = 'hgtagsfnodes1' 658 _fnodescachefile = 'hgtagsfnodes1'
626 _fnodesrecsize = 4 + 20 # changeset fragment + filenode 659 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
627 _fnodesmissingrec = '\xff' * 24 660 _fnodesmissingrec = '\xff' * 24
661
628 662
629 class hgtagsfnodescache(object): 663 class hgtagsfnodescache(object):
630 """Persistent cache mapping revisions to .hgtags filenodes. 664 """Persistent cache mapping revisions to .hgtags filenodes.
631 665
632 The cache is an array of records. Each item in the array corresponds to 666 The cache is an array of records. Each item in the array corresponds to
643 only parsed on read. 677 only parsed on read.
644 678
645 Instances behave like lists. ``c[i]`` works where i is a rev or 679 Instances behave like lists. ``c[i]`` works where i is a rev or
646 changeset node. Missing indexes are populated automatically on access. 680 changeset node. Missing indexes are populated automatically on access.
647 """ 681 """
682
648 def __init__(self, repo): 683 def __init__(self, repo):
649 assert repo.filtername is None 684 assert repo.filtername is None
650 685
651 self._repo = repo 686 self._repo = repo
652 687
653 # Only for reporting purposes. 688 # Only for reporting purposes.
654 self.lookupcount = 0 689 self.lookupcount = 0
655 self.hitcount = 0 690 self.hitcount = 0
656
657 691
658 try: 692 try:
659 data = repo.cachevfs.read(_fnodescachefile) 693 data = repo.cachevfs.read(_fnodescachefile)
660 except (OSError, IOError): 694 except (OSError, IOError):
661 data = "" 695 data = ""
701 rev = ctx.rev() 735 rev = ctx.rev()
702 736
703 self.lookupcount += 1 737 self.lookupcount += 1
704 738
705 offset = rev * _fnodesrecsize 739 offset = rev * _fnodesrecsize
706 record = '%s' % self._raw[offset:offset + _fnodesrecsize] 740 record = '%s' % self._raw[offset : offset + _fnodesrecsize]
707 properprefix = node[0:4] 741 properprefix = node[0:4]
708 742
709 # Validate and return existing entry. 743 # Validate and return existing entry.
710 if record != _fnodesmissingrec: 744 if record != _fnodesmissingrec:
711 fileprefix = record[0:4] 745 fileprefix = record[0:4]
764 self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode) 798 self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode)
765 799
766 def _writeentry(self, offset, prefix, fnode): 800 def _writeentry(self, offset, prefix, fnode):
767 # Slices on array instances only accept other array. 801 # Slices on array instances only accept other array.
768 entry = bytearray(prefix + fnode) 802 entry = bytearray(prefix + fnode)
769 self._raw[offset:offset + _fnodesrecsize] = entry 803 self._raw[offset : offset + _fnodesrecsize] = entry
770 # self._dirtyoffset could be None. 804 # self._dirtyoffset could be None.
771 self._dirtyoffset = min(self._dirtyoffset or 0, offset or 0) 805 self._dirtyoffset = min(self._dirtyoffset or 0, offset or 0)
772 806
773 def write(self): 807 def write(self):
774 """Perform all necessary writes to cache file. 808 """Perform all necessary writes to cache file.
777 not be obtained. 811 not be obtained.
778 """ 812 """
779 if self._dirtyoffset is None: 813 if self._dirtyoffset is None:
780 return 814 return
781 815
782 data = self._raw[self._dirtyoffset:] 816 data = self._raw[self._dirtyoffset :]
783 if not data: 817 if not data:
784 return 818 return
785 819
786 repo = self._repo 820 repo = self._repo
787 821
788 try: 822 try:
789 lock = repo.wlock(wait=False) 823 lock = repo.wlock(wait=False)
790 except error.LockError: 824 except error.LockError:
791 repo.ui.log('tagscache', 'not writing .hg/cache/%s because ' 825 repo.ui.log(
792 'lock cannot be acquired\n' % (_fnodescachefile)) 826 'tagscache',
827 'not writing .hg/cache/%s because '
828 'lock cannot be acquired\n' % _fnodescachefile,
829 )
793 return 830 return
794 831
795 try: 832 try:
796 f = repo.cachevfs.open(_fnodescachefile, 'ab') 833 f = repo.cachevfs.open(_fnodescachefile, 'ab')
797 try: 834 try:
798 # if the file has been truncated 835 # if the file has been truncated
799 actualoffset = f.tell() 836 actualoffset = f.tell()
800 if actualoffset < self._dirtyoffset: 837 if actualoffset < self._dirtyoffset:
801 self._dirtyoffset = actualoffset 838 self._dirtyoffset = actualoffset
802 data = self._raw[self._dirtyoffset:] 839 data = self._raw[self._dirtyoffset :]
803 f.seek(self._dirtyoffset) 840 f.seek(self._dirtyoffset)
804 f.truncate() 841 f.truncate()
805 repo.ui.log('tagscache', 842 repo.ui.log(
806 'writing %d bytes to cache/%s\n' % ( 843 'tagscache',
807 len(data), _fnodescachefile)) 844 'writing %d bytes to cache/%s\n'
845 % (len(data), _fnodescachefile),
846 )
808 f.write(data) 847 f.write(data)
809 self._dirtyoffset = None 848 self._dirtyoffset = None
810 finally: 849 finally:
811 f.close() 850 f.close()
812 except (IOError, OSError) as inst: 851 except (IOError, OSError) as inst:
813 repo.ui.log('tagscache', 852 repo.ui.log(
814 "couldn't write cache/%s: %s\n" % ( 853 'tagscache',
815 _fnodescachefile, stringutil.forcebytestr(inst))) 854 "couldn't write cache/%s: %s\n"
855 % (_fnodescachefile, stringutil.forcebytestr(inst)),
856 )
816 finally: 857 finally:
817 lock.release() 858 lock.release()