##// END OF EJS Templates
tags: fix typo in fast path detection of fnode resolution (issue6673)...
Yuya Nishihara -
r49846:d4b66dc5 stable
parent child Browse files
Show More
@@ -1,915 +1,915 b''
1 1 # tags.py - read tag info from local repository
2 2 #
3 3 # Copyright 2009 Olivia Mackall <olivia@selenic.com>
4 4 # Copyright 2009 Greg Ward <greg@gerg.ca>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 # Currently this module only deals with reading and caching tags.
10 10 # Eventually, it could take care of updating (adding/removing/moving)
11 11 # tags too.
12 12
13 13 from __future__ import absolute_import
14 14
15 15 import errno
16 16 import io
17 17
18 18 from .node import (
19 19 bin,
20 20 hex,
21 21 nullrev,
22 22 short,
23 23 )
24 24 from .i18n import _
25 25 from . import (
26 26 encoding,
27 27 error,
28 28 match as matchmod,
29 29 pycompat,
30 30 scmutil,
31 31 util,
32 32 )
33 33 from .utils import stringutil
34 34
35 35 # Tags computation can be expensive and caches exist to make it fast in
36 36 # the common case.
37 37 #
38 38 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
39 39 # each revision in the repository. The file is effectively an array of
40 40 # fixed length records. Read the docs for "hgtagsfnodescache" for technical
41 41 # details.
42 42 #
43 43 # The .hgtags filenode cache grows in proportion to the length of the
44 44 # changelog. The file is truncated when the # changelog is stripped.
45 45 #
46 46 # The purpose of the filenode cache is to avoid the most expensive part
47 47 # of finding global tags, which is looking up the .hgtags filenode in the
48 48 # manifest for each head. This can take dozens or over 100ms for
49 49 # repositories with very large manifests. Multiplied by dozens or even
50 50 # hundreds of heads and there is a significant performance concern.
51 51 #
52 52 # There also exist a separate cache file for each repository filter.
53 53 # These "tags-*" files store information about the history of tags.
54 54 #
55 55 # The tags cache files consists of a cache validation line followed by
56 56 # a history of tags.
57 57 #
58 58 # The cache validation line has the format:
59 59 #
60 60 # <tiprev> <tipnode> [<filteredhash>]
61 61 #
62 62 # <tiprev> is an integer revision and <tipnode> is a 40 character hex
63 63 # node for that changeset. These redundantly identify the repository
64 64 # tip from the time the cache was written. In addition, <filteredhash>,
65 65 # if present, is a 40 character hex hash of the contents of the filtered
66 66 # revisions for this filter. If the set of filtered revs changes, the
67 67 # hash will change and invalidate the cache.
68 68 #
69 69 # The history part of the tags cache consists of lines of the form:
70 70 #
71 71 # <node> <tag>
72 72 #
73 73 # (This format is identical to that of .hgtags files.)
74 74 #
75 75 # <tag> is the tag name and <node> is the 40 character hex changeset
76 76 # the tag is associated with.
77 77 #
78 78 # Tags are written sorted by tag name.
79 79 #
80 80 # Tags associated with multiple changesets have an entry for each changeset.
81 81 # The most recent changeset (in terms of revlog ordering for the head
82 82 # setting it) for each tag is last.
83 83
84 84
85 85 def fnoderevs(ui, repo, revs):
86 86 """return the list of '.hgtags' fnodes used in a set revisions
87 87
88 88 This is returned as list of unique fnodes. We use a list instead of a set
89 89 because order matters when it comes to tags."""
90 90 unfi = repo.unfiltered()
91 91 tonode = unfi.changelog.node
92 92 nodes = [tonode(r) for r in revs]
93 93 fnodes = _getfnodes(ui, repo, nodes)
94 94 fnodes = _filterfnodes(fnodes, nodes)
95 95 return fnodes
96 96
97 97
98 98 def _nulltonone(repo, value):
99 99 """convert nullid to None
100 100
101 101 For tag value, nullid means "deleted". This small utility function helps
102 102 translating that to None."""
103 103 if value == repo.nullid:
104 104 return None
105 105 return value
106 106
107 107
108 108 def difftags(ui, repo, oldfnodes, newfnodes):
109 109 """list differences between tags expressed in two set of file-nodes
110 110
111 111 The list contains entries in the form: (tagname, oldvalue, new value).
112 112 None is used to expressed missing value:
113 113 ('foo', None, 'abcd') is a new tag,
114 114 ('bar', 'ef01', None) is a deletion,
115 115 ('baz', 'abcd', 'ef01') is a tag movement.
116 116 """
117 117 if oldfnodes == newfnodes:
118 118 return []
119 119 oldtags = _tagsfromfnodes(ui, repo, oldfnodes)
120 120 newtags = _tagsfromfnodes(ui, repo, newfnodes)
121 121
122 122 # list of (tag, old, new): None means missing
123 123 entries = []
124 124 for tag, (new, __) in newtags.items():
125 125 new = _nulltonone(repo, new)
126 126 old, __ = oldtags.pop(tag, (None, None))
127 127 old = _nulltonone(repo, old)
128 128 if old != new:
129 129 entries.append((tag, old, new))
130 130 # handle deleted tags
131 131 for tag, (old, __) in oldtags.items():
132 132 old = _nulltonone(repo, old)
133 133 if old is not None:
134 134 entries.append((tag, old, None))
135 135 entries.sort()
136 136 return entries
137 137
138 138
139 139 def writediff(fp, difflist):
140 140 """write tags diff information to a file.
141 141
142 142 Data are stored with a line based format:
143 143
144 144 <action> <hex-node> <tag-name>\n
145 145
146 146 Action are defined as follow:
147 147 -R tag is removed,
148 148 +A tag is added,
149 149 -M tag is moved (old value),
150 150 +M tag is moved (new value),
151 151
152 152 Example:
153 153
154 154 +A 875517b4806a848f942811a315a5bce30804ae85 t5
155 155
156 156 See documentation of difftags output for details about the input.
157 157 """
158 158 add = b'+A %s %s\n'
159 159 remove = b'-R %s %s\n'
160 160 updateold = b'-M %s %s\n'
161 161 updatenew = b'+M %s %s\n'
162 162 for tag, old, new in difflist:
163 163 # translate to hex
164 164 if old is not None:
165 165 old = hex(old)
166 166 if new is not None:
167 167 new = hex(new)
168 168 # write to file
169 169 if old is None:
170 170 fp.write(add % (new, tag))
171 171 elif new is None:
172 172 fp.write(remove % (old, tag))
173 173 else:
174 174 fp.write(updateold % (old, tag))
175 175 fp.write(updatenew % (new, tag))
176 176
177 177
178 178 def findglobaltags(ui, repo):
179 179 """Find global tags in a repo: return a tagsmap
180 180
181 181 tagsmap: tag name to (node, hist) 2-tuples.
182 182
183 183 The tags cache is read and updated as a side-effect of calling.
184 184 """
185 185 (heads, tagfnode, valid, cachetags, shouldwrite) = _readtagcache(ui, repo)
186 186 if cachetags is not None:
187 187 assert not shouldwrite
188 188 # XXX is this really 100% correct? are there oddball special
189 189 # cases where a global tag should outrank a local tag but won't,
190 190 # because cachetags does not contain rank info?
191 191 alltags = {}
192 192 _updatetags(cachetags, alltags)
193 193 return alltags
194 194
195 195 for head in reversed(heads): # oldest to newest
196 196 assert repo.changelog.index.has_node(
197 197 head
198 198 ), b"tag cache returned bogus head %s" % short(head)
199 199 fnodes = _filterfnodes(tagfnode, reversed(heads))
200 200 alltags = _tagsfromfnodes(ui, repo, fnodes)
201 201
202 202 # and update the cache (if necessary)
203 203 if shouldwrite:
204 204 _writetagcache(ui, repo, valid, alltags)
205 205 return alltags
206 206
207 207
208 208 def _filterfnodes(tagfnode, nodes):
209 209 """return a list of unique fnodes
210 210
211 211 The order of this list matches the order of "nodes". Preserving this order
212 212 is important as reading tags in different order provides different
213 213 results."""
214 214 seen = set() # set of fnode
215 215 fnodes = []
216 216 for no in nodes: # oldest to newest
217 217 fnode = tagfnode.get(no)
218 218 if fnode and fnode not in seen:
219 219 seen.add(fnode)
220 220 fnodes.append(fnode)
221 221 return fnodes
222 222
223 223
224 224 def _tagsfromfnodes(ui, repo, fnodes):
225 225 """return a tagsmap from a list of file-node
226 226
227 227 tagsmap: tag name to (node, hist) 2-tuples.
228 228
229 229 The order of the list matters."""
230 230 alltags = {}
231 231 fctx = None
232 232 for fnode in fnodes:
233 233 if fctx is None:
234 234 fctx = repo.filectx(b'.hgtags', fileid=fnode)
235 235 else:
236 236 fctx = fctx.filectx(fnode)
237 237 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
238 238 _updatetags(filetags, alltags)
239 239 return alltags
240 240
241 241
242 242 def readlocaltags(ui, repo, alltags, tagtypes):
243 243 '''Read local tags in repo. Update alltags and tagtypes.'''
244 244 try:
245 245 data = repo.vfs.read(b"localtags")
246 246 except IOError as inst:
247 247 if inst.errno != errno.ENOENT:
248 248 raise
249 249 return
250 250
251 251 # localtags is in the local encoding; re-encode to UTF-8 on
252 252 # input for consistency with the rest of this module.
253 253 filetags = _readtags(
254 254 ui, repo, data.splitlines(), b"localtags", recode=encoding.fromlocal
255 255 )
256 256
257 257 # remove tags pointing to invalid nodes
258 258 cl = repo.changelog
259 259 for t in list(filetags):
260 260 try:
261 261 cl.rev(filetags[t][0])
262 262 except (LookupError, ValueError):
263 263 del filetags[t]
264 264
265 265 _updatetags(filetags, alltags, b'local', tagtypes)
266 266
267 267
268 268 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
269 269 """Read tag definitions from a file (or any source of lines).
270 270
271 271 This function returns two sortdicts with similar information:
272 272
273 273 - the first dict, bintaghist, contains the tag information as expected by
274 274 the _readtags function, i.e. a mapping from tag name to (node, hist):
275 275 - node is the node id from the last line read for that name,
276 276 - hist is the list of node ids previously associated with it (in file
277 277 order). All node ids are binary, not hex.
278 278
279 279 - the second dict, hextaglines, is a mapping from tag name to a list of
280 280 [hexnode, line number] pairs, ordered from the oldest to the newest node.
281 281
282 282 When calcnodelines is False the hextaglines dict is not calculated (an
283 283 empty dict is returned). This is done to improve this function's
284 284 performance in cases where the line numbers are not needed.
285 285 """
286 286
287 287 bintaghist = util.sortdict()
288 288 hextaglines = util.sortdict()
289 289 count = 0
290 290
291 291 def dbg(msg):
292 292 ui.debug(b"%s, line %d: %s\n" % (fn, count, msg))
293 293
294 294 for nline, line in enumerate(lines):
295 295 count += 1
296 296 if not line:
297 297 continue
298 298 try:
299 299 (nodehex, name) = line.split(b" ", 1)
300 300 except ValueError:
301 301 dbg(b"cannot parse entry")
302 302 continue
303 303 name = name.strip()
304 304 if recode:
305 305 name = recode(name)
306 306 try:
307 307 nodebin = bin(nodehex)
308 308 except TypeError:
309 309 dbg(b"node '%s' is not well formed" % nodehex)
310 310 continue
311 311
312 312 # update filetags
313 313 if calcnodelines:
314 314 # map tag name to a list of line numbers
315 315 if name not in hextaglines:
316 316 hextaglines[name] = []
317 317 hextaglines[name].append([nodehex, nline])
318 318 continue
319 319 # map tag name to (node, hist)
320 320 if name not in bintaghist:
321 321 bintaghist[name] = []
322 322 bintaghist[name].append(nodebin)
323 323 return bintaghist, hextaglines
324 324
325 325
326 326 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
327 327 """Read tag definitions from a file (or any source of lines).
328 328
329 329 Returns a mapping from tag name to (node, hist).
330 330
331 331 "node" is the node id from the last line read for that name. "hist"
332 332 is the list of node ids previously associated with it (in file order).
333 333 All node ids are binary, not hex.
334 334 """
335 335 filetags, nodelines = _readtaghist(
336 336 ui, repo, lines, fn, recode=recode, calcnodelines=calcnodelines
337 337 )
338 338 # util.sortdict().__setitem__ is much slower at replacing then inserting
339 339 # new entries. The difference can matter if there are thousands of tags.
340 340 # Create a new sortdict to avoid the performance penalty.
341 341 newtags = util.sortdict()
342 342 for tag, taghist in filetags.items():
343 343 newtags[tag] = (taghist[-1], taghist[:-1])
344 344 return newtags
345 345
346 346
347 347 def _updatetags(filetags, alltags, tagtype=None, tagtypes=None):
348 348 """Incorporate the tag info read from one file into dictionnaries
349 349
350 350 The first one, 'alltags', is a "tagmaps" (see 'findglobaltags' for details).
351 351
352 352 The second one, 'tagtypes', is optional and will be updated to track the
353 353 "tagtype" of entries in the tagmaps. When set, the 'tagtype' argument also
354 354 needs to be set."""
355 355 if tagtype is None:
356 356 assert tagtypes is None
357 357
358 358 for name, nodehist in pycompat.iteritems(filetags):
359 359 if name not in alltags:
360 360 alltags[name] = nodehist
361 361 if tagtype is not None:
362 362 tagtypes[name] = tagtype
363 363 continue
364 364
365 365 # we prefer alltags[name] if:
366 366 # it supersedes us OR
367 367 # mutual supersedes and it has a higher rank
368 368 # otherwise we win because we're tip-most
369 369 anode, ahist = nodehist
370 370 bnode, bhist = alltags[name]
371 371 if (
372 372 bnode != anode
373 373 and anode in bhist
374 374 and (bnode not in ahist or len(bhist) > len(ahist))
375 375 ):
376 376 anode = bnode
377 377 elif tagtype is not None:
378 378 tagtypes[name] = tagtype
379 379 ahist.extend([n for n in bhist if n not in ahist])
380 380 alltags[name] = anode, ahist
381 381
382 382
383 383 def _filename(repo):
384 384 """name of a tagcache file for a given repo or repoview"""
385 385 filename = b'tags2'
386 386 if repo.filtername:
387 387 filename = b'%s-%s' % (filename, repo.filtername)
388 388 return filename
389 389
390 390
391 391 def _readtagcache(ui, repo):
392 392 """Read the tag cache.
393 393
394 394 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
395 395
396 396 If the cache is completely up-to-date, "cachetags" is a dict of the
397 397 form returned by _readtags() and "heads", "fnodes", and "validinfo" are
398 398 None and "shouldwrite" is False.
399 399
400 400 If the cache is not up to date, "cachetags" is None. "heads" is a list
401 401 of all heads currently in the repository, ordered from tip to oldest.
402 402 "validinfo" is a tuple describing cache validation info. This is used
403 403 when writing the tags cache. "fnodes" is a mapping from head to .hgtags
404 404 filenode. "shouldwrite" is True.
405 405
406 406 If the cache is not up to date, the caller is responsible for reading tag
407 407 info from each returned head. (See findglobaltags().)
408 408 """
409 409 try:
410 410 cachefile = repo.cachevfs(_filename(repo), b'r')
411 411 # force reading the file for static-http
412 412 cachelines = iter(cachefile)
413 413 except IOError:
414 414 cachefile = None
415 415
416 416 cacherev = None
417 417 cachenode = None
418 418 cachehash = None
419 419 if cachefile:
420 420 try:
421 421 validline = next(cachelines)
422 422 validline = validline.split()
423 423 cacherev = int(validline[0])
424 424 cachenode = bin(validline[1])
425 425 if len(validline) > 2:
426 426 cachehash = bin(validline[2])
427 427 except Exception:
428 428 # corruption of the cache, just recompute it.
429 429 pass
430 430
431 431 tipnode = repo.changelog.tip()
432 432 tiprev = len(repo.changelog) - 1
433 433
434 434 # Case 1 (common): tip is the same, so nothing has changed.
435 435 # (Unchanged tip trivially means no changesets have been added.
436 436 # But, thanks to localrepository.destroyed(), it also means none
437 437 # have been destroyed by strip or rollback.)
438 438 if (
439 439 cacherev == tiprev
440 440 and cachenode == tipnode
441 441 and cachehash == scmutil.filteredhash(repo, tiprev)
442 442 ):
443 443 tags = _readtags(ui, repo, cachelines, cachefile.name)
444 444 cachefile.close()
445 445 return (None, None, None, tags, False)
446 446 if cachefile:
447 447 cachefile.close() # ignore rest of file
448 448
449 449 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
450 450
451 451 repoheads = repo.heads()
452 452 # Case 2 (uncommon): empty repo; get out quickly and don't bother
453 453 # writing an empty cache.
454 454 if repoheads == [repo.nullid]:
455 455 return ([], {}, valid, {}, False)
456 456
457 457 # Case 3 (uncommon): cache file missing or empty.
458 458
459 459 # Case 4 (uncommon): tip rev decreased. This should only happen
460 460 # when we're called from localrepository.destroyed(). Refresh the
461 461 # cache so future invocations will not see disappeared heads in the
462 462 # cache.
463 463
464 464 # Case 5 (common): tip has changed, so we've added/replaced heads.
465 465
466 466 # As it happens, the code to handle cases 3, 4, 5 is the same.
467 467
468 468 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
469 469 # exposed".
470 470 if not len(repo.file(b'.hgtags')):
471 471 # No tags have ever been committed, so we can avoid a
472 472 # potentially expensive search.
473 473 return ([], {}, valid, None, True)
474 474
475 475 # Now we have to lookup the .hgtags filenode for every new head.
476 476 # This is the most expensive part of finding tags, so performance
477 477 # depends primarily on the size of newheads. Worst case: no cache
478 478 # file, so newheads == repoheads.
479 479 # Reversed order helps the cache ('repoheads' is in descending order)
480 480 cachefnode = _getfnodes(ui, repo, reversed(repoheads))
481 481
482 482 # Caller has to iterate over all heads, but can use the filenodes in
483 483 # cachefnode to get to each .hgtags revision quickly.
484 484 return (repoheads, cachefnode, valid, None, True)
485 485
486 486
487 487 def _getfnodes(ui, repo, nodes):
488 488 """return .hgtags fnodes for a list of changeset nodes
489 489
490 490 Return value is a {node: fnode} mapping. There will be no entry for nodes
491 491 without a '.hgtags' file.
492 492 """
493 493 starttime = util.timer()
494 494 fnodescache = hgtagsfnodescache(repo.unfiltered())
495 495 cachefnode = {}
496 496 validated_fnodes = set()
497 497 unknown_entries = set()
498 498 for node in nodes:
499 499 fnode = fnodescache.getfnode(node)
500 500 flog = repo.file(b'.hgtags')
501 501 if fnode != repo.nullid:
502 502 if fnode not in validated_fnodes:
503 503 if flog.hasnode(fnode):
504 504 validated_fnodes.add(fnode)
505 505 else:
506 506 unknown_entries.add(node)
507 507 cachefnode[node] = fnode
508 508
509 509 if unknown_entries:
510 510 fixed_nodemap = fnodescache.refresh_invalid_nodes(unknown_entries)
511 511 for node, fnode in pycompat.iteritems(fixed_nodemap):
512 512 if fnode != repo.nullid:
513 513 cachefnode[node] = fnode
514 514
515 515 fnodescache.write()
516 516
517 517 duration = util.timer() - starttime
518 518 ui.log(
519 519 b'tagscache',
520 520 b'%d/%d cache hits/lookups in %0.4f seconds\n',
521 521 fnodescache.hitcount,
522 522 fnodescache.lookupcount,
523 523 duration,
524 524 )
525 525 return cachefnode
526 526
527 527
528 528 def _writetagcache(ui, repo, valid, cachetags):
529 529 filename = _filename(repo)
530 530 try:
531 531 cachefile = repo.cachevfs(filename, b'w', atomictemp=True)
532 532 except (OSError, IOError):
533 533 return
534 534
535 535 ui.log(
536 536 b'tagscache',
537 537 b'writing .hg/cache/%s with %d tags\n',
538 538 filename,
539 539 len(cachetags),
540 540 )
541 541
542 542 if valid[2]:
543 543 cachefile.write(
544 544 b'%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2]))
545 545 )
546 546 else:
547 547 cachefile.write(b'%d %s\n' % (valid[0], hex(valid[1])))
548 548
549 549 # Tag names in the cache are in UTF-8 -- which is the whole reason
550 550 # we keep them in UTF-8 throughout this module. If we converted
551 551 # them local encoding on input, we would lose info writing them to
552 552 # the cache.
553 553 for (name, (node, hist)) in sorted(pycompat.iteritems(cachetags)):
554 554 for n in hist:
555 555 cachefile.write(b"%s %s\n" % (hex(n), name))
556 556 cachefile.write(b"%s %s\n" % (hex(node), name))
557 557
558 558 try:
559 559 cachefile.close()
560 560 except (OSError, IOError):
561 561 pass
562 562
563 563
564 564 def tag(repo, names, node, message, local, user, date, editor=False):
565 565 """tag a revision with one or more symbolic names.
566 566
567 567 names is a list of strings or, when adding a single tag, names may be a
568 568 string.
569 569
570 570 if local is True, the tags are stored in a per-repository file.
571 571 otherwise, they are stored in the .hgtags file, and a new
572 572 changeset is committed with the change.
573 573
574 574 keyword arguments:
575 575
576 576 local: whether to store tags in non-version-controlled file
577 577 (default False)
578 578
579 579 message: commit message to use if committing
580 580
581 581 user: name of user to use if committing
582 582
583 583 date: date tuple to use if committing"""
584 584
585 585 if not local:
586 586 m = matchmod.exact([b'.hgtags'])
587 587 st = repo.status(match=m, unknown=True, ignored=True)
588 588 if any(
589 589 (
590 590 st.modified,
591 591 st.added,
592 592 st.removed,
593 593 st.deleted,
594 594 st.unknown,
595 595 st.ignored,
596 596 )
597 597 ):
598 598 raise error.Abort(
599 599 _(b'working copy of .hgtags is changed'),
600 600 hint=_(b'please commit .hgtags manually'),
601 601 )
602 602
603 603 with repo.wlock():
604 604 repo.tags() # instantiate the cache
605 605 _tag(repo, names, node, message, local, user, date, editor=editor)
606 606
607 607
608 608 def _tag(
609 609 repo, names, node, message, local, user, date, extra=None, editor=False
610 610 ):
611 611 if isinstance(names, bytes):
612 612 names = (names,)
613 613
614 614 branches = repo.branchmap()
615 615 for name in names:
616 616 repo.hook(b'pretag', throw=True, node=hex(node), tag=name, local=local)
617 617 if name in branches:
618 618 repo.ui.warn(
619 619 _(b"warning: tag %s conflicts with existing branch name\n")
620 620 % name
621 621 )
622 622
623 623 def writetags(fp, names, munge, prevtags):
624 624 fp.seek(0, io.SEEK_END)
625 625 if prevtags and not prevtags.endswith(b'\n'):
626 626 fp.write(b'\n')
627 627 for name in names:
628 628 if munge:
629 629 m = munge(name)
630 630 else:
631 631 m = name
632 632
633 633 if repo._tagscache.tagtypes and name in repo._tagscache.tagtypes:
634 634 old = repo.tags().get(name, repo.nullid)
635 635 fp.write(b'%s %s\n' % (hex(old), m))
636 636 fp.write(b'%s %s\n' % (hex(node), m))
637 637 fp.close()
638 638
639 639 prevtags = b''
640 640 if local:
641 641 try:
642 642 fp = repo.vfs(b'localtags', b'r+')
643 643 except IOError:
644 644 fp = repo.vfs(b'localtags', b'a')
645 645 else:
646 646 prevtags = fp.read()
647 647
648 648 # local tags are stored in the current charset
649 649 writetags(fp, names, None, prevtags)
650 650 for name in names:
651 651 repo.hook(b'tag', node=hex(node), tag=name, local=local)
652 652 return
653 653
654 654 try:
655 655 fp = repo.wvfs(b'.hgtags', b'rb+')
656 656 except IOError as e:
657 657 if e.errno != errno.ENOENT:
658 658 raise
659 659 fp = repo.wvfs(b'.hgtags', b'ab')
660 660 else:
661 661 prevtags = fp.read()
662 662
663 663 # committed tags are stored in UTF-8
664 664 writetags(fp, names, encoding.fromlocal, prevtags)
665 665
666 666 fp.close()
667 667
668 668 repo.invalidatecaches()
669 669
670 670 if b'.hgtags' not in repo.dirstate:
671 671 repo[None].add([b'.hgtags'])
672 672
673 673 m = matchmod.exact([b'.hgtags'])
674 674 tagnode = repo.commit(
675 675 message, user, date, extra=extra, match=m, editor=editor
676 676 )
677 677
678 678 for name in names:
679 679 repo.hook(b'tag', node=hex(node), tag=name, local=local)
680 680
681 681 return tagnode
682 682
683 683
684 684 _fnodescachefile = b'hgtagsfnodes1'
685 685 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
686 686 _fnodesmissingrec = b'\xff' * 24
687 687
688 688
689 689 class hgtagsfnodescache(object):
690 690 """Persistent cache mapping revisions to .hgtags filenodes.
691 691
692 692 The cache is an array of records. Each item in the array corresponds to
693 693 a changelog revision. Values in the array contain the first 4 bytes of
694 694 the node hash and the 20 bytes .hgtags filenode for that revision.
695 695
696 696 The first 4 bytes are present as a form of verification. Repository
697 697 stripping and rewriting may change the node at a numeric revision in the
698 698 changelog. The changeset fragment serves as a verifier to detect
699 699 rewriting. This logic is shared with the rev branch cache (see
700 700 branchmap.py).
701 701
702 702 The instance holds in memory the full cache content but entries are
703 703 only parsed on read.
704 704
705 705 Instances behave like lists. ``c[i]`` works where i is a rev or
706 706 changeset node. Missing indexes are populated automatically on access.
707 707 """
708 708
709 709 def __init__(self, repo):
710 710 assert repo.filtername is None
711 711
712 712 self._repo = repo
713 713
714 714 # Only for reporting purposes.
715 715 self.lookupcount = 0
716 716 self.hitcount = 0
717 717
718 718 try:
719 719 data = repo.cachevfs.read(_fnodescachefile)
720 720 except (OSError, IOError):
721 721 data = b""
722 722 self._raw = bytearray(data)
723 723
724 724 # The end state of self._raw is an array that is of the exact length
725 725 # required to hold a record for every revision in the repository.
726 726 # We truncate or extend the array as necessary. self._dirtyoffset is
727 727 # defined to be the start offset at which we need to write the output
728 728 # file. This offset is also adjusted when new entries are calculated
729 729 # for array members.
730 730 cllen = len(repo.changelog)
731 731 wantedlen = cllen * _fnodesrecsize
732 732 rawlen = len(self._raw)
733 733
734 734 self._dirtyoffset = None
735 735
736 736 rawlentokeep = min(
737 737 wantedlen, (rawlen // _fnodesrecsize) * _fnodesrecsize
738 738 )
739 739 if rawlen > rawlentokeep:
740 740 # There's no easy way to truncate array instances. This seems
741 741 # slightly less evil than copying a potentially large array slice.
742 742 for i in range(rawlen - rawlentokeep):
743 743 self._raw.pop()
744 744 rawlen = len(self._raw)
745 745 self._dirtyoffset = rawlen
746 746 if rawlen < wantedlen:
747 747 if self._dirtyoffset is None:
748 748 self._dirtyoffset = rawlen
749 749 # TODO: zero fill entire record, because it's invalid not missing?
750 750 self._raw.extend(b'\xff' * (wantedlen - rawlen))
751 751
752 752 def getfnode(self, node, computemissing=True):
753 753 """Obtain the filenode of the .hgtags file at a specified revision.
754 754
755 755 If the value is in the cache, the entry will be validated and returned.
756 756 Otherwise, the filenode will be computed and returned unless
757 757 "computemissing" is False. In that case, None will be returned if
758 758 the entry is missing or False if the entry is invalid without
759 759 any potentially expensive computation being performed.
760 760
761 761 If an .hgtags does not exist at the specified revision, nullid is
762 762 returned.
763 763 """
764 764 if node == self._repo.nullid:
765 765 return node
766 766
767 767 ctx = self._repo[node]
768 768 rev = ctx.rev()
769 769
770 770 self.lookupcount += 1
771 771
772 772 offset = rev * _fnodesrecsize
773 773 record = b'%s' % self._raw[offset : offset + _fnodesrecsize]
774 774 properprefix = node[0:4]
775 775
776 776 # Validate and return existing entry.
777 777 if record != _fnodesmissingrec and len(record) == _fnodesrecsize:
778 778 fileprefix = record[0:4]
779 779
780 780 if fileprefix == properprefix:
781 781 self.hitcount += 1
782 782 return record[4:]
783 783
784 784 # Fall through.
785 785
786 786 # If we get here, the entry is either missing or invalid.
787 787
788 788 if not computemissing:
789 789 if record != _fnodesmissingrec:
790 790 return False
791 791 return None
792 792
793 793 fnode = self._computefnode(node)
794 794 self._writeentry(offset, properprefix, fnode)
795 795 return fnode
796 796
797 797 def _computefnode(self, node):
798 798 """Finds the tag filenode for a node which is missing or invalid
799 799 in cache"""
800 800 ctx = self._repo[node]
801 801 rev = ctx.rev()
802 802 fnode = None
803 803 cl = self._repo.changelog
804 804 p1rev, p2rev = cl._uncheckedparentrevs(rev)
805 805 p1node = cl.node(p1rev)
806 806 p1fnode = self.getfnode(p1node, computemissing=False)
807 807 if p2rev != nullrev:
808 808 # There is some no-merge changeset where p1 is null and p2 is set
809 809 # Processing them as merge is just slower, but still gives a good
810 810 # result.
811 p2node = cl.node(p1rev)
811 p2node = cl.node(p2rev)
812 812 p2fnode = self.getfnode(p2node, computemissing=False)
813 813 if p1fnode != p2fnode:
814 814 # we cannot rely on readfast because we don't know against what
815 815 # parent the readfast delta is computed
816 816 p1fnode = None
817 817 if p1fnode:
818 818 mctx = ctx.manifestctx()
819 819 fnode = mctx.readfast().get(b'.hgtags')
820 820 if fnode is None:
821 821 fnode = p1fnode
822 822 if fnode is None:
823 823 # Populate missing entry.
824 824 try:
825 825 fnode = ctx.filenode(b'.hgtags')
826 826 except error.LookupError:
827 827 # No .hgtags file on this revision.
828 828 fnode = self._repo.nullid
829 829 return fnode
830 830
831 831 def setfnode(self, node, fnode):
832 832 """Set the .hgtags filenode for a given changeset."""
833 833 assert len(fnode) == 20
834 834 ctx = self._repo[node]
835 835
836 836 # Do a lookup first to avoid writing if nothing has changed.
837 837 if self.getfnode(ctx.node(), computemissing=False) == fnode:
838 838 return
839 839
840 840 self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode)
841 841
842 842 def refresh_invalid_nodes(self, nodes):
843 843 """recomputes file nodes for a given set of nodes which has unknown
844 844 filenodes for them in the cache
845 845 Also updates the in-memory cache with the correct filenode.
846 846 Caller needs to take care about calling `.write()` so that updates are
847 847 persisted.
848 848 Returns a map {node: recomputed fnode}
849 849 """
850 850 fixed_nodemap = {}
851 851 for node in nodes:
852 852 fnode = self._computefnode(node)
853 853 fixed_nodemap[node] = fnode
854 854 self.setfnode(node, fnode)
855 855 return fixed_nodemap
856 856
857 857 def _writeentry(self, offset, prefix, fnode):
858 858 # Slices on array instances only accept other array.
859 859 entry = bytearray(prefix + fnode)
860 860 self._raw[offset : offset + _fnodesrecsize] = entry
861 861 # self._dirtyoffset could be None.
862 862 self._dirtyoffset = min(self._dirtyoffset or 0, offset or 0)
863 863
864 864 def write(self):
865 865 """Perform all necessary writes to cache file.
866 866
867 867 This may no-op if no writes are needed or if a write lock could
868 868 not be obtained.
869 869 """
870 870 if self._dirtyoffset is None:
871 871 return
872 872
873 873 data = self._raw[self._dirtyoffset :]
874 874 if not data:
875 875 return
876 876
877 877 repo = self._repo
878 878
879 879 try:
880 880 lock = repo.lock(wait=False)
881 881 except error.LockError:
882 882 repo.ui.log(
883 883 b'tagscache',
884 884 b'not writing .hg/cache/%s because '
885 885 b'lock cannot be acquired\n' % _fnodescachefile,
886 886 )
887 887 return
888 888
889 889 try:
890 890 f = repo.cachevfs.open(_fnodescachefile, b'ab')
891 891 try:
892 892 # if the file has been truncated
893 893 actualoffset = f.tell()
894 894 if actualoffset < self._dirtyoffset:
895 895 self._dirtyoffset = actualoffset
896 896 data = self._raw[self._dirtyoffset :]
897 897 f.seek(self._dirtyoffset)
898 898 f.truncate()
899 899 repo.ui.log(
900 900 b'tagscache',
901 901 b'writing %d bytes to cache/%s\n'
902 902 % (len(data), _fnodescachefile),
903 903 )
904 904 f.write(data)
905 905 self._dirtyoffset = None
906 906 finally:
907 907 f.close()
908 908 except (IOError, OSError) as inst:
909 909 repo.ui.log(
910 910 b'tagscache',
911 911 b"couldn't write cache/%s: %s\n"
912 912 % (_fnodescachefile, stringutil.forcebytestr(inst)),
913 913 )
914 914 finally:
915 915 lock.release()
@@ -1,935 +1,990 b''
1 1 setup
2 2
3 3 $ cat >> $HGRCPATH << EOF
4 4 > [extensions]
5 5 > blackbox=
6 6 > mock=$TESTDIR/mockblackbox.py
7 7 > [blackbox]
8 8 > track = command, commandfinish, tagscache
9 9 > EOF
10 10
11 11 Helper functions:
12 12
13 13 $ cacheexists() {
14 14 > [ -f .hg/cache/tags2-visible ] && echo "tag cache exists" || echo "no tag cache"
15 15 > }
16 16
17 17 $ fnodescacheexists() {
18 18 > [ -f .hg/cache/hgtagsfnodes1 ] && echo "fnodes cache exists" || echo "no fnodes cache"
19 19 > }
20 20
21 21 $ dumptags() {
22 22 > rev=$1
23 23 > echo "rev $rev: .hgtags:"
24 24 > hg cat -r$rev .hgtags
25 25 > }
26 26
27 27 # XXX need to test that the tag cache works when we strip an old head
28 28 # and add a new one rooted off non-tip: i.e. node and rev of tip are the
29 29 # same, but stuff has changed behind tip.
30 30
31 31 Setup:
32 32
33 33 $ hg init t
34 34 $ cd t
35 35 $ cacheexists
36 36 no tag cache
37 37 $ fnodescacheexists
38 38 no fnodes cache
39 39 $ hg id
40 40 000000000000 tip
41 41 $ cacheexists
42 42 no tag cache
43 43 $ fnodescacheexists
44 44 no fnodes cache
45 45 $ echo a > a
46 46 $ hg add a
47 47 $ hg commit -m "test"
48 48 $ hg co
49 49 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 50 $ hg identify
51 51 acb14030fe0a tip
52 52 $ hg identify -r 'wdir()'
53 53 acb14030fe0a tip
54 54 $ cacheexists
55 55 tag cache exists
56 56 No fnodes cache because .hgtags file doesn't exist
57 57 (this is an implementation detail)
58 58 $ fnodescacheexists
59 59 no fnodes cache
60 60
61 61 Try corrupting the cache
62 62
63 63 $ printf 'a b' > .hg/cache/tags2-visible
64 64 $ hg identify
65 65 acb14030fe0a tip
66 66 $ cacheexists
67 67 tag cache exists
68 68 $ fnodescacheexists
69 69 no fnodes cache
70 70 $ hg identify
71 71 acb14030fe0a tip
72 72
73 73 Create local tag with long name:
74 74
75 75 $ T=`hg identify --debug --id`
76 76 $ hg tag -l "This is a local tag with a really long name!"
77 77 $ hg tags
78 78 tip 0:acb14030fe0a
79 79 This is a local tag with a really long name! 0:acb14030fe0a
80 80 $ rm .hg/localtags
81 81
82 82 Create a tag behind hg's back:
83 83
84 84 $ echo "$T first" > .hgtags
85 85 $ cat .hgtags
86 86 acb14030fe0a21b60322c440ad2d20cf7685a376 first
87 87 $ hg add .hgtags
88 88 $ hg commit -m "add tags"
89 89 $ hg tags
90 90 tip 1:b9154636be93
91 91 first 0:acb14030fe0a
92 92 $ hg identify
93 93 b9154636be93 tip
94 94
95 95 We should have a fnodes cache now that we have a real tag
96 96 The cache should have an empty entry for rev 0 and a valid entry for rev 1.
97 97
98 98
99 99 $ fnodescacheexists
100 100 fnodes cache exists
101 101 $ f --size --hexdump .hg/cache/hgtagsfnodes1
102 102 .hg/cache/hgtagsfnodes1: size=48
103 103 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
104 104 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
105 105 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
106 106 $ hg debugtagscache
107 107 0 acb14030fe0a21b60322c440ad2d20cf7685a376 missing
108 108 1 b9154636be938d3d431e75a7c906504a079bfe07 26b7b4a773e09ee3c52f510e19e05e1ff966d859
109 109
110 110 Repeat with cold tag cache:
111 111
112 112 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
113 113 $ hg identify
114 114 b9154636be93 tip
115 115
116 116 $ fnodescacheexists
117 117 fnodes cache exists
118 118 $ f --size --hexdump .hg/cache/hgtagsfnodes1
119 119 .hg/cache/hgtagsfnodes1: size=48
120 120 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
121 121 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
122 122 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
123 123
124 124 And again, but now unable to write tag cache or lock file:
125 125
126 126 #if unix-permissions no-fsmonitor
127 127
128 128 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
129 129 $ chmod 555 .hg/cache
130 130 $ hg identify
131 131 b9154636be93 tip
132 132 $ chmod 755 .hg/cache
133 133
134 134 (this block should be protected by no-fsmonitor, because "chmod 555 .hg"
135 135 makes watchman fail at accessing to files under .hg)
136 136
137 137 $ chmod 555 .hg
138 138 $ hg identify
139 139 b9154636be93 tip
140 140 $ chmod 755 .hg
141 141 #endif
142 142
143 143 Tag cache debug info written to blackbox log
144 144
145 145 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
146 146 $ hg identify
147 147 b9154636be93 tip
148 148 $ hg blackbox -l 6
149 149 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify
150 150 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing 48 bytes to cache/hgtagsfnodes1
151 151 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> 0/2 cache hits/lookups in * seconds (glob)
152 152 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing .hg/cache/tags2-visible with 1 tags
153 153 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify exited 0 after * seconds (glob)
154 154 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l 6
155 155
156 156 Failure to acquire lock results in no write
157 157
158 158 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
159 159 $ echo 'foo:1' > .hg/store/lock
160 160 $ hg identify
161 161 b9154636be93 tip
162 162 $ hg blackbox -l 6
163 163 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify
164 164 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> not writing .hg/cache/hgtagsfnodes1 because lock cannot be acquired
165 165 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> 0/2 cache hits/lookups in * seconds (glob)
166 166 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing .hg/cache/tags2-visible with 1 tags
167 167 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify exited 0 after * seconds (glob)
168 168 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l 6
169 169
170 170 $ fnodescacheexists
171 171 no fnodes cache
172 172
173 173 $ rm .hg/store/lock
174 174
175 175 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
176 176 $ hg identify
177 177 b9154636be93 tip
178 178
179 179 Create a branch:
180 180
181 181 $ echo bb > a
182 182 $ hg status
183 183 M a
184 184 $ hg identify
185 185 b9154636be93+ tip
186 186 $ hg co first
187 187 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
188 188 $ hg id
189 189 acb14030fe0a+ first
190 190 $ hg id -r 'wdir()'
191 191 acb14030fe0a+ first
192 192 $ hg -v id
193 193 acb14030fe0a+ first
194 194 $ hg status
195 195 M a
196 196 $ echo 1 > b
197 197 $ hg add b
198 198 $ hg commit -m "branch"
199 199 created new head
200 200
201 201 Creating a new commit shouldn't append the .hgtags fnodes cache until
202 202 tags info is accessed
203 203
204 204 $ f --size --hexdump .hg/cache/hgtagsfnodes1
205 205 .hg/cache/hgtagsfnodes1: size=48
206 206 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
207 207 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
208 208 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
209 209
210 210 $ hg id
211 211 c8edf04160c7 tip
212 212
213 213 First 4 bytes of record 3 are changeset fragment
214 214
215 215 $ f --size --hexdump .hg/cache/hgtagsfnodes1
216 216 .hg/cache/hgtagsfnodes1: size=72
217 217 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
218 218 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
219 219 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
220 220 0030: c8 ed f0 41 00 00 00 00 00 00 00 00 00 00 00 00 |...A............|
221 221 0040: 00 00 00 00 00 00 00 00 |........|
222 222
223 223 Merge the two heads:
224 224
225 225 $ hg merge 1
226 226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
227 227 (branch merge, don't forget to commit)
228 228 $ hg blackbox -l3
229 229 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28 (5000)> merge 1
230 230 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28+b9154636be938d3d431e75a7c906504a079bfe07 (5000)> merge 1 exited 0 after * seconds (glob)
231 231 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28+b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l3
232 232 $ hg id
233 233 c8edf04160c7+b9154636be93+ tip
234 234 $ hg status
235 235 M .hgtags
236 236 $ hg commit -m "merge"
237 237
238 238 Create a fake head, make sure tag not visible afterwards:
239 239
240 240 $ cp .hgtags tags
241 241 $ hg tag last
242 242 $ hg rm .hgtags
243 243 $ hg commit -m "remove"
244 244
245 245 $ mv tags .hgtags
246 246 $ hg add .hgtags
247 247 $ hg commit -m "readd"
248 248 $
249 249 $ hg tags
250 250 tip 6:35ff301afafe
251 251 first 0:acb14030fe0a
252 252
253 253 Add invalid tags:
254 254
255 255 $ echo "spam" >> .hgtags
256 256 $ echo >> .hgtags
257 257 $ echo "foo bar" >> .hgtags
258 258 $ echo "a5a5 invalid" >> .hg/localtags
259 259 $ cat .hgtags
260 260 acb14030fe0a21b60322c440ad2d20cf7685a376 first
261 261 spam
262 262
263 263 foo bar
264 264 $ hg commit -m "tags"
265 265
266 266 Report tag parse error on other head:
267 267
268 268 $ hg up 3
269 269 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 270 $ echo 'x y' >> .hgtags
271 271 $ hg commit -m "head"
272 272 created new head
273 273
274 274 $ hg tags --debug
275 275 .hgtags@75d9f02dfe28, line 2: cannot parse entry
276 276 .hgtags@75d9f02dfe28, line 4: node 'foo' is not well formed
277 277 .hgtags@c4be69a18c11, line 2: node 'x' is not well formed
278 278 tip 8:c4be69a18c11e8bc3a5fdbb576017c25f7d84663
279 279 first 0:acb14030fe0a21b60322c440ad2d20cf7685a376
280 280 $ hg tip
281 281 changeset: 8:c4be69a18c11
282 282 tag: tip
283 283 parent: 3:ac5e980c4dc0
284 284 user: test
285 285 date: Thu Jan 01 00:00:00 1970 +0000
286 286 summary: head
287 287
288 288
289 289 Test tag precedence rules:
290 290
291 291 $ cd ..
292 292 $ hg init t2
293 293 $ cd t2
294 294 $ echo foo > foo
295 295 $ hg add foo
296 296 $ hg ci -m 'add foo' # rev 0
297 297 $ hg tag bar # rev 1
298 298 $ echo >> foo
299 299 $ hg ci -m 'change foo 1' # rev 2
300 300 $ hg up -C 1
301 301 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
302 302 $ hg tag -r 1 -f bar # rev 3
303 303 $ hg up -C 1
304 304 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
305 305 $ echo >> foo
306 306 $ hg ci -m 'change foo 2' # rev 4
307 307 created new head
308 308 $ hg tags
309 309 tip 4:0c192d7d5e6b
310 310 bar 1:78391a272241
311 311
312 312 Repeat in case of cache effects:
313 313
314 314 $ hg tags
315 315 tip 4:0c192d7d5e6b
316 316 bar 1:78391a272241
317 317
318 318 Detailed dump of tag info:
319 319
320 320 $ hg heads -q # expect 4, 3, 2
321 321 4:0c192d7d5e6b
322 322 3:6fa450212aeb
323 323 2:7a94127795a3
324 324 $ dumptags 2
325 325 rev 2: .hgtags:
326 326 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
327 327 $ dumptags 3
328 328 rev 3: .hgtags:
329 329 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
330 330 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
331 331 78391a272241d70354aa14c874552cad6b51bb42 bar
332 332 $ dumptags 4
333 333 rev 4: .hgtags:
334 334 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
335 335
336 336 Dump cache:
337 337
338 338 $ cat .hg/cache/tags2-visible
339 339 4 0c192d7d5e6b78a714de54a2e9627952a877e25a
340 340 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
341 341 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
342 342 78391a272241d70354aa14c874552cad6b51bb42 bar
343 343
344 344 $ f --size --hexdump .hg/cache/hgtagsfnodes1
345 345 .hg/cache/hgtagsfnodes1: size=120
346 346 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
347 347 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
348 348 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
349 349 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
350 350 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
351 351 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
352 352 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
353 353 0070: 78 ee 5a 2d ad bc 94 3d |x.Z-...=|
354 354
355 355 Corrupt the .hgtags fnodes cache
356 356 Extra junk data at the end should get overwritten on next cache update
357 357
358 358 $ echo extra >> .hg/cache/hgtagsfnodes1
359 359 $ echo dummy1 > foo
360 360 $ hg commit -m throwaway1
361 361
362 362 $ hg tags
363 363 tip 5:8dbfe60eff30
364 364 bar 1:78391a272241
365 365
366 366 $ hg blackbox -l 6
367 367 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> tags
368 368 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> writing 24 bytes to cache/hgtagsfnodes1
369 369 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> 3/4 cache hits/lookups in * seconds (glob)
370 370 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> writing .hg/cache/tags2-visible with 1 tags
371 371 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> tags exited 0 after * seconds (glob)
372 372 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> blackbox -l 6
373 373
374 374 On junk data + missing cache entries, hg also overwrites the junk.
375 375
376 376 $ rm -f .hg/cache/tags2-visible
377 377 >>> import os
378 378 >>> with open(".hg/cache/hgtagsfnodes1", "ab+") as fp:
379 379 ... fp.seek(-10, os.SEEK_END) and None
380 380 ... fp.truncate() and None
381 381
382 382 $ hg debugtagscache | tail -2
383 383 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
384 384 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 missing
385 385 $ hg tags
386 386 tip 5:8dbfe60eff30
387 387 bar 1:78391a272241
388 388 $ hg debugtagscache | tail -2
389 389 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
390 390 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8af31de17fab7422878ee5a2dadbc943d
391 391
392 392 If the 4 bytes of node hash for a record don't match an existing node, the entry
393 393 is flagged as invalid.
394 394
395 395 >>> import os
396 396 >>> with open(".hg/cache/hgtagsfnodes1", "rb+") as fp:
397 397 ... fp.seek(-24, os.SEEK_END) and None
398 398 ... fp.write(b'\xde\xad') and None
399 399
400 400 $ f --size --hexdump .hg/cache/hgtagsfnodes1
401 401 .hg/cache/hgtagsfnodes1: size=144
402 402 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
403 403 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
404 404 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
405 405 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
406 406 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
407 407 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
408 408 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
409 409 0070: 78 ee 5a 2d ad bc 94 3d de ad e6 0e 0c 04 f2 a8 |x.Z-...=........|
410 410 0080: af 31 de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |.1....B(x.Z-...=|
411 411
412 412 $ hg debugtagscache | tail -2
413 413 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
414 414 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 invalid
415 415
416 416 $ hg tags
417 417 tip 5:8dbfe60eff30
418 418 bar 1:78391a272241
419 419
420 420 BUG: If the filenode part of an entry in hgtagsfnodes is corrupt and
421 421 tags2-visible is missing, `hg tags` aborts. Corrupting the leading 4 bytes of
422 422 node hash (as above) doesn't seem to trigger the issue. Also note that the
423 423 debug command hides the corruption, both with and without tags2-visible.
424 424
425 425 $ mv .hg/cache/hgtagsfnodes1 .hg/cache/hgtagsfnodes1.bak
426 426 $ hg debugupdatecaches
427 427
428 428 >>> import os
429 429 >>> with open(".hg/cache/hgtagsfnodes1", "rb+") as fp:
430 430 ... fp.seek(-16, os.SEEK_END) and None
431 431 ... fp.write(b'\xde\xad') and None
432 432
433 433 $ f --size --hexdump .hg/cache/hgtagsfnodes1
434 434 .hg/cache/hgtagsfnodes1: size=144
435 435 0000: bb d1 79 df 00 00 00 00 00 00 00 00 00 00 00 00 |..y.............|
436 436 0010: 00 00 00 00 00 00 00 00 78 39 1a 27 0c 04 f2 a8 |........x9.'....|
437 437 0020: af 31 de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |.1....B(x.Z-...=|
438 438 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
439 439 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
440 440 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
441 441 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
442 442 0070: 78 ee 5a 2d ad bc 94 3d 8d bf e6 0e 0c 04 f2 a8 |x.Z-...=........|
443 443 0080: de ad de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |......B(x.Z-...=|
444 444
445 445 $ hg debugtagscache | tail -2
446 446 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
447 447 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8deadde17fab7422878ee5a2dadbc943d (unknown node)
448 448
449 449 $ rm -f .hg/cache/tags2-visible
450 450 $ hg debugtagscache | tail -2
451 451 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
452 452 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8deadde17fab7422878ee5a2dadbc943d (unknown node)
453 453
454 454 $ hg tags
455 455 tip 5:8dbfe60eff30
456 456 bar 1:78391a272241
457 457
458 458 BUG: Unless this file is restored, the `hg tags` in the next unix-permissions
459 459 conditional will fail: "abort: data/.hgtags.i@0c04f2a8dead: no match found"
460 460
461 461 $ mv .hg/cache/hgtagsfnodes1.bak .hg/cache/hgtagsfnodes1
462 462
463 463 #if unix-permissions no-root
464 464 Errors writing to .hgtags fnodes cache are silently ignored
465 465
466 466 $ echo dummy2 > foo
467 467 $ hg commit -m throwaway2
468 468
469 469 $ chmod a-w .hg/cache/hgtagsfnodes1
470 470 $ rm -f .hg/cache/tags2-visible
471 471
472 472 $ hg tags
473 473 tip 6:b968051b5cf3
474 474 bar 1:78391a272241
475 475
476 476 $ hg blackbox -l 6
477 477 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags
478 478 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> couldn't write cache/hgtagsfnodes1: [Errno *] * (glob)
479 479 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/4 cache hits/lookups in * seconds (glob)
480 480 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags
481 481 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob)
482 482 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> blackbox -l 6
483 483
484 484 $ chmod a+w .hg/cache/hgtagsfnodes1
485 485
486 486 $ rm -f .hg/cache/tags2-visible
487 487 $ hg tags
488 488 tip 6:b968051b5cf3
489 489 bar 1:78391a272241
490 490
491 491 $ hg blackbox -l 6
492 492 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags
493 493 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing 24 bytes to cache/hgtagsfnodes1
494 494 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/4 cache hits/lookups in * seconds (glob)
495 495 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags
496 496 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob)
497 497 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> blackbox -l 6
498 498
499 499 $ f --size .hg/cache/hgtagsfnodes1
500 500 .hg/cache/hgtagsfnodes1: size=168
501 501
502 502 $ hg -q --config extensions.strip= strip -r 6 --no-backup
503 503 #endif
504 504
505 505 Stripping doesn't truncate the tags cache until new data is available
506 506
507 507 $ rm -f .hg/cache/hgtagsfnodes1 .hg/cache/tags2-visible
508 508 $ hg tags
509 509 tip 5:8dbfe60eff30
510 510 bar 1:78391a272241
511 511
512 512 $ f --size .hg/cache/hgtagsfnodes1
513 513 .hg/cache/hgtagsfnodes1: size=144
514 514
515 515 $ hg -q --config extensions.strip= strip -r 5 --no-backup
516 516 $ hg tags
517 517 tip 4:0c192d7d5e6b
518 518 bar 1:78391a272241
519 519
520 520 $ hg blackbox -l 5
521 521 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> writing 24 bytes to cache/hgtagsfnodes1
522 522 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> 2/4 cache hits/lookups in * seconds (glob)
523 523 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> writing .hg/cache/tags2-visible with 1 tags
524 524 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> tags exited 0 after * seconds (glob)
525 525 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> blackbox -l 5
526 526
527 527 $ f --size .hg/cache/hgtagsfnodes1
528 528 .hg/cache/hgtagsfnodes1: size=120
529 529
530 530 $ echo dummy > foo
531 531 $ hg commit -m throwaway3
532 532
533 533 $ hg tags
534 534 tip 5:035f65efb448
535 535 bar 1:78391a272241
536 536
537 537 $ hg blackbox -l 6
538 538 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> tags
539 539 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> writing 24 bytes to cache/hgtagsfnodes1
540 540 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> 3/4 cache hits/lookups in * seconds (glob)
541 541 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> writing .hg/cache/tags2-visible with 1 tags
542 542 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> tags exited 0 after * seconds (glob)
543 543 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> blackbox -l 6
544 544 $ f --size .hg/cache/hgtagsfnodes1
545 545 .hg/cache/hgtagsfnodes1: size=144
546 546
547 547 $ hg -q --config extensions.strip= strip -r 5 --no-backup
548 548
549 549 Test tag removal:
550 550
551 551 $ hg tag --remove bar # rev 5
552 552 $ hg tip -vp
553 553 changeset: 5:5f6e8655b1c7
554 554 tag: tip
555 555 user: test
556 556 date: Thu Jan 01 00:00:00 1970 +0000
557 557 files: .hgtags
558 558 description:
559 559 Removed tag bar
560 560
561 561
562 562 diff -r 0c192d7d5e6b -r 5f6e8655b1c7 .hgtags
563 563 --- a/.hgtags Thu Jan 01 00:00:00 1970 +0000
564 564 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
565 565 @@ -1,1 +1,3 @@
566 566 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
567 567 +78391a272241d70354aa14c874552cad6b51bb42 bar
568 568 +0000000000000000000000000000000000000000 bar
569 569
570 570 $ hg tags
571 571 tip 5:5f6e8655b1c7
572 572 $ hg tags # again, try to expose cache bugs
573 573 tip 5:5f6e8655b1c7
574 574
575 575 Remove nonexistent tag:
576 576
577 577 $ hg tag --remove foobar
578 578 abort: tag 'foobar' does not exist
579 579 [10]
580 580 $ hg tip
581 581 changeset: 5:5f6e8655b1c7
582 582 tag: tip
583 583 user: test
584 584 date: Thu Jan 01 00:00:00 1970 +0000
585 585 summary: Removed tag bar
586 586
587 587
588 588 Undo a tag with rollback:
589 589
590 590 $ hg rollback # destroy rev 5 (restore bar)
591 591 repository tip rolled back to revision 4 (undo commit)
592 592 working directory now based on revision 4
593 593 $ hg tags
594 594 tip 4:0c192d7d5e6b
595 595 bar 1:78391a272241
596 596 $ hg tags
597 597 tip 4:0c192d7d5e6b
598 598 bar 1:78391a272241
599 599
600 600 Test tag rank:
601 601
602 602 $ cd ..
603 603 $ hg init t3
604 604 $ cd t3
605 605 $ echo foo > foo
606 606 $ hg add foo
607 607 $ hg ci -m 'add foo' # rev 0
608 608 $ hg tag -f bar # rev 1 bar -> 0
609 609 $ hg tag -f bar # rev 2 bar -> 1
610 610 $ hg tag -fr 0 bar # rev 3 bar -> 0
611 611 $ hg tag -fr 1 bar # rev 4 bar -> 1
612 612 $ hg tag -fr 0 bar # rev 5 bar -> 0
613 613 $ hg tags
614 614 tip 5:85f05169d91d
615 615 bar 0:bbd179dfa0a7
616 616 $ hg co 3
617 617 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
618 618 $ echo barbar > foo
619 619 $ hg ci -m 'change foo' # rev 6
620 620 created new head
621 621 $ hg tags
622 622 tip 6:735c3ca72986
623 623 bar 0:bbd179dfa0a7
624 624
625 625 Don't allow moving tag without -f:
626 626
627 627 $ hg tag -r 3 bar
628 628 abort: tag 'bar' already exists (use -f to force)
629 629 [10]
630 630 $ hg tags
631 631 tip 6:735c3ca72986
632 632 bar 0:bbd179dfa0a7
633 633
634 634 Strip 1: expose an old head:
635 635
636 636 $ hg --config extensions.mq= strip 5
637 637 saved backup bundle to $TESTTMP/t3/.hg/strip-backup/*-backup.hg (glob)
638 638 $ hg tags # partly stale cache
639 639 tip 5:735c3ca72986
640 640 bar 1:78391a272241
641 641 $ hg tags # up-to-date cache
642 642 tip 5:735c3ca72986
643 643 bar 1:78391a272241
644 644
645 645 Strip 2: destroy whole branch, no old head exposed
646 646
647 647 $ hg --config extensions.mq= strip 4
648 648 saved backup bundle to $TESTTMP/t3/.hg/strip-backup/*-backup.hg (glob)
649 649 $ hg tags # partly stale
650 650 tip 4:735c3ca72986
651 651 bar 0:bbd179dfa0a7
652 652 $ rm -f .hg/cache/tags2-visible
653 653 $ hg tags # cold cache
654 654 tip 4:735c3ca72986
655 655 bar 0:bbd179dfa0a7
656 656
657 657 Test tag rank with 3 heads:
658 658
659 659 $ cd ..
660 660 $ hg init t4
661 661 $ cd t4
662 662 $ echo foo > foo
663 663 $ hg add
664 664 adding foo
665 665 $ hg ci -m 'add foo' # rev 0
666 666 $ hg tag bar # rev 1 bar -> 0
667 667 $ hg tag -f bar # rev 2 bar -> 1
668 668 $ hg up -qC 0
669 669 $ hg tag -fr 2 bar # rev 3 bar -> 2
670 670 $ hg tags
671 671 tip 3:197c21bbbf2c
672 672 bar 2:6fa450212aeb
673 673 $ hg up -qC 0
674 674 $ hg tag -m 'retag rev 0' -fr 0 bar # rev 4 bar -> 0, but bar stays at 2
675 675
676 676 Bar should still point to rev 2:
677 677
678 678 $ hg tags
679 679 tip 4:3b4b14ed0202
680 680 bar 2:6fa450212aeb
681 681
682 682 Test that removing global/local tags does not get confused when trying
683 683 to remove a tag of type X which actually only exists as a type Y:
684 684
685 685 $ cd ..
686 686 $ hg init t5
687 687 $ cd t5
688 688 $ echo foo > foo
689 689 $ hg add
690 690 adding foo
691 691 $ hg ci -m 'add foo' # rev 0
692 692
693 693 $ hg tag -r 0 -l localtag
694 694 $ hg tag --remove localtag
695 695 abort: tag 'localtag' is not a global tag
696 696 [10]
697 697 $
698 698 $ hg tag -r 0 globaltag
699 699 $ hg tag --remove -l globaltag
700 700 abort: tag 'globaltag' is not a local tag
701 701 [10]
702 702 $ hg tags -v
703 703 tip 1:a0b6fe111088
704 704 localtag 0:bbd179dfa0a7 local
705 705 globaltag 0:bbd179dfa0a7
706 706
707 707 Templated output:
708 708
709 709 (immediate values)
710 710
711 711 $ hg tags -T '{pad(tag, 9)} {rev}:{node} ({type})\n'
712 712 tip 1:a0b6fe111088c8c29567d3876cc466aa02927cae ()
713 713 localtag 0:bbd179dfa0a71671c253b3ae0aa1513b60d199fa (local)
714 714 globaltag 0:bbd179dfa0a71671c253b3ae0aa1513b60d199fa ()
715 715
716 716 (ctx/revcache dependent)
717 717
718 718 $ hg tags -T '{pad(tag, 9)} {rev} {file_adds}\n'
719 719 tip 1 .hgtags
720 720 localtag 0 foo
721 721 globaltag 0 foo
722 722
723 723 $ hg tags -T '{pad(tag, 9)} {rev}:{node|shortest}\n'
724 724 tip 1:a0b6
725 725 localtag 0:bbd1
726 726 globaltag 0:bbd1
727 727
728 728 Test for issue3911
729 729
730 730 $ hg tag -r 0 -l localtag2
731 731 $ hg tag -l --remove localtag2
732 732 $ hg tags -v
733 733 tip 1:a0b6fe111088
734 734 localtag 0:bbd179dfa0a7 local
735 735 globaltag 0:bbd179dfa0a7
736 736
737 737 $ hg tag -r 1 -f localtag
738 738 $ hg tags -v
739 739 tip 2:5c70a037bb37
740 740 localtag 1:a0b6fe111088
741 741 globaltag 0:bbd179dfa0a7
742 742
743 743 $ hg tags -v
744 744 tip 2:5c70a037bb37
745 745 localtag 1:a0b6fe111088
746 746 globaltag 0:bbd179dfa0a7
747 747
748 748 $ hg tag -r 1 localtag2
749 749 $ hg tags -v
750 750 tip 3:bbfb8cd42be2
751 751 localtag2 1:a0b6fe111088
752 752 localtag 1:a0b6fe111088
753 753 globaltag 0:bbd179dfa0a7
754 754
755 755 $ hg tags -v
756 756 tip 3:bbfb8cd42be2
757 757 localtag2 1:a0b6fe111088
758 758 localtag 1:a0b6fe111088
759 759 globaltag 0:bbd179dfa0a7
760 760
761 761 $ cd ..
762 762
763 763 Create a repository with tags data to test .hgtags fnodes transfer
764 764
765 765 $ hg init tagsserver
766 766 $ cd tagsserver
767 767 $ touch foo
768 768 $ hg -q commit -A -m initial
769 769 $ hg tag -m 'tag 0.1' 0.1
770 770 $ echo second > foo
771 771 $ hg commit -m second
772 772 $ hg tag -m 'tag 0.2' 0.2
773 773 $ hg tags
774 774 tip 3:40f0358cb314
775 775 0.2 2:f63cc8fe54e4
776 776 0.1 0:96ee1d7354c4
777 777 $ cd ..
778 778
779 779 Cloning should pull down hgtags fnodes mappings and write the cache file
780 780
781 781 $ hg clone --pull tagsserver tagsclient
782 782 requesting all changes
783 783 adding changesets
784 784 adding manifests
785 785 adding file changes
786 786 added 4 changesets with 4 changes to 2 files
787 787 new changesets 96ee1d7354c4:40f0358cb314
788 788 updating to branch default
789 789 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
790 790
791 791 Missing tags2* files means the cache wasn't written through the normal mechanism.
792 792
793 793 $ ls tagsclient/.hg/cache
794 794 branch2-base
795 795 branch2-immutable
796 796 branch2-served
797 797 branch2-served.hidden
798 798 branch2-visible
799 799 branch2-visible-hidden
800 800 hgtagsfnodes1
801 801 rbc-names-v1
802 802 rbc-revs-v1
803 803 tags2
804 804 tags2-served
805 805
806 806 Cache should contain the head only, even though other nodes have tags data
807 807
808 808 $ f --size --hexdump tagsclient/.hg/cache/hgtagsfnodes1
809 809 tagsclient/.hg/cache/hgtagsfnodes1: size=96
810 810 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
811 811 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
812 812 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
813 813 0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
814 814 0040: ff ff ff ff ff ff ff ff 40 f0 35 8c 19 e0 a7 d3 |........@.5.....|
815 815 0050: 8a 5c 6a 82 4d cf fb a5 87 d0 2f a3 1e 4f 2f 8a |.\j.M...../..O/.|
816 816
817 817 Running hg tags should produce tags2* file and not change cache
818 818
819 819 $ hg -R tagsclient tags
820 820 tip 3:40f0358cb314
821 821 0.2 2:f63cc8fe54e4
822 822 0.1 0:96ee1d7354c4
823 823
824 824 $ ls tagsclient/.hg/cache
825 825 branch2-base
826 826 branch2-immutable
827 827 branch2-served
828 828 branch2-served.hidden
829 829 branch2-visible
830 830 branch2-visible-hidden
831 831 hgtagsfnodes1
832 832 rbc-names-v1
833 833 rbc-revs-v1
834 834 tags2
835 835 tags2-served
836 836 tags2-visible
837 837
838 838 $ f --size --hexdump tagsclient/.hg/cache/hgtagsfnodes1
839 839 tagsclient/.hg/cache/hgtagsfnodes1: size=96
840 840 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
841 841 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
842 842 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
843 843 0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
844 844 0040: ff ff ff ff ff ff ff ff 40 f0 35 8c 19 e0 a7 d3 |........@.5.....|
845 845 0050: 8a 5c 6a 82 4d cf fb a5 87 d0 2f a3 1e 4f 2f 8a |.\j.M...../..O/.|
846 846
847 847 Check that the bundle includes cache data
848 848
849 849 $ hg -R tagsclient bundle --all ./test-cache-in-bundle-all-rev.hg
850 850 4 changesets found
851 851 $ hg debugbundle ./test-cache-in-bundle-all-rev.hg
852 852 Stream params: {Compression: BZ}
853 853 changegroup -- {nbchanges: 4, version: 02} (mandatory: True)
854 854 96ee1d7354c4ad7372047672c36a1f561e3a6a4c
855 855 c4dab0c2fd337eb9191f80c3024830a4889a8f34
856 856 f63cc8fe54e4d326f8d692805d70e092f851ddb1
857 857 40f0358cb314c824a5929ee527308d90e023bc10
858 858 hgtagsfnodes -- {} (mandatory: True)
859 859 cache:rev-branch-cache -- {} (mandatory: False)
860 860
861 861 Check that local clone includes cache data
862 862
863 863 $ hg clone tagsclient tags-local-clone
864 864 updating to branch default
865 865 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
866 866 $ (cd tags-local-clone/.hg/cache/; ls -1 tag*)
867 867 tags2
868 868 tags2-served
869 869 tags2-visible
870 870
871 871 Avoid writing logs on trying to delete an already deleted tag
872 872 $ hg init issue5752
873 873 $ cd issue5752
874 874 $ echo > a
875 875 $ hg commit -Am 'add a'
876 876 adding a
877 877 $ hg tag a
878 878 $ hg tags
879 879 tip 1:bd7ee4f3939b
880 880 a 0:a8a82d372bb3
881 881 $ hg log
882 882 changeset: 1:bd7ee4f3939b
883 883 tag: tip
884 884 user: test
885 885 date: Thu Jan 01 00:00:00 1970 +0000
886 886 summary: Added tag a for changeset a8a82d372bb3
887 887
888 888 changeset: 0:a8a82d372bb3
889 889 tag: a
890 890 user: test
891 891 date: Thu Jan 01 00:00:00 1970 +0000
892 892 summary: add a
893 893
894 894 $ hg tag --remove a
895 895 $ hg log
896 896 changeset: 2:e7feacc7ec9e
897 897 tag: tip
898 898 user: test
899 899 date: Thu Jan 01 00:00:00 1970 +0000
900 900 summary: Removed tag a
901 901
902 902 changeset: 1:bd7ee4f3939b
903 903 user: test
904 904 date: Thu Jan 01 00:00:00 1970 +0000
905 905 summary: Added tag a for changeset a8a82d372bb3
906 906
907 907 changeset: 0:a8a82d372bb3
908 908 user: test
909 909 date: Thu Jan 01 00:00:00 1970 +0000
910 910 summary: add a
911 911
912 912 $ hg tag --remove a
913 913 abort: tag 'a' is already removed
914 914 [10]
915 915 $ hg log
916 916 changeset: 2:e7feacc7ec9e
917 917 tag: tip
918 918 user: test
919 919 date: Thu Jan 01 00:00:00 1970 +0000
920 920 summary: Removed tag a
921 921
922 922 changeset: 1:bd7ee4f3939b
923 923 user: test
924 924 date: Thu Jan 01 00:00:00 1970 +0000
925 925 summary: Added tag a for changeset a8a82d372bb3
926 926
927 927 changeset: 0:a8a82d372bb3
928 928 user: test
929 929 date: Thu Jan 01 00:00:00 1970 +0000
930 930 summary: add a
931 931
932 932 $ cat .hgtags
933 933 a8a82d372bb35b42ff736e74f07c23bcd99c371f a
934 934 a8a82d372bb35b42ff736e74f07c23bcd99c371f a
935 935 0000000000000000000000000000000000000000 a
936
937 $ cd ..
938
939 .hgtags fnode should be properly resolved at merge revision (issue6673)
940
941 $ hg init issue6673
942 $ cd issue6673
943
944 $ touch a
945 $ hg ci -qAm a
946 $ hg branch -q stable
947 $ hg ci -m branch
948
949 $ hg up -q default
950 $ hg merge -q stable
951 $ hg ci -m merge
952
953 add tag to stable branch:
954
955 $ hg up -q stable
956 $ echo a >> a
957 $ hg ci -m a
958 $ hg tag whatever
959 $ hg log -GT'{rev} {tags}\n'
960 @ 4 tip
961 |
962 o 3 whatever
963 |
964 | o 2
965 |/|
966 o | 1
967 |/
968 o 0
969
970
971 merge tagged stable into default:
972
973 $ hg up -q default
974 $ hg merge -q stable
975 $ hg ci -m merge
976 $ hg log -GT'{rev} {tags}\n'
977 @ 5 tip
978 |\
979 | o 4
980 | |
981 | o 3 whatever
982 | |
983 o | 2
984 |\|
985 | o 1
986 |/
987 o 0
988
989
990 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now