##// END OF EJS Templates
tag: use filtered repo when creating new tags (issue5539)...
Denis Laxalde -
r34017:2d80e078 default
parent child Browse files
Show More
@@ -1,788 +1,788
1 1 # tags.py - read tag info from local repository
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@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
17 17 from .node import (
18 18 bin,
19 19 hex,
20 20 nullid,
21 21 short,
22 22 )
23 23 from .i18n import _
24 24 from . import (
25 25 encoding,
26 26 error,
27 27 match as matchmod,
28 28 scmutil,
29 29 util,
30 30 )
31 31
32 32 # Tags computation can be expensive and caches exist to make it fast in
33 33 # the common case.
34 34 #
35 35 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
36 36 # each revision in the repository. The file is effectively an array of
37 37 # fixed length records. Read the docs for "hgtagsfnodescache" for technical
38 38 # details.
39 39 #
40 40 # The .hgtags filenode cache grows in proportion to the length of the
41 41 # changelog. The file is truncated when the # changelog is stripped.
42 42 #
43 43 # The purpose of the filenode cache is to avoid the most expensive part
44 44 # of finding global tags, which is looking up the .hgtags filenode in the
45 45 # manifest for each head. This can take dozens or over 100ms for
46 46 # repositories with very large manifests. Multiplied by dozens or even
47 47 # hundreds of heads and there is a significant performance concern.
48 48 #
49 49 # There also exist a separate cache file for each repository filter.
50 50 # These "tags-*" files store information about the history of tags.
51 51 #
52 52 # The tags cache files consists of a cache validation line followed by
53 53 # a history of tags.
54 54 #
55 55 # The cache validation line has the format:
56 56 #
57 57 # <tiprev> <tipnode> [<filteredhash>]
58 58 #
59 59 # <tiprev> is an integer revision and <tipnode> is a 40 character hex
60 60 # node for that changeset. These redundantly identify the repository
61 61 # tip from the time the cache was written. In addition, <filteredhash>,
62 62 # if present, is a 40 character hex hash of the contents of the filtered
63 63 # revisions for this filter. If the set of filtered revs changes, the
64 64 # hash will change and invalidate the cache.
65 65 #
66 66 # The history part of the tags cache consists of lines of the form:
67 67 #
68 68 # <node> <tag>
69 69 #
70 70 # (This format is identical to that of .hgtags files.)
71 71 #
72 72 # <tag> is the tag name and <node> is the 40 character hex changeset
73 73 # the tag is associated with.
74 74 #
75 75 # Tags are written sorted by tag name.
76 76 #
77 77 # Tags associated with multiple changesets have an entry for each changeset.
78 78 # The most recent changeset (in terms of revlog ordering for the head
79 79 # setting it) for each tag is last.
80 80
81 81 def fnoderevs(ui, repo, revs):
82 82 """return the list of '.hgtags' fnodes used in a set revisions
83 83
84 84 This is returned as list of unique fnodes. We use a list instead of a set
85 85 because order matters when it comes to tags."""
86 86 unfi = repo.unfiltered()
87 87 tonode = unfi.changelog.node
88 88 nodes = [tonode(r) for r in revs]
89 89 fnodes = _getfnodes(ui, repo, nodes[::-1]) # reversed help the cache
90 90 fnodes = _filterfnodes(fnodes, nodes)
91 91 return fnodes
92 92
93 93 def _nulltonone(value):
94 94 """convert nullid to None
95 95
96 96 For tag value, nullid means "deleted". This small utility function helps
97 97 translating that to None."""
98 98 if value == nullid:
99 99 return None
100 100 return value
101 101
102 102 def difftags(ui, repo, oldfnodes, newfnodes):
103 103 """list differences between tags expressed in two set of file-nodes
104 104
105 105 The list contains entries in the form: (tagname, oldvalue, new value).
106 106 None is used to expressed missing value:
107 107 ('foo', None, 'abcd') is a new tag,
108 108 ('bar', 'ef01', None) is a deletion,
109 109 ('baz', 'abcd', 'ef01') is a tag movement.
110 110 """
111 111 if oldfnodes == newfnodes:
112 112 return []
113 113 oldtags = _tagsfromfnodes(ui, repo, oldfnodes)
114 114 newtags = _tagsfromfnodes(ui, repo, newfnodes)
115 115
116 116 # list of (tag, old, new): None means missing
117 117 entries = []
118 118 for tag, (new, __) in newtags.items():
119 119 new = _nulltonone(new)
120 120 old, __ = oldtags.pop(tag, (None, None))
121 121 old = _nulltonone(old)
122 122 if old != new:
123 123 entries.append((tag, old, new))
124 124 # handle deleted tags
125 125 for tag, (old, __) in oldtags.items():
126 126 old = _nulltonone(old)
127 127 if old is not None:
128 128 entries.append((tag, old, None))
129 129 entries.sort()
130 130 return entries
131 131
132 132 def writediff(fp, difflist):
133 133 """write tags diff information to a file.
134 134
135 135 Data are stored with a line based format:
136 136
137 137 <action> <hex-node> <tag-name>\n
138 138
139 139 Action are defined as follow:
140 140 -R tag is removed,
141 141 +A tag is added,
142 142 -M tag is moved (old value),
143 143 +M tag is moved (new value),
144 144
145 145 Example:
146 146
147 147 +A 875517b4806a848f942811a315a5bce30804ae85 t5
148 148
149 149 See documentation of difftags output for details about the input.
150 150 """
151 151 add = '+A %s %s\n'
152 152 remove = '-R %s %s\n'
153 153 updateold = '-M %s %s\n'
154 154 updatenew = '+M %s %s\n'
155 155 for tag, old, new in difflist:
156 156 # translate to hex
157 157 if old is not None:
158 158 old = hex(old)
159 159 if new is not None:
160 160 new = hex(new)
161 161 # write to file
162 162 if old is None:
163 163 fp.write(add % (new, tag))
164 164 elif new is None:
165 165 fp.write(remove % (old, tag))
166 166 else:
167 167 fp.write(updateold % (old, tag))
168 168 fp.write(updatenew % (new, tag))
169 169
170 170 def findglobaltags(ui, repo):
171 171 '''Find global tags in a repo: return a tagsmap
172 172
173 173 tagsmap: tag name to (node, hist) 2-tuples.
174 174
175 175 The tags cache is read and updated as a side-effect of calling.
176 176 '''
177 177 (heads, tagfnode, valid, cachetags, shouldwrite) = _readtagcache(ui, repo)
178 178 if cachetags is not None:
179 179 assert not shouldwrite
180 180 # XXX is this really 100% correct? are there oddball special
181 181 # cases where a global tag should outrank a local tag but won't,
182 182 # because cachetags does not contain rank info?
183 183 alltags = {}
184 184 _updatetags(cachetags, alltags)
185 185 return alltags
186 186
187 187 for head in reversed(heads): # oldest to newest
188 188 assert head in repo.changelog.nodemap, \
189 189 "tag cache returned bogus head %s" % short(head)
190 190 fnodes = _filterfnodes(tagfnode, reversed(heads))
191 191 alltags = _tagsfromfnodes(ui, repo, fnodes)
192 192
193 193 # and update the cache (if necessary)
194 194 if shouldwrite:
195 195 _writetagcache(ui, repo, valid, alltags)
196 196 return alltags
197 197
198 198 def _filterfnodes(tagfnode, nodes):
199 199 """return a list of unique fnodes
200 200
201 201 The order of this list matches the order of "nodes". Preserving this order
202 202 is important as reading tags in different order provides different
203 203 results."""
204 204 seen = set() # set of fnode
205 205 fnodes = []
206 206 for no in nodes: # oldest to newest
207 207 fnode = tagfnode.get(no)
208 208 if fnode and fnode not in seen:
209 209 seen.add(fnode)
210 210 fnodes.append(fnode)
211 211 return fnodes
212 212
213 213 def _tagsfromfnodes(ui, repo, fnodes):
214 214 """return a tagsmap from a list of file-node
215 215
216 216 tagsmap: tag name to (node, hist) 2-tuples.
217 217
218 218 The order of the list matters."""
219 219 alltags = {}
220 220 fctx = None
221 221 for fnode in fnodes:
222 222 if fctx is None:
223 223 fctx = repo.filectx('.hgtags', fileid=fnode)
224 224 else:
225 225 fctx = fctx.filectx(fnode)
226 226 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
227 227 _updatetags(filetags, alltags)
228 228 return alltags
229 229
230 230 def readlocaltags(ui, repo, alltags, tagtypes):
231 231 '''Read local tags in repo. Update alltags and tagtypes.'''
232 232 try:
233 233 data = repo.vfs.read("localtags")
234 234 except IOError as inst:
235 235 if inst.errno != errno.ENOENT:
236 236 raise
237 237 return
238 238
239 239 # localtags is in the local encoding; re-encode to UTF-8 on
240 240 # input for consistency with the rest of this module.
241 241 filetags = _readtags(
242 242 ui, repo, data.splitlines(), "localtags",
243 243 recode=encoding.fromlocal)
244 244
245 245 # remove tags pointing to invalid nodes
246 246 cl = repo.changelog
247 247 for t in filetags.keys():
248 248 try:
249 249 cl.rev(filetags[t][0])
250 250 except (LookupError, ValueError):
251 251 del filetags[t]
252 252
253 253 _updatetags(filetags, alltags, 'local', tagtypes)
254 254
255 255 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
256 256 '''Read tag definitions from a file (or any source of lines).
257 257
258 258 This function returns two sortdicts with similar information:
259 259
260 260 - the first dict, bintaghist, contains the tag information as expected by
261 261 the _readtags function, i.e. a mapping from tag name to (node, hist):
262 262 - node is the node id from the last line read for that name,
263 263 - hist is the list of node ids previously associated with it (in file
264 264 order). All node ids are binary, not hex.
265 265
266 266 - the second dict, hextaglines, is a mapping from tag name to a list of
267 267 [hexnode, line number] pairs, ordered from the oldest to the newest node.
268 268
269 269 When calcnodelines is False the hextaglines dict is not calculated (an
270 270 empty dict is returned). This is done to improve this function's
271 271 performance in cases where the line numbers are not needed.
272 272 '''
273 273
274 274 bintaghist = util.sortdict()
275 275 hextaglines = util.sortdict()
276 276 count = 0
277 277
278 278 def dbg(msg):
279 279 ui.debug("%s, line %s: %s\n" % (fn, count, msg))
280 280
281 281 for nline, line in enumerate(lines):
282 282 count += 1
283 283 if not line:
284 284 continue
285 285 try:
286 286 (nodehex, name) = line.split(" ", 1)
287 287 except ValueError:
288 288 dbg("cannot parse entry")
289 289 continue
290 290 name = name.strip()
291 291 if recode:
292 292 name = recode(name)
293 293 try:
294 294 nodebin = bin(nodehex)
295 295 except TypeError:
296 296 dbg("node '%s' is not well formed" % nodehex)
297 297 continue
298 298
299 299 # update filetags
300 300 if calcnodelines:
301 301 # map tag name to a list of line numbers
302 302 if name not in hextaglines:
303 303 hextaglines[name] = []
304 304 hextaglines[name].append([nodehex, nline])
305 305 continue
306 306 # map tag name to (node, hist)
307 307 if name not in bintaghist:
308 308 bintaghist[name] = []
309 309 bintaghist[name].append(nodebin)
310 310 return bintaghist, hextaglines
311 311
312 312 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
313 313 '''Read tag definitions from a file (or any source of lines).
314 314
315 315 Returns a mapping from tag name to (node, hist).
316 316
317 317 "node" is the node id from the last line read for that name. "hist"
318 318 is the list of node ids previously associated with it (in file order).
319 319 All node ids are binary, not hex.
320 320 '''
321 321 filetags, nodelines = _readtaghist(ui, repo, lines, fn, recode=recode,
322 322 calcnodelines=calcnodelines)
323 323 # util.sortdict().__setitem__ is much slower at replacing then inserting
324 324 # new entries. The difference can matter if there are thousands of tags.
325 325 # Create a new sortdict to avoid the performance penalty.
326 326 newtags = util.sortdict()
327 327 for tag, taghist in filetags.items():
328 328 newtags[tag] = (taghist[-1], taghist[:-1])
329 329 return newtags
330 330
331 331 def _updatetags(filetags, alltags, tagtype=None, tagtypes=None):
332 332 """Incorporate the tag info read from one file into dictionnaries
333 333
334 334 The first one, 'alltags', is a "tagmaps" (see 'findglobaltags' for details).
335 335
336 336 The second one, 'tagtypes', is optional and will be updated to track the
337 337 "tagtype" of entries in the tagmaps. When set, the 'tagtype' argument also
338 338 needs to be set."""
339 339 if tagtype is None:
340 340 assert tagtypes is None
341 341
342 342 for name, nodehist in filetags.iteritems():
343 343 if name not in alltags:
344 344 alltags[name] = nodehist
345 345 if tagtype is not None:
346 346 tagtypes[name] = tagtype
347 347 continue
348 348
349 349 # we prefer alltags[name] if:
350 350 # it supersedes us OR
351 351 # mutual supersedes and it has a higher rank
352 352 # otherwise we win because we're tip-most
353 353 anode, ahist = nodehist
354 354 bnode, bhist = alltags[name]
355 355 if (bnode != anode and anode in bhist and
356 356 (bnode not in ahist or len(bhist) > len(ahist))):
357 357 anode = bnode
358 358 elif tagtype is not None:
359 359 tagtypes[name] = tagtype
360 360 ahist.extend([n for n in bhist if n not in ahist])
361 361 alltags[name] = anode, ahist
362 362
363 363 def _filename(repo):
364 364 """name of a tagcache file for a given repo or repoview"""
365 365 filename = 'tags2'
366 366 if repo.filtername:
367 367 filename = '%s-%s' % (filename, repo.filtername)
368 368 return filename
369 369
370 370 def _readtagcache(ui, repo):
371 371 '''Read the tag cache.
372 372
373 373 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
374 374
375 375 If the cache is completely up-to-date, "cachetags" is a dict of the
376 376 form returned by _readtags() and "heads", "fnodes", and "validinfo" are
377 377 None and "shouldwrite" is False.
378 378
379 379 If the cache is not up to date, "cachetags" is None. "heads" is a list
380 380 of all heads currently in the repository, ordered from tip to oldest.
381 381 "validinfo" is a tuple describing cache validation info. This is used
382 382 when writing the tags cache. "fnodes" is a mapping from head to .hgtags
383 383 filenode. "shouldwrite" is True.
384 384
385 385 If the cache is not up to date, the caller is responsible for reading tag
386 386 info from each returned head. (See findglobaltags().)
387 387 '''
388 388 try:
389 389 cachefile = repo.cachevfs(_filename(repo), 'r')
390 390 # force reading the file for static-http
391 391 cachelines = iter(cachefile)
392 392 except IOError:
393 393 cachefile = None
394 394
395 395 cacherev = None
396 396 cachenode = None
397 397 cachehash = None
398 398 if cachefile:
399 399 try:
400 400 validline = next(cachelines)
401 401 validline = validline.split()
402 402 cacherev = int(validline[0])
403 403 cachenode = bin(validline[1])
404 404 if len(validline) > 2:
405 405 cachehash = bin(validline[2])
406 406 except Exception:
407 407 # corruption of the cache, just recompute it.
408 408 pass
409 409
410 410 tipnode = repo.changelog.tip()
411 411 tiprev = len(repo.changelog) - 1
412 412
413 413 # Case 1 (common): tip is the same, so nothing has changed.
414 414 # (Unchanged tip trivially means no changesets have been added.
415 415 # But, thanks to localrepository.destroyed(), it also means none
416 416 # have been destroyed by strip or rollback.)
417 417 if (cacherev == tiprev
418 418 and cachenode == tipnode
419 419 and cachehash == scmutil.filteredhash(repo, tiprev)):
420 420 tags = _readtags(ui, repo, cachelines, cachefile.name)
421 421 cachefile.close()
422 422 return (None, None, None, tags, False)
423 423 if cachefile:
424 424 cachefile.close() # ignore rest of file
425 425
426 426 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
427 427
428 428 repoheads = repo.heads()
429 429 # Case 2 (uncommon): empty repo; get out quickly and don't bother
430 430 # writing an empty cache.
431 431 if repoheads == [nullid]:
432 432 return ([], {}, valid, {}, False)
433 433
434 434 # Case 3 (uncommon): cache file missing or empty.
435 435
436 436 # Case 4 (uncommon): tip rev decreased. This should only happen
437 437 # when we're called from localrepository.destroyed(). Refresh the
438 438 # cache so future invocations will not see disappeared heads in the
439 439 # cache.
440 440
441 441 # Case 5 (common): tip has changed, so we've added/replaced heads.
442 442
443 443 # As it happens, the code to handle cases 3, 4, 5 is the same.
444 444
445 445 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
446 446 # exposed".
447 447 if not len(repo.file('.hgtags')):
448 448 # No tags have ever been committed, so we can avoid a
449 449 # potentially expensive search.
450 450 return ([], {}, valid, None, True)
451 451
452 452
453 453 # Now we have to lookup the .hgtags filenode for every new head.
454 454 # This is the most expensive part of finding tags, so performance
455 455 # depends primarily on the size of newheads. Worst case: no cache
456 456 # file, so newheads == repoheads.
457 457 cachefnode = _getfnodes(ui, repo, repoheads)
458 458
459 459 # Caller has to iterate over all heads, but can use the filenodes in
460 460 # cachefnode to get to each .hgtags revision quickly.
461 461 return (repoheads, cachefnode, valid, None, True)
462 462
463 463 def _getfnodes(ui, repo, nodes):
464 464 """return .hgtags fnodes for a list of changeset nodes
465 465
466 466 Return value is a {node: fnode} mapping. There will be no entry for nodes
467 467 without a '.hgtags' file.
468 468 """
469 469 starttime = util.timer()
470 470 fnodescache = hgtagsfnodescache(repo.unfiltered())
471 471 cachefnode = {}
472 472 for node in reversed(nodes):
473 473 fnode = fnodescache.getfnode(node)
474 474 if fnode != nullid:
475 475 cachefnode[node] = fnode
476 476
477 477 fnodescache.write()
478 478
479 479 duration = util.timer() - starttime
480 480 ui.log('tagscache',
481 481 '%d/%d cache hits/lookups in %0.4f '
482 482 'seconds\n',
483 483 fnodescache.hitcount, fnodescache.lookupcount, duration)
484 484 return cachefnode
485 485
486 486 def _writetagcache(ui, repo, valid, cachetags):
487 487 filename = _filename(repo)
488 488 try:
489 489 cachefile = repo.cachevfs(filename, 'w', atomictemp=True)
490 490 except (OSError, IOError):
491 491 return
492 492
493 493 ui.log('tagscache', 'writing .hg/cache/%s with %d tags\n',
494 494 filename, len(cachetags))
495 495
496 496 if valid[2]:
497 497 cachefile.write('%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2])))
498 498 else:
499 499 cachefile.write('%d %s\n' % (valid[0], hex(valid[1])))
500 500
501 501 # Tag names in the cache are in UTF-8 -- which is the whole reason
502 502 # we keep them in UTF-8 throughout this module. If we converted
503 503 # them local encoding on input, we would lose info writing them to
504 504 # the cache.
505 505 for (name, (node, hist)) in sorted(cachetags.iteritems()):
506 506 for n in hist:
507 507 cachefile.write("%s %s\n" % (hex(n), name))
508 508 cachefile.write("%s %s\n" % (hex(node), name))
509 509
510 510 try:
511 511 cachefile.close()
512 512 except (OSError, IOError):
513 513 pass
514 514
515 515 def tag(repo, names, node, message, local, user, date, editor=False):
516 516 '''tag a revision with one or more symbolic names.
517 517
518 518 names is a list of strings or, when adding a single tag, names may be a
519 519 string.
520 520
521 521 if local is True, the tags are stored in a per-repository file.
522 522 otherwise, they are stored in the .hgtags file, and a new
523 523 changeset is committed with the change.
524 524
525 525 keyword arguments:
526 526
527 527 local: whether to store tags in non-version-controlled file
528 528 (default False)
529 529
530 530 message: commit message to use if committing
531 531
532 532 user: name of user to use if committing
533 533
534 534 date: date tuple to use if committing'''
535 535
536 536 if not local:
537 537 m = matchmod.exact(repo.root, '', ['.hgtags'])
538 538 if any(repo.status(match=m, unknown=True, ignored=True)):
539 539 raise error.Abort(_('working copy of .hgtags is changed'),
540 540 hint=_('please commit .hgtags manually'))
541 541
542 542 with repo.wlock():
543 543 repo.tags() # instantiate the cache
544 _tag(repo.unfiltered(), names, node, message, local, user, date,
544 _tag(repo, names, node, message, local, user, date,
545 545 editor=editor)
546 546
547 547 def _tag(repo, names, node, message, local, user, date, extra=None,
548 548 editor=False):
549 549 if isinstance(names, str):
550 550 names = (names,)
551 551
552 552 branches = repo.branchmap()
553 553 for name in names:
554 554 repo.hook('pretag', throw=True, node=hex(node), tag=name,
555 555 local=local)
556 556 if name in branches:
557 557 repo.ui.warn(_("warning: tag %s conflicts with existing"
558 558 " branch name\n") % name)
559 559
560 560 def writetags(fp, names, munge, prevtags):
561 561 fp.seek(0, 2)
562 562 if prevtags and prevtags[-1] != '\n':
563 563 fp.write('\n')
564 564 for name in names:
565 565 if munge:
566 566 m = munge(name)
567 567 else:
568 568 m = name
569 569
570 570 if (repo._tagscache.tagtypes and
571 571 name in repo._tagscache.tagtypes):
572 572 old = repo.tags().get(name, nullid)
573 573 fp.write('%s %s\n' % (hex(old), m))
574 574 fp.write('%s %s\n' % (hex(node), m))
575 575 fp.close()
576 576
577 577 prevtags = ''
578 578 if local:
579 579 try:
580 580 fp = repo.vfs('localtags', 'r+')
581 581 except IOError:
582 582 fp = repo.vfs('localtags', 'a')
583 583 else:
584 584 prevtags = fp.read()
585 585
586 586 # local tags are stored in the current charset
587 587 writetags(fp, names, None, prevtags)
588 588 for name in names:
589 589 repo.hook('tag', node=hex(node), tag=name, local=local)
590 590 return
591 591
592 592 try:
593 593 fp = repo.wvfs('.hgtags', 'rb+')
594 594 except IOError as e:
595 595 if e.errno != errno.ENOENT:
596 596 raise
597 597 fp = repo.wvfs('.hgtags', 'ab')
598 598 else:
599 599 prevtags = fp.read()
600 600
601 601 # committed tags are stored in UTF-8
602 602 writetags(fp, names, encoding.fromlocal, prevtags)
603 603
604 604 fp.close()
605 605
606 606 repo.invalidatecaches()
607 607
608 608 if '.hgtags' not in repo.dirstate:
609 609 repo[None].add(['.hgtags'])
610 610
611 611 m = matchmod.exact(repo.root, '', ['.hgtags'])
612 612 tagnode = repo.commit(message, user, date, extra=extra, match=m,
613 613 editor=editor)
614 614
615 615 for name in names:
616 616 repo.hook('tag', node=hex(node), tag=name, local=local)
617 617
618 618 return tagnode
619 619
620 620 _fnodescachefile = 'hgtagsfnodes1'
621 621 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
622 622 _fnodesmissingrec = '\xff' * 24
623 623
624 624 class hgtagsfnodescache(object):
625 625 """Persistent cache mapping revisions to .hgtags filenodes.
626 626
627 627 The cache is an array of records. Each item in the array corresponds to
628 628 a changelog revision. Values in the array contain the first 4 bytes of
629 629 the node hash and the 20 bytes .hgtags filenode for that revision.
630 630
631 631 The first 4 bytes are present as a form of verification. Repository
632 632 stripping and rewriting may change the node at a numeric revision in the
633 633 changelog. The changeset fragment serves as a verifier to detect
634 634 rewriting. This logic is shared with the rev branch cache (see
635 635 branchmap.py).
636 636
637 637 The instance holds in memory the full cache content but entries are
638 638 only parsed on read.
639 639
640 640 Instances behave like lists. ``c[i]`` works where i is a rev or
641 641 changeset node. Missing indexes are populated automatically on access.
642 642 """
643 643 def __init__(self, repo):
644 644 assert repo.filtername is None
645 645
646 646 self._repo = repo
647 647
648 648 # Only for reporting purposes.
649 649 self.lookupcount = 0
650 650 self.hitcount = 0
651 651
652 652
653 653 try:
654 654 data = repo.cachevfs.read(_fnodescachefile)
655 655 except (OSError, IOError):
656 656 data = ""
657 657 self._raw = bytearray(data)
658 658
659 659 # The end state of self._raw is an array that is of the exact length
660 660 # required to hold a record for every revision in the repository.
661 661 # We truncate or extend the array as necessary. self._dirtyoffset is
662 662 # defined to be the start offset at which we need to write the output
663 663 # file. This offset is also adjusted when new entries are calculated
664 664 # for array members.
665 665 cllen = len(repo.changelog)
666 666 wantedlen = cllen * _fnodesrecsize
667 667 rawlen = len(self._raw)
668 668
669 669 self._dirtyoffset = None
670 670
671 671 if rawlen < wantedlen:
672 672 self._dirtyoffset = rawlen
673 673 self._raw.extend('\xff' * (wantedlen - rawlen))
674 674 elif rawlen > wantedlen:
675 675 # There's no easy way to truncate array instances. This seems
676 676 # slightly less evil than copying a potentially large array slice.
677 677 for i in range(rawlen - wantedlen):
678 678 self._raw.pop()
679 679 self._dirtyoffset = len(self._raw)
680 680
681 681 def getfnode(self, node, computemissing=True):
682 682 """Obtain the filenode of the .hgtags file at a specified revision.
683 683
684 684 If the value is in the cache, the entry will be validated and returned.
685 685 Otherwise, the filenode will be computed and returned unless
686 686 "computemissing" is False, in which case None will be returned without
687 687 any potentially expensive computation being performed.
688 688
689 689 If an .hgtags does not exist at the specified revision, nullid is
690 690 returned.
691 691 """
692 692 ctx = self._repo[node]
693 693 rev = ctx.rev()
694 694
695 695 self.lookupcount += 1
696 696
697 697 offset = rev * _fnodesrecsize
698 698 record = '%s' % self._raw[offset:offset + _fnodesrecsize]
699 699 properprefix = node[0:4]
700 700
701 701 # Validate and return existing entry.
702 702 if record != _fnodesmissingrec:
703 703 fileprefix = record[0:4]
704 704
705 705 if fileprefix == properprefix:
706 706 self.hitcount += 1
707 707 return record[4:]
708 708
709 709 # Fall through.
710 710
711 711 # If we get here, the entry is either missing or invalid.
712 712
713 713 if not computemissing:
714 714 return None
715 715
716 716 # Populate missing entry.
717 717 try:
718 718 fnode = ctx.filenode('.hgtags')
719 719 except error.LookupError:
720 720 # No .hgtags file on this revision.
721 721 fnode = nullid
722 722
723 723 self._writeentry(offset, properprefix, fnode)
724 724 return fnode
725 725
726 726 def setfnode(self, node, fnode):
727 727 """Set the .hgtags filenode for a given changeset."""
728 728 assert len(fnode) == 20
729 729 ctx = self._repo[node]
730 730
731 731 # Do a lookup first to avoid writing if nothing has changed.
732 732 if self.getfnode(ctx.node(), computemissing=False) == fnode:
733 733 return
734 734
735 735 self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode)
736 736
737 737 def _writeentry(self, offset, prefix, fnode):
738 738 # Slices on array instances only accept other array.
739 739 entry = bytearray(prefix + fnode)
740 740 self._raw[offset:offset + _fnodesrecsize] = entry
741 741 # self._dirtyoffset could be None.
742 742 self._dirtyoffset = min(self._dirtyoffset, offset) or 0
743 743
744 744 def write(self):
745 745 """Perform all necessary writes to cache file.
746 746
747 747 This may no-op if no writes are needed or if a write lock could
748 748 not be obtained.
749 749 """
750 750 if self._dirtyoffset is None:
751 751 return
752 752
753 753 data = self._raw[self._dirtyoffset:]
754 754 if not data:
755 755 return
756 756
757 757 repo = self._repo
758 758
759 759 try:
760 760 lock = repo.wlock(wait=False)
761 761 except error.LockError:
762 762 repo.ui.log('tagscache', 'not writing .hg/cache/%s because '
763 763 'lock cannot be acquired\n' % (_fnodescachefile))
764 764 return
765 765
766 766 try:
767 767 f = repo.cachevfs.open(_fnodescachefile, 'ab')
768 768 try:
769 769 # if the file has been truncated
770 770 actualoffset = f.tell()
771 771 if actualoffset < self._dirtyoffset:
772 772 self._dirtyoffset = actualoffset
773 773 data = self._raw[self._dirtyoffset:]
774 774 f.seek(self._dirtyoffset)
775 775 f.truncate()
776 776 repo.ui.log('tagscache',
777 777 'writing %d bytes to cache/%s\n' % (
778 778 len(data), _fnodescachefile))
779 779 f.write(data)
780 780 self._dirtyoffset = None
781 781 finally:
782 782 f.close()
783 783 except (IOError, OSError) as inst:
784 784 repo.ui.log('tagscache',
785 785 "couldn't write cache/%s: %s\n" % (
786 786 _fnodescachefile, inst))
787 787 finally:
788 788 lock.release()
@@ -1,749 +1,802
1 1 $ cat >> $HGRCPATH << EOF
2 2 > [experimental]
3 3 > hook-track-tags=1
4 4 > [hooks]
5 5 > txnclose.track-tag=sh ${TESTTMP}/taghook.sh
6 6 > EOF
7 7
8 8 $ cat << EOF > taghook.sh
9 9 > #!/bin/sh
10 10 > # escape the "$" otherwise the test runner interpret it when writting the
11 11 > # file...
12 12 > if [ -n "\$HG_TAG_MOVED" ]; then
13 13 > echo 'hook: tag changes detected'
14 14 > sed 's/^/hook: /' .hg/changes/tags.changes
15 15 > fi
16 16 > EOF
17 17 $ hg init test
18 18 $ cd test
19 19
20 20 $ echo a > a
21 21 $ hg add a
22 22 $ hg commit -m "test"
23 23 $ hg history
24 24 changeset: 0:acb14030fe0a
25 25 tag: tip
26 26 user: test
27 27 date: Thu Jan 01 00:00:00 1970 +0000
28 28 summary: test
29 29
30 30
31 31 $ hg tag ' '
32 32 abort: tag names cannot consist entirely of whitespace
33 33 [255]
34 34
35 35 (this tests also that editor is not invoked, if '--edit' is not
36 36 specified)
37 37
38 38 $ HGEDITOR=cat hg tag "bleah"
39 39 hook: tag changes detected
40 40 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
41 41 $ hg history
42 42 changeset: 1:d4f0d2909abc
43 43 tag: tip
44 44 user: test
45 45 date: Thu Jan 01 00:00:00 1970 +0000
46 46 summary: Added tag bleah for changeset acb14030fe0a
47 47
48 48 changeset: 0:acb14030fe0a
49 49 tag: bleah
50 50 user: test
51 51 date: Thu Jan 01 00:00:00 1970 +0000
52 52 summary: test
53 53
54 54
55 55 $ echo foo >> .hgtags
56 56 $ hg tag "bleah2"
57 57 abort: working copy of .hgtags is changed
58 58 (please commit .hgtags manually)
59 59 [255]
60 60
61 61 $ hg revert .hgtags
62 62 $ hg tag -r 0 x y z y y z
63 63 abort: tag names must be unique
64 64 [255]
65 65 $ hg tag tap nada dot tip
66 66 abort: the name 'tip' is reserved
67 67 [255]
68 68 $ hg tag .
69 69 abort: the name '.' is reserved
70 70 [255]
71 71 $ hg tag null
72 72 abort: the name 'null' is reserved
73 73 [255]
74 74 $ hg tag "bleah"
75 75 abort: tag 'bleah' already exists (use -f to force)
76 76 [255]
77 77 $ hg tag "blecch" "bleah"
78 78 abort: tag 'bleah' already exists (use -f to force)
79 79 [255]
80 80
81 81 $ hg tag --remove "blecch"
82 82 abort: tag 'blecch' does not exist
83 83 [255]
84 84 $ hg tag --remove "bleah" "blecch" "blough"
85 85 abort: tag 'blecch' does not exist
86 86 [255]
87 87
88 88 $ hg tag -r 0 "bleah0"
89 89 hook: tag changes detected
90 90 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
91 91 $ hg tag -l -r 1 "bleah1"
92 92 $ hg tag gack gawk gorp
93 93 hook: tag changes detected
94 94 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gack
95 95 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gawk
96 96 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gorp
97 97 $ hg tag -f gack
98 98 hook: tag changes detected
99 99 hook: -M 336fccc858a4eb69609a291105009e484a6b6b8d gack
100 100 hook: +M 799667b6f2d9b957f73fa644a918c2df22bab58f gack
101 101 $ hg tag --remove gack gorp
102 102 hook: tag changes detected
103 103 hook: -R 799667b6f2d9b957f73fa644a918c2df22bab58f gack
104 104 hook: -R 336fccc858a4eb69609a291105009e484a6b6b8d gorp
105 105
106 106 $ hg tag "bleah "
107 107 abort: tag 'bleah' already exists (use -f to force)
108 108 [255]
109 109 $ hg tag " bleah"
110 110 abort: tag 'bleah' already exists (use -f to force)
111 111 [255]
112 112 $ hg tag " bleah"
113 113 abort: tag 'bleah' already exists (use -f to force)
114 114 [255]
115 115 $ hg tag -r 0 " bleahbleah "
116 116 hook: tag changes detected
117 117 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleahbleah
118 118 $ hg tag -r 0 " bleah bleah "
119 119 hook: tag changes detected
120 120 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah bleah
121 121
122 122 $ cat .hgtags
123 123 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
124 124 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
125 125 336fccc858a4eb69609a291105009e484a6b6b8d gack
126 126 336fccc858a4eb69609a291105009e484a6b6b8d gawk
127 127 336fccc858a4eb69609a291105009e484a6b6b8d gorp
128 128 336fccc858a4eb69609a291105009e484a6b6b8d gack
129 129 799667b6f2d9b957f73fa644a918c2df22bab58f gack
130 130 799667b6f2d9b957f73fa644a918c2df22bab58f gack
131 131 0000000000000000000000000000000000000000 gack
132 132 336fccc858a4eb69609a291105009e484a6b6b8d gorp
133 133 0000000000000000000000000000000000000000 gorp
134 134 acb14030fe0a21b60322c440ad2d20cf7685a376 bleahbleah
135 135 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah bleah
136 136
137 137 $ cat .hg/localtags
138 138 d4f0d2909abc9290e2773c08837d70c1794e3f5a bleah1
139 139
140 140 tagging on a non-head revision
141 141
142 142 $ hg update 0
143 143 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
144 144 $ hg tag -l localblah
145 145 $ hg tag "foobar"
146 146 abort: working directory is not at a branch head (use -f to force)
147 147 [255]
148 148 $ hg tag -f "foobar"
149 149 hook: tag changes detected
150 150 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
151 151 $ cat .hgtags
152 152 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
153 153 $ cat .hg/localtags
154 154 d4f0d2909abc9290e2773c08837d70c1794e3f5a bleah1
155 155 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
156 156
157 157 $ hg tag -l 'xx
158 158 > newline'
159 159 abort: '\n' cannot be used in a name
160 160 [255]
161 161 $ hg tag -l 'xx:xx'
162 162 abort: ':' cannot be used in a name
163 163 [255]
164 164
165 165 cloning local tags
166 166
167 167 $ cd ..
168 168 $ hg -R test log -r0:5
169 169 changeset: 0:acb14030fe0a
170 170 tag: bleah
171 171 tag: bleah bleah
172 172 tag: bleah0
173 173 tag: bleahbleah
174 174 tag: foobar
175 175 tag: localblah
176 176 user: test
177 177 date: Thu Jan 01 00:00:00 1970 +0000
178 178 summary: test
179 179
180 180 changeset: 1:d4f0d2909abc
181 181 tag: bleah1
182 182 user: test
183 183 date: Thu Jan 01 00:00:00 1970 +0000
184 184 summary: Added tag bleah for changeset acb14030fe0a
185 185
186 186 changeset: 2:336fccc858a4
187 187 tag: gawk
188 188 user: test
189 189 date: Thu Jan 01 00:00:00 1970 +0000
190 190 summary: Added tag bleah0 for changeset acb14030fe0a
191 191
192 192 changeset: 3:799667b6f2d9
193 193 user: test
194 194 date: Thu Jan 01 00:00:00 1970 +0000
195 195 summary: Added tag gack, gawk, gorp for changeset 336fccc858a4
196 196
197 197 changeset: 4:154eeb7c0138
198 198 user: test
199 199 date: Thu Jan 01 00:00:00 1970 +0000
200 200 summary: Added tag gack for changeset 799667b6f2d9
201 201
202 202 changeset: 5:b4bb47aaff09
203 203 user: test
204 204 date: Thu Jan 01 00:00:00 1970 +0000
205 205 summary: Removed tag gack, gorp
206 206
207 207 $ hg clone -q -rbleah1 test test1
208 208 hook: tag changes detected
209 209 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
210 210 $ hg -R test1 parents --style=compact
211 211 1[tip] d4f0d2909abc 1970-01-01 00:00 +0000 test
212 212 Added tag bleah for changeset acb14030fe0a
213 213
214 214 $ hg clone -q -r5 test#bleah1 test2
215 215 hook: tag changes detected
216 216 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
217 217 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
218 218 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gawk
219 219 $ hg -R test2 parents --style=compact
220 220 5[tip] b4bb47aaff09 1970-01-01 00:00 +0000 test
221 221 Removed tag gack, gorp
222 222
223 223 $ hg clone -q -U test#bleah1 test3
224 224 hook: tag changes detected
225 225 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
226 226 $ hg -R test3 parents --style=compact
227 227
228 228 $ cd test
229 229
230 230 Issue601: hg tag doesn't do the right thing if .hgtags or localtags
231 231 doesn't end with EOL
232 232
233 233 $ $PYTHON << EOF
234 234 > f = file('.hg/localtags'); last = f.readlines()[-1][:-1]; f.close()
235 235 > f = file('.hg/localtags', 'w'); f.write(last); f.close()
236 236 > EOF
237 237 $ cat .hg/localtags; echo
238 238 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
239 239 $ hg tag -l localnewline
240 240 $ cat .hg/localtags; echo
241 241 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
242 242 c2899151f4e76890c602a2597a650a72666681bf localnewline
243 243
244 244
245 245 $ $PYTHON << EOF
246 246 > f = file('.hgtags'); last = f.readlines()[-1][:-1]; f.close()
247 247 > f = file('.hgtags', 'w'); f.write(last); f.close()
248 248 > EOF
249 249 $ hg ci -m'broken manual edit of .hgtags'
250 250 $ cat .hgtags; echo
251 251 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
252 252 $ hg tag newline
253 253 hook: tag changes detected
254 254 hook: +A a0eea09de1eeec777b46f2085260a373b2fbc293 newline
255 255 $ cat .hgtags; echo
256 256 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
257 257 a0eea09de1eeec777b46f2085260a373b2fbc293 newline
258 258
259 259
260 260 tag and branch using same name
261 261
262 262 $ hg branch tag-and-branch-same-name
263 263 marked working directory as branch tag-and-branch-same-name
264 264 (branches are permanent and global, did you want a bookmark?)
265 265 $ hg ci -m"discouraged"
266 266 $ hg tag tag-and-branch-same-name
267 267 warning: tag tag-and-branch-same-name conflicts with existing branch name
268 268 hook: tag changes detected
269 269 hook: +A fc93d2ea1cd78e91216c6cfbbf26747c10ce11ae tag-and-branch-same-name
270 270
271 271 test custom commit messages
272 272
273 273 $ cat > editor.sh << '__EOF__'
274 274 > echo "==== before editing"
275 275 > cat "$1"
276 276 > echo "===="
277 277 > echo "custom tag message" > "$1"
278 278 > echo "second line" >> "$1"
279 279 > __EOF__
280 280
281 281 at first, test saving last-message.txt
282 282
283 283 (test that editor is not invoked before transaction starting)
284 284
285 285 $ cat > .hg/hgrc << '__EOF__'
286 286 > [hooks]
287 287 > # this failure occurs before editor invocation
288 288 > pretag.test-saving-lastmessage = false
289 289 > __EOF__
290 290 $ rm -f .hg/last-message.txt
291 291 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e
292 292 abort: pretag.test-saving-lastmessage hook exited with status 1
293 293 [255]
294 294 $ test -f .hg/last-message.txt
295 295 [1]
296 296
297 297 (test that editor is invoked and commit message is saved into
298 298 "last-message.txt")
299 299
300 300 $ cat >> .hg/hgrc << '__EOF__'
301 301 > [hooks]
302 302 > pretag.test-saving-lastmessage =
303 303 > # this failure occurs after editor invocation
304 304 > pretxncommit.unexpectedabort = false
305 305 > __EOF__
306 306
307 307 (this tests also that editor is invoked, if '--edit' is specified,
308 308 regardless of '--message')
309 309
310 310 $ rm -f .hg/last-message.txt
311 311 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e -m "foo bar"
312 312 ==== before editing
313 313 foo bar
314 314
315 315
316 316 HG: Enter commit message. Lines beginning with 'HG:' are removed.
317 317 HG: Leave message empty to abort commit.
318 318 HG: --
319 319 HG: user: test
320 320 HG: branch 'tag-and-branch-same-name'
321 321 HG: changed .hgtags
322 322 ====
323 323 note: commit message saved in .hg/last-message.txt
324 324 transaction abort!
325 325 rollback completed
326 326 abort: pretxncommit.unexpectedabort hook exited with status 1
327 327 [255]
328 328 $ cat .hg/last-message.txt
329 329 custom tag message
330 330 second line
331 331
332 332 $ cat >> .hg/hgrc << '__EOF__'
333 333 > [hooks]
334 334 > pretxncommit.unexpectedabort =
335 335 > __EOF__
336 336 $ hg status .hgtags
337 337 M .hgtags
338 338 $ hg revert --no-backup -q .hgtags
339 339
340 340 then, test custom commit message itself
341 341
342 342 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e
343 343 ==== before editing
344 344 Added tag custom-tag for changeset 75a534207be6
345 345
346 346
347 347 HG: Enter commit message. Lines beginning with 'HG:' are removed.
348 348 HG: Leave message empty to abort commit.
349 349 HG: --
350 350 HG: user: test
351 351 HG: branch 'tag-and-branch-same-name'
352 352 HG: changed .hgtags
353 353 ====
354 354 hook: tag changes detected
355 355 hook: +A 75a534207be6b03576e0c7a4fa5708d045f1c876 custom-tag
356 356 $ hg log -l1 --template "{desc}\n"
357 357 custom tag message
358 358 second line
359 359
360 360
361 361 local tag with .hgtags modified
362 362
363 363 $ hg tag hgtags-modified
364 364 hook: tag changes detected
365 365 hook: +A 0f26aaea6f74c3ed6c4aad8844403c9ba128d23a hgtags-modified
366 366 $ hg rollback
367 367 repository tip rolled back to revision 13 (undo commit)
368 368 working directory now based on revision 13
369 369 $ hg st
370 370 M .hgtags
371 371 ? .hgtags.orig
372 372 ? editor.sh
373 373 $ hg tag --local baz
374 374 $ hg revert --no-backup .hgtags
375 375
376 376
377 377 tagging when at named-branch-head that's not a topo-head
378 378
379 379 $ hg up default
380 380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
381 381 $ hg merge -t internal:local
382 382 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
383 383 (branch merge, don't forget to commit)
384 384 $ hg ci -m 'merge named branch'
385 385 hook: tag changes detected
386 386 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
387 387 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleah bleah
388 388 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
389 389 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleahbleah
390 390 hook: -R 336fccc858a4eb69609a291105009e484a6b6b8d gawk
391 391 $ hg up 13
392 392 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
393 393 $ hg tag new-topo-head
394 394 hook: tag changes detected
395 395 hook: +A 0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head
396 396
397 397 tagging on null rev
398 398
399 399 $ hg up null
400 400 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
401 401 $ hg tag nullrev
402 402 abort: working directory is not at a branch head (use -f to force)
403 403 [255]
404 404
405 405 $ hg init empty
406 406 $ hg tag -R empty nullrev
407 407 abort: cannot tag null revision
408 408 [255]
409 409
410 410 $ hg tag -R empty -r 00000000000 -f nulltag
411 411 abort: cannot tag null revision
412 412 [255]
413 413
414 issue5539: pruned tags do not appear in .hgtags
415
416 $ cat >> $HGRCPATH << EOF
417 > [experimental]
418 > stabilization=createmarkers,exchange
419 > EOF
420 $ hg up e4d483960b9b --quiet
421 $ echo aaa >>a
422 $ hg ci -maaa
423 $ hg log -r . -T "{node}\n"
424 743b3afd5aa69f130c246806e48ad2e699f490d2
425 $ hg tag issue5539
426 hook: tag changes detected
427 hook: +A 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
428 $ cat .hgtags
429 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
430 a0eea09de1eeec777b46f2085260a373b2fbc293 newline
431 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
432 $ hg log -r . -T "{node}\n"
433 abeb261f0508ecebcd345ce21e7a25112df417aa
434 (mimic 'hg prune' command by obsoleting current changeset and then moving to its parent)
435 $ hg debugobsolete abeb261f0508ecebcd345ce21e7a25112df417aa --record-parents
436 obsoleted 1 changesets
437 $ hg up ".^" --quiet
438 $ cat .hgtags
439 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
440 a0eea09de1eeec777b46f2085260a373b2fbc293 newline
441 $ echo bbb >>a
442 $ hg ci -mbbb
443 $ hg log -r . -T "{node}\n"
444 089dd20da58cae34165c37b064539c6ac0c6a0dd
445 $ hg tag issue5539
446 hook: tag changes detected
447 hook: -M 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
448 hook: +M 089dd20da58cae34165c37b064539c6ac0c6a0dd issue5539
449 $ hg id
450 0accf560a709 tip
451 $ cat .hgtags
452 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
453 a0eea09de1eeec777b46f2085260a373b2fbc293 newline
454 089dd20da58cae34165c37b064539c6ac0c6a0dd issue5539
455 $ hg tags
456 tip 19:0accf560a709
457 issue5539 18:089dd20da58c
458 new-topo-head 13:0f26aaea6f74
459 baz 13:0f26aaea6f74
460 custom-tag 12:75a534207be6
461 tag-and-branch-same-name 11:fc93d2ea1cd7
462 newline 9:a0eea09de1ee
463 localnewline 8:c2899151f4e7
464 localblah 0:acb14030fe0a
465 foobar 0:acb14030fe0a
466
414 467 $ cd ..
415 468
416 469 tagging on an uncommitted merge (issue2542)
417 470
418 471 $ hg init repo-tag-uncommitted-merge
419 472 $ cd repo-tag-uncommitted-merge
420 473 $ echo c1 > f1
421 474 $ hg ci -Am0
422 475 adding f1
423 476 $ echo c2 > f2
424 477 $ hg ci -Am1
425 478 adding f2
426 479 $ hg co -q 0
427 480 $ hg branch b1
428 481 marked working directory as branch b1
429 482 (branches are permanent and global, did you want a bookmark?)
430 483 $ hg ci -m2
431 484 $ hg up default
432 485 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
433 486 $ hg merge b1
434 487 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
435 488 (branch merge, don't forget to commit)
436 489
437 490 $ hg tag t1
438 491 abort: uncommitted merge
439 492 [255]
440 493 $ hg status
441 494 $ hg tag --rev 1 t2
442 495 abort: uncommitted merge
443 496 [255]
444 497 $ hg tag --rev 1 --local t3
445 498 $ hg tags -v
446 499 tip 2:2a156e8887cc
447 500 t3 1:c3adabd1a5f4 local
448 501
449 502 $ cd ..
450 503
451 504 commit hook on tag used to be run without write lock - issue3344
452 505
453 506 $ hg init repo-tag
454 507 $ touch repo-tag/test
455 508 $ hg -R repo-tag commit -A -m "test"
456 509 adding test
457 510 $ hg init repo-tag-target
458 511 $ cat > "$TESTTMP/issue3344.sh" <<EOF
459 512 > hg push "$TESTTMP/repo-tag-target"
460 513 > EOF
461 514 $ hg -R repo-tag --config hooks.commit="sh ../issue3344.sh" tag tag
462 515 hook: tag changes detected
463 516 hook: +A be090ea6625635128e90f7d89df8beeb2bcc1653 tag
464 517 pushing to $TESTTMP/repo-tag-target (glob)
465 518 searching for changes
466 519 adding changesets
467 520 adding manifests
468 521 adding file changes
469 522 added 2 changesets with 2 changes to 2 files
470 523 hook: tag changes detected
471 524 hook: +A be090ea6625635128e90f7d89df8beeb2bcc1653 tag
472 525
473 526 automatically merge resolvable tag conflicts (i.e. tags that differ in rank)
474 527 create two clones with some different tags as well as some common tags
475 528 check that we can merge tags that differ in rank
476 529
477 530 $ hg init repo-automatic-tag-merge
478 531 $ cd repo-automatic-tag-merge
479 532 $ echo c0 > f0
480 533 $ hg ci -A -m0
481 534 adding f0
482 535 $ hg tag tbase
483 536 hook: tag changes detected
484 537 hook: +A 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
485 538 $ hg up -qr '.^'
486 539 $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
487 540 1
488 541 $ hg up -q
489 542 $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
490 543 2
491 544 $ cd ..
492 545 $ hg clone repo-automatic-tag-merge repo-automatic-tag-merge-clone
493 546 updating to branch default
494 547 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
495 548 $ cd repo-automatic-tag-merge-clone
496 549 $ echo c1 > f1
497 550 $ hg ci -A -m1
498 551 adding f1
499 552 $ hg tag t1 t2 t3
500 553 hook: tag changes detected
501 554 hook: +A 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
502 555 hook: +A 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
503 556 hook: +A 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
504 557 $ hg tag --remove t2
505 558 hook: tag changes detected
506 559 hook: -R 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
507 560 $ hg tag t5
508 561 hook: tag changes detected
509 562 hook: +A 875517b4806a848f942811a315a5bce30804ae85 t5
510 563 $ echo c2 > f2
511 564 $ hg ci -A -m2
512 565 adding f2
513 566 $ hg tag -f t3
514 567 hook: tag changes detected
515 568 hook: -M 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
516 569 hook: +M 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
517 570
518 571 $ cd ../repo-automatic-tag-merge
519 572 $ echo c3 > f3
520 573 $ hg ci -A -m3
521 574 adding f3
522 575 $ hg tag -f t4 t5 t6
523 576 hook: tag changes detected
524 577 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
525 578 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
526 579 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
527 580
528 581 $ hg up -q '.^'
529 582 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
530 583 1 changes since t4:t5:t6
531 584 $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
532 585 0 changes since t4:t5:t6
533 586 $ echo c5 > f3
534 587 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
535 588 1 changes since t4:t5:t6
536 589 $ hg up -qC
537 590
538 591 $ hg tag --remove t5
539 592 hook: tag changes detected
540 593 hook: -R 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
541 594 $ echo c4 > f4
542 595 $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
543 596 2 changes since t4:t6
544 597 $ hg log -r '.' -T "{latesttag % '{latesttag}\n'}"
545 598 t4
546 599 t6
547 600 $ hg log -r '.' -T "{latesttag('t4') % 'T: {tag}, C: {changes}, D: {distance}\n'}"
548 601 T: t4, C: 2, D: 2
549 602 $ hg log -r '.' -T "{latesttag('re:\d') % 'T: {tag}, C: {changes}, D: {distance}\n'}"
550 603 T: t4, C: 2, D: 2
551 604 T: t6, C: 2, D: 2
552 605 $ hg log -r . -T '{join(latesttag(), "*")}\n'
553 606 t4*t6
554 607 $ hg ci -A -m4
555 608 adding f4
556 609 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
557 610 4 changes since t4:t6
558 611 $ hg tag t2
559 612 hook: tag changes detected
560 613 hook: +A 929bca7b18d067cbf3844c3896319a940059d748 t2
561 614 $ hg tag -f t6
562 615 hook: tag changes detected
563 616 hook: -M 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
564 617 hook: +M 09af2ce14077a94effef208b49a718f4836d4338 t6
565 618
566 619 $ cd ../repo-automatic-tag-merge-clone
567 620 $ hg pull
568 621 pulling from $TESTTMP/repo-automatic-tag-merge (glob)
569 622 searching for changes
570 623 adding changesets
571 624 adding manifests
572 625 adding file changes
573 626 added 6 changesets with 6 changes to 3 files (+1 heads)
574 627 hook: tag changes detected
575 628 hook: +A 929bca7b18d067cbf3844c3896319a940059d748 t2
576 629 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
577 630 hook: -R 875517b4806a848f942811a315a5bce30804ae85 t5
578 631 hook: +A 09af2ce14077a94effef208b49a718f4836d4338 t6
579 632 (run 'hg heads' to see heads, 'hg merge' to merge)
580 633 $ hg merge --tool internal:tagmerge
581 634 merging .hgtags
582 635 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
583 636 (branch merge, don't forget to commit)
584 637 $ hg status
585 638 M .hgtags
586 639 M f3
587 640 M f4
588 641 $ hg resolve -l
589 642 R .hgtags
590 643 $ cat .hgtags
591 644 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
592 645 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
593 646 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
594 647 09af2ce14077a94effef208b49a718f4836d4338 t6
595 648 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
596 649 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
597 650 929bca7b18d067cbf3844c3896319a940059d748 t2
598 651 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
599 652 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
600 653 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
601 654 0000000000000000000000000000000000000000 t2
602 655 875517b4806a848f942811a315a5bce30804ae85 t5
603 656 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
604 657 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
605 658 0000000000000000000000000000000000000000 t5
606 659 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
607 660 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
608 661
609 662 check that the merge tried to minimize the diff with the first merge parent
610 663
611 664 $ hg diff --git -r 'p1()' .hgtags
612 665 diff --git a/.hgtags b/.hgtags
613 666 --- a/.hgtags
614 667 +++ b/.hgtags
615 668 @@ -1,9 +1,17 @@
616 669 +9aa4e1292a27a248f8d07339bed9931d54907be7 t4
617 670 +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
618 671 +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
619 672 +09af2ce14077a94effef208b49a718f4836d4338 t6
620 673 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
621 674 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
622 675 +929bca7b18d067cbf3844c3896319a940059d748 t2
623 676 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
624 677 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
625 678 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
626 679 0000000000000000000000000000000000000000 t2
627 680 875517b4806a848f942811a315a5bce30804ae85 t5
628 681 +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
629 682 +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
630 683 +0000000000000000000000000000000000000000 t5
631 684 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
632 685 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
633 686
634 687 detect merge tag conflicts
635 688
636 689 $ hg update -C -r tip
637 690 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
638 691 $ hg tag t7
639 692 hook: tag changes detected
640 693 hook: +A b325cc5b642c5b465bdbe8c09627cb372de3d47d t7
641 694 $ hg update -C -r 'first(sort(head()))'
642 695 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
643 696 $ printf "%s %s\n" `hg log -r . --template "{node} t7"` >> .hgtags
644 697 $ hg commit -m "manually add conflicting t7 tag"
645 698 hook: tag changes detected
646 699 hook: -R 929bca7b18d067cbf3844c3896319a940059d748 t2
647 700 hook: +A 875517b4806a848f942811a315a5bce30804ae85 t5
648 701 hook: -M b325cc5b642c5b465bdbe8c09627cb372de3d47d t7
649 702 hook: +M ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
650 703 $ hg merge --tool internal:tagmerge
651 704 merging .hgtags
652 705 automatic .hgtags merge failed
653 706 the following 1 tags are in conflict: t7
654 707 automatic tag merging of .hgtags failed! (use 'hg resolve --tool :merge' or another merge tool of your choice)
655 708 2 files updated, 0 files merged, 0 files removed, 1 files unresolved
656 709 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
657 710 [1]
658 711 $ hg resolve -l
659 712 U .hgtags
660 713 $ cat .hgtags
661 714 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
662 715 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
663 716 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
664 717 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
665 718 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
666 719 0000000000000000000000000000000000000000 t2
667 720 875517b4806a848f942811a315a5bce30804ae85 t5
668 721 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
669 722 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
670 723 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
671 724
672 725 $ cd ..
673 726
674 727 handle the loss of tags
675 728
676 729 $ hg clone repo-automatic-tag-merge-clone repo-merge-lost-tags
677 730 updating to branch default
678 731 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
679 732 $ cd repo-merge-lost-tags
680 733 $ echo c5 > f5
681 734 $ hg ci -A -m5
682 735 adding f5
683 736 $ hg tag -f t7
684 737 hook: tag changes detected
685 738 hook: -M ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
686 739 hook: +M fd3a9e394ce3afb354a496323bf68ac1755a30de t7
687 740 $ hg update -r 'p1(t7)'
688 741 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
689 742 $ printf '' > .hgtags
690 743 $ hg commit -m 'delete all tags'
691 744 created new head
692 745 $ hg log -r 'max(t7::)'
693 746 changeset: 17:ffe462b50880
694 747 user: test
695 748 date: Thu Jan 01 00:00:00 1970 +0000
696 749 summary: Added tag t7 for changeset fd3a9e394ce3
697 750
698 751 $ hg update -r 'max(t7::)'
699 752 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
700 753 $ hg merge -r tip --tool internal:tagmerge
701 754 merging .hgtags
702 755 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
703 756 (branch merge, don't forget to commit)
704 757 $ hg resolve -l
705 758 R .hgtags
706 759 $ cat .hgtags
707 760 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
708 761 0000000000000000000000000000000000000000 tbase
709 762 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
710 763 0000000000000000000000000000000000000000 t1
711 764 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
712 765 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
713 766 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
714 767 0000000000000000000000000000000000000000 t2
715 768 875517b4806a848f942811a315a5bce30804ae85 t5
716 769 0000000000000000000000000000000000000000 t5
717 770 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
718 771 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
719 772 0000000000000000000000000000000000000000 t3
720 773 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
721 774 0000000000000000000000000000000000000000 t7
722 775 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
723 776 fd3a9e394ce3afb354a496323bf68ac1755a30de t7
724 777
725 778 also check that we minimize the diff with the 1st merge parent
726 779
727 780 $ hg diff --git -r 'p1()' .hgtags
728 781 diff --git a/.hgtags b/.hgtags
729 782 --- a/.hgtags
730 783 +++ b/.hgtags
731 784 @@ -1,12 +1,17 @@
732 785 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
733 786 +0000000000000000000000000000000000000000 tbase
734 787 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
735 788 +0000000000000000000000000000000000000000 t1
736 789 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
737 790 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
738 791 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
739 792 0000000000000000000000000000000000000000 t2
740 793 875517b4806a848f942811a315a5bce30804ae85 t5
741 794 +0000000000000000000000000000000000000000 t5
742 795 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
743 796 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
744 797 +0000000000000000000000000000000000000000 t3
745 798 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
746 799 +0000000000000000000000000000000000000000 t7
747 800 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
748 801 fd3a9e394ce3afb354a496323bf68ac1755a30de t7
749 802
General Comments 0
You need to be logged in to leave comments. Login now