Mercurial > hg
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() |