##// END OF EJS Templates
templatekw: make {latesttag} a hybrid list...
Matt Harbison -
r25727:b8245386 default
parent child Browse files
Show More
@@ -1,486 +1,490 b''
1 1 # templatekw.py - common changeset template keywords
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex
9 9 import patch, util, error
10 10 import hbisect
11 11
12 12 # This helper class allows us to handle both:
13 13 # "{files}" (legacy command-line-specific list hack) and
14 14 # "{files % '{file}\n'}" (hgweb-style with inlining and function support)
15 15 # and to access raw values:
16 16 # "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
17 17 # "{get(extras, key)}"
18 18
19 19 class _hybrid(object):
20 20 def __init__(self, gen, values, makemap, joinfmt=None):
21 21 self.gen = gen
22 22 self.values = values
23 23 self._makemap = makemap
24 24 if joinfmt:
25 25 self.joinfmt = joinfmt
26 26 else:
27 27 self.joinfmt = lambda x: x.values()[0]
28 28 def __iter__(self):
29 29 return self.gen
30 30 def __call__(self):
31 31 makemap = self._makemap
32 32 for x in self.values:
33 33 yield makemap(x)
34 34 def __contains__(self, x):
35 35 return x in self.values
36 36 def __len__(self):
37 37 return len(self.values)
38 38 def __getattr__(self, name):
39 39 if name != 'get':
40 40 raise AttributeError(name)
41 41 return getattr(self.values, name)
42 42
43 43 def showlist(name, values, plural=None, element=None, separator=' ', **args):
44 44 if not element:
45 45 element = name
46 46 f = _showlist(name, values, plural, separator, **args)
47 47 return _hybrid(f, values, lambda x: {element: x})
48 48
49 49 def _showlist(name, values, plural=None, separator=' ', **args):
50 50 '''expand set of values.
51 51 name is name of key in template map.
52 52 values is list of strings or dicts.
53 53 plural is plural of name, if not simply name + 's'.
54 54 separator is used to join values as a string
55 55
56 56 expansion works like this, given name 'foo'.
57 57
58 58 if values is empty, expand 'no_foos'.
59 59
60 60 if 'foo' not in template map, return values as a string,
61 61 joined by 'separator'.
62 62
63 63 expand 'start_foos'.
64 64
65 65 for each value, expand 'foo'. if 'last_foo' in template
66 66 map, expand it instead of 'foo' for last key.
67 67
68 68 expand 'end_foos'.
69 69 '''
70 70 templ = args['templ']
71 71 if plural:
72 72 names = plural
73 73 else: names = name + 's'
74 74 if not values:
75 75 noname = 'no_' + names
76 76 if noname in templ:
77 77 yield templ(noname, **args)
78 78 return
79 79 if name not in templ:
80 80 if isinstance(values[0], str):
81 81 yield separator.join(values)
82 82 else:
83 83 for v in values:
84 84 yield dict(v, **args)
85 85 return
86 86 startname = 'start_' + names
87 87 if startname in templ:
88 88 yield templ(startname, **args)
89 89 vargs = args.copy()
90 90 def one(v, tag=name):
91 91 try:
92 92 vargs.update(v)
93 93 except (AttributeError, ValueError):
94 94 try:
95 95 for a, b in v:
96 96 vargs[a] = b
97 97 except ValueError:
98 98 vargs[name] = v
99 99 return templ(tag, **vargs)
100 100 lastname = 'last_' + name
101 101 if lastname in templ:
102 102 last = values.pop()
103 103 else:
104 104 last = None
105 105 for v in values:
106 106 yield one(v)
107 107 if last is not None:
108 108 yield one(last, tag=lastname)
109 109 endname = 'end_' + names
110 110 if endname in templ:
111 111 yield templ(endname, **args)
112 112
113 113 def getfiles(repo, ctx, revcache):
114 114 if 'files' not in revcache:
115 115 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
116 116 return revcache['files']
117 117
118 118 def getlatesttags(repo, ctx, cache):
119 119 '''return date, distance and name for the latest tag of rev'''
120 120
121 121 if 'latesttags' not in cache:
122 122 # Cache mapping from rev to a tuple with tag date, tag
123 123 # distance and tag name
124 124 cache['latesttags'] = {-1: (0, 0, ['null'])}
125 125 latesttags = cache['latesttags']
126 126
127 127 rev = ctx.rev()
128 128 todo = [rev]
129 129 while todo:
130 130 rev = todo.pop()
131 131 if rev in latesttags:
132 132 continue
133 133 ctx = repo[rev]
134 134 tags = [t for t in ctx.tags()
135 135 if (repo.tagtype(t) and repo.tagtype(t) != 'local')]
136 136 if tags:
137 137 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
138 138 continue
139 139 try:
140 140 # The tuples are laid out so the right one can be found by
141 141 # comparison.
142 142 pdate, pdist, ptag = max(
143 143 latesttags[p.rev()] for p in ctx.parents())
144 144 except KeyError:
145 145 # Cache miss - recurse
146 146 todo.append(rev)
147 147 todo.extend(p.rev() for p in ctx.parents())
148 148 continue
149 149 latesttags[rev] = pdate, pdist + 1, ptag
150 150 return latesttags[rev]
151 151
152 152 def getrenamedfn(repo, endrev=None):
153 153 rcache = {}
154 154 if endrev is None:
155 155 endrev = len(repo)
156 156
157 157 def getrenamed(fn, rev):
158 158 '''looks up all renames for a file (up to endrev) the first
159 159 time the file is given. It indexes on the changerev and only
160 160 parses the manifest if linkrev != changerev.
161 161 Returns rename info for fn at changerev rev.'''
162 162 if fn not in rcache:
163 163 rcache[fn] = {}
164 164 fl = repo.file(fn)
165 165 for i in fl:
166 166 lr = fl.linkrev(i)
167 167 renamed = fl.renamed(fl.node(i))
168 168 rcache[fn][lr] = renamed
169 169 if lr >= endrev:
170 170 break
171 171 if rev in rcache[fn]:
172 172 return rcache[fn][rev]
173 173
174 174 # If linkrev != rev (i.e. rev not found in rcache) fallback to
175 175 # filectx logic.
176 176 try:
177 177 return repo[rev][fn].renamed()
178 178 except error.LookupError:
179 179 return None
180 180
181 181 return getrenamed
182 182
183 183
184 184 def showauthor(repo, ctx, templ, **args):
185 185 """:author: String. The unmodified author of the changeset."""
186 186 return ctx.user()
187 187
188 188 def showbisect(repo, ctx, templ, **args):
189 189 """:bisect: String. The changeset bisection status."""
190 190 return hbisect.label(repo, ctx.node())
191 191
192 192 def showbranch(**args):
193 193 """:branch: String. The name of the branch on which the changeset was
194 194 committed.
195 195 """
196 196 return args['ctx'].branch()
197 197
198 198 def showbranches(**args):
199 199 """:branches: List of strings. The name of the branch on which the
200 200 changeset was committed. Will be empty if the branch name was
201 201 default.
202 202 """
203 203 branch = args['ctx'].branch()
204 204 if branch != 'default':
205 205 return showlist('branch', [branch], plural='branches', **args)
206 206 return showlist('branch', [], plural='branches', **args)
207 207
208 208 def showbookmarks(**args):
209 209 """:bookmarks: List of strings. Any bookmarks associated with the
210 210 changeset. Also sets 'active', the name of the active bookmark.
211 211 """
212 212 repo = args['ctx']._repo
213 213 bookmarks = args['ctx'].bookmarks()
214 214 active = repo._activebookmark
215 215 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
216 216 f = _showlist('bookmark', bookmarks, **args)
217 217 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
218 218
219 219 def showchildren(**args):
220 220 """:children: List of strings. The children of the changeset."""
221 221 ctx = args['ctx']
222 222 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
223 223 return showlist('children', childrevs, element='child', **args)
224 224
225 225 # Deprecated, but kept alive for help generation a purpose.
226 226 def showcurrentbookmark(**args):
227 227 """:currentbookmark: String. The active bookmark, if it is
228 228 associated with the changeset (DEPRECATED)"""
229 229 return showactivebookmark(**args)
230 230
231 231 def showactivebookmark(**args):
232 232 """:activebookmark: String. The active bookmark, if it is
233 233 associated with the changeset"""
234 234 active = args['repo']._activebookmark
235 235 if active and active in args['ctx'].bookmarks():
236 236 return active
237 237 return ''
238 238
239 239 def showdate(repo, ctx, templ, **args):
240 240 """:date: Date information. The date when the changeset was committed."""
241 241 return ctx.date()
242 242
243 243 def showdescription(repo, ctx, templ, **args):
244 244 """:desc: String. The text of the changeset description."""
245 245 return ctx.description().strip()
246 246
247 247 def showdiffstat(repo, ctx, templ, **args):
248 248 """:diffstat: String. Statistics of changes with the following format:
249 249 "modified files: +added/-removed lines"
250 250 """
251 251 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
252 252 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
253 253 return '%s: +%s/-%s' % (len(stats), adds, removes)
254 254
255 255 def showextras(**args):
256 256 """:extras: List of dicts with key, value entries of the 'extras'
257 257 field of this changeset."""
258 258 extras = args['ctx'].extra()
259 259 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
260 260 makemap = lambda k: {'key': k, 'value': extras[k]}
261 261 c = [makemap(k) for k in extras]
262 262 f = _showlist('extra', c, plural='extras', **args)
263 263 return _hybrid(f, extras, makemap,
264 264 lambda x: '%s=%s' % (x['key'], x['value']))
265 265
266 266 def showfileadds(**args):
267 267 """:file_adds: List of strings. Files added by this changeset."""
268 268 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
269 269 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
270 270 element='file', **args)
271 271
272 272 def showfilecopies(**args):
273 273 """:file_copies: List of strings. Files copied in this changeset with
274 274 their sources.
275 275 """
276 276 cache, ctx = args['cache'], args['ctx']
277 277 copies = args['revcache'].get('copies')
278 278 if copies is None:
279 279 if 'getrenamed' not in cache:
280 280 cache['getrenamed'] = getrenamedfn(args['repo'])
281 281 copies = []
282 282 getrenamed = cache['getrenamed']
283 283 for fn in ctx.files():
284 284 rename = getrenamed(fn, ctx.rev())
285 285 if rename:
286 286 copies.append((fn, rename[0]))
287 287
288 288 copies = util.sortdict(copies)
289 289 makemap = lambda k: {'name': k, 'source': copies[k]}
290 290 c = [makemap(k) for k in copies]
291 291 f = _showlist('file_copy', c, plural='file_copies', **args)
292 292 return _hybrid(f, copies, makemap,
293 293 lambda x: '%s (%s)' % (x['name'], x['source']))
294 294
295 295 # showfilecopiesswitch() displays file copies only if copy records are
296 296 # provided before calling the templater, usually with a --copies
297 297 # command line switch.
298 298 def showfilecopiesswitch(**args):
299 299 """:file_copies_switch: List of strings. Like "file_copies" but displayed
300 300 only if the --copied switch is set.
301 301 """
302 302 copies = args['revcache'].get('copies') or []
303 303 copies = util.sortdict(copies)
304 304 makemap = lambda k: {'name': k, 'source': copies[k]}
305 305 c = [makemap(k) for k in copies]
306 306 f = _showlist('file_copy', c, plural='file_copies', **args)
307 307 return _hybrid(f, copies, makemap,
308 308 lambda x: '%s (%s)' % (x['name'], x['source']))
309 309
310 310 def showfiledels(**args):
311 311 """:file_dels: List of strings. Files removed by this changeset."""
312 312 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
313 313 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
314 314 element='file', **args)
315 315
316 316 def showfilemods(**args):
317 317 """:file_mods: List of strings. Files modified by this changeset."""
318 318 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
319 319 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
320 320 element='file', **args)
321 321
322 322 def showfiles(**args):
323 323 """:files: List of strings. All files modified, added, or removed by this
324 324 changeset.
325 325 """
326 326 return showlist('file', args['ctx'].files(), **args)
327 327
328 def showlatesttag(repo, ctx, templ, cache, **args):
329 """:latesttag: String. Most recent global tag in the ancestors of this
330 changeset.
328 def showlatesttag(**args):
329 """:latesttag: List of strings. The global tags on the most recent globally
330 tagged ancestor of this changeset.
331 331 """
332 return ':'.join(getlatesttags(repo, ctx, cache)[2])
332 repo, ctx = args['repo'], args['ctx']
333 cache = args['cache']
334 latesttags = getlatesttags(repo, ctx, cache)[2]
335
336 return showlist('latesttag', latesttags, separator=':', **args)
333 337
334 338 def showlatesttagdistance(repo, ctx, templ, cache, **args):
335 339 """:latesttagdistance: Integer. Longest path to the latest tag."""
336 340 return getlatesttags(repo, ctx, cache)[1]
337 341
338 342 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
339 343 """:changessincelatesttag: Integer. All ancestors not in the latest tag."""
340 344 latesttag = getlatesttags(repo, ctx, cache)[2][0]
341 345 offset = 0
342 346 revs = [ctx.rev()]
343 347
344 348 # The only() revset doesn't currently support wdir()
345 349 if ctx.rev() is None:
346 350 offset = 1
347 351 revs = [p.rev() for p in ctx.parents()]
348 352
349 353 return len(repo.revs('only(%ld, %s)', revs, latesttag)) + offset
350 354
351 355 def showmanifest(**args):
352 356 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
353 357 mnode = ctx.manifestnode()
354 358 args = args.copy()
355 359 args.update({'rev': repo.manifest.rev(mnode), 'node': hex(mnode)})
356 360 return templ('manifest', **args)
357 361
358 362 def shownode(repo, ctx, templ, **args):
359 363 """:node: String. The changeset identification hash, as a 40 hexadecimal
360 364 digit string.
361 365 """
362 366 return ctx.hex()
363 367
364 368 def showp1rev(repo, ctx, templ, **args):
365 369 """:p1rev: Integer. The repository-local revision number of the changeset's
366 370 first parent, or -1 if the changeset has no parents."""
367 371 return ctx.p1().rev()
368 372
369 373 def showp2rev(repo, ctx, templ, **args):
370 374 """:p2rev: Integer. The repository-local revision number of the changeset's
371 375 second parent, or -1 if the changeset has no second parent."""
372 376 return ctx.p2().rev()
373 377
374 378 def showp1node(repo, ctx, templ, **args):
375 379 """:p1node: String. The identification hash of the changeset's first parent,
376 380 as a 40 digit hexadecimal string. If the changeset has no parents, all
377 381 digits are 0."""
378 382 return ctx.p1().hex()
379 383
380 384 def showp2node(repo, ctx, templ, **args):
381 385 """:p2node: String. The identification hash of the changeset's second
382 386 parent, as a 40 digit hexadecimal string. If the changeset has no second
383 387 parent, all digits are 0."""
384 388 return ctx.p2().hex()
385 389
386 390 def showphase(repo, ctx, templ, **args):
387 391 """:phase: String. The changeset phase name."""
388 392 return ctx.phasestr()
389 393
390 394 def showphaseidx(repo, ctx, templ, **args):
391 395 """:phaseidx: Integer. The changeset phase index."""
392 396 return ctx.phase()
393 397
394 398 def showrev(repo, ctx, templ, **args):
395 399 """:rev: Integer. The repository-local changeset revision number."""
396 400 return ctx.rev()
397 401
398 402 def showsubrepos(**args):
399 403 """:subrepos: List of strings. Updated subrepositories in the changeset."""
400 404 ctx = args['ctx']
401 405 substate = ctx.substate
402 406 if not substate:
403 407 return showlist('subrepo', [], **args)
404 408 psubstate = ctx.parents()[0].substate or {}
405 409 subrepos = []
406 410 for sub in substate:
407 411 if sub not in psubstate or substate[sub] != psubstate[sub]:
408 412 subrepos.append(sub) # modified or newly added in ctx
409 413 for sub in psubstate:
410 414 if sub not in substate:
411 415 subrepos.append(sub) # removed in ctx
412 416 return showlist('subrepo', sorted(subrepos), **args)
413 417
414 418 def shownames(namespace, **args):
415 419 """helper method to generate a template keyword for a namespace"""
416 420 ctx = args['ctx']
417 421 repo = ctx.repo()
418 422 ns = repo.names[namespace]
419 423 names = ns.names(repo, ctx.node())
420 424 return showlist(ns.templatename, names, plural=namespace, **args)
421 425
422 426 # don't remove "showtags" definition, even though namespaces will put
423 427 # a helper function for "tags" keyword into "keywords" map automatically,
424 428 # because online help text is built without namespaces initialization
425 429 def showtags(**args):
426 430 """:tags: List of strings. Any tags associated with the changeset."""
427 431 return shownames('tags', **args)
428 432
429 433 # keywords are callables like:
430 434 # fn(repo, ctx, templ, cache, revcache, **args)
431 435 # with:
432 436 # repo - current repository instance
433 437 # ctx - the changectx being displayed
434 438 # templ - the templater instance
435 439 # cache - a cache dictionary for the whole templater run
436 440 # revcache - a cache dictionary for the current revision
437 441 keywords = {
438 442 'activebookmark': showactivebookmark,
439 443 'author': showauthor,
440 444 'bisect': showbisect,
441 445 'branch': showbranch,
442 446 'branches': showbranches,
443 447 'bookmarks': showbookmarks,
444 448 'changessincelatesttag': showchangessincelatesttag,
445 449 'children': showchildren,
446 450 # currentbookmark is deprecated
447 451 'currentbookmark': showcurrentbookmark,
448 452 'date': showdate,
449 453 'desc': showdescription,
450 454 'diffstat': showdiffstat,
451 455 'extras': showextras,
452 456 'file_adds': showfileadds,
453 457 'file_copies': showfilecopies,
454 458 'file_copies_switch': showfilecopiesswitch,
455 459 'file_dels': showfiledels,
456 460 'file_mods': showfilemods,
457 461 'files': showfiles,
458 462 'latesttag': showlatesttag,
459 463 'latesttagdistance': showlatesttagdistance,
460 464 'manifest': showmanifest,
461 465 'node': shownode,
462 466 'p1rev': showp1rev,
463 467 'p1node': showp1node,
464 468 'p2rev': showp2rev,
465 469 'p2node': showp2node,
466 470 'phase': showphase,
467 471 'phaseidx': showphaseidx,
468 472 'rev': showrev,
469 473 'subrepos': showsubrepos,
470 474 'tags': showtags,
471 475 }
472 476
473 477 def _showparents(**args):
474 478 """:parents: List of strings. The parents of the changeset in "rev:node"
475 479 format. If the changeset has only one "natural" parent (the predecessor
476 480 revision) nothing is shown."""
477 481 pass
478 482
479 483 dockeywords = {
480 484 'parents': _showparents,
481 485 }
482 486 dockeywords.update(keywords)
483 487 del dockeywords['branches']
484 488
485 489 # tell hggettext to extract docstrings from these functions:
486 490 i18nfunctions = dockeywords.values()
@@ -1,636 +1,639 b''
1 1 $ hg init test
2 2 $ cd test
3 3
4 4 $ echo a > a
5 5 $ hg add a
6 6 $ hg commit -m "test"
7 7 $ hg history
8 8 changeset: 0:acb14030fe0a
9 9 tag: tip
10 10 user: test
11 11 date: Thu Jan 01 00:00:00 1970 +0000
12 12 summary: test
13 13
14 14
15 15 $ hg tag ' '
16 16 abort: tag names cannot consist entirely of whitespace
17 17 [255]
18 18
19 19 (this tests also that editor is not invoked, if '--edit' is not
20 20 specified)
21 21
22 22 $ HGEDITOR=cat hg tag "bleah"
23 23 $ hg history
24 24 changeset: 1:d4f0d2909abc
25 25 tag: tip
26 26 user: test
27 27 date: Thu Jan 01 00:00:00 1970 +0000
28 28 summary: Added tag bleah for changeset acb14030fe0a
29 29
30 30 changeset: 0:acb14030fe0a
31 31 tag: bleah
32 32 user: test
33 33 date: Thu Jan 01 00:00:00 1970 +0000
34 34 summary: test
35 35
36 36
37 37 $ echo foo >> .hgtags
38 38 $ hg tag "bleah2"
39 39 abort: working copy of .hgtags is changed
40 40 (please commit .hgtags manually)
41 41 [255]
42 42
43 43 $ hg revert .hgtags
44 44 $ hg tag -r 0 x y z y y z
45 45 abort: tag names must be unique
46 46 [255]
47 47 $ hg tag tap nada dot tip
48 48 abort: the name 'tip' is reserved
49 49 [255]
50 50 $ hg tag .
51 51 abort: the name '.' is reserved
52 52 [255]
53 53 $ hg tag null
54 54 abort: the name 'null' is reserved
55 55 [255]
56 56 $ hg tag "bleah"
57 57 abort: tag 'bleah' already exists (use -f to force)
58 58 [255]
59 59 $ hg tag "blecch" "bleah"
60 60 abort: tag 'bleah' already exists (use -f to force)
61 61 [255]
62 62
63 63 $ hg tag --remove "blecch"
64 64 abort: tag 'blecch' does not exist
65 65 [255]
66 66 $ hg tag --remove "bleah" "blecch" "blough"
67 67 abort: tag 'blecch' does not exist
68 68 [255]
69 69
70 70 $ hg tag -r 0 "bleah0"
71 71 $ hg tag -l -r 1 "bleah1"
72 72 $ hg tag gack gawk gorp
73 73 $ hg tag -f gack
74 74 $ hg tag --remove gack gorp
75 75
76 76 $ hg tag "bleah "
77 77 abort: tag 'bleah' already exists (use -f to force)
78 78 [255]
79 79 $ hg tag " bleah"
80 80 abort: tag 'bleah' already exists (use -f to force)
81 81 [255]
82 82 $ hg tag " bleah"
83 83 abort: tag 'bleah' already exists (use -f to force)
84 84 [255]
85 85 $ hg tag -r 0 " bleahbleah "
86 86 $ hg tag -r 0 " bleah bleah "
87 87
88 88 $ cat .hgtags
89 89 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
90 90 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
91 91 336fccc858a4eb69609a291105009e484a6b6b8d gack
92 92 336fccc858a4eb69609a291105009e484a6b6b8d gawk
93 93 336fccc858a4eb69609a291105009e484a6b6b8d gorp
94 94 336fccc858a4eb69609a291105009e484a6b6b8d gack
95 95 799667b6f2d9b957f73fa644a918c2df22bab58f gack
96 96 799667b6f2d9b957f73fa644a918c2df22bab58f gack
97 97 0000000000000000000000000000000000000000 gack
98 98 336fccc858a4eb69609a291105009e484a6b6b8d gorp
99 99 0000000000000000000000000000000000000000 gorp
100 100 acb14030fe0a21b60322c440ad2d20cf7685a376 bleahbleah
101 101 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah bleah
102 102
103 103 $ cat .hg/localtags
104 104 d4f0d2909abc9290e2773c08837d70c1794e3f5a bleah1
105 105
106 106 tagging on a non-head revision
107 107
108 108 $ hg update 0
109 109 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
110 110 $ hg tag -l localblah
111 111 $ hg tag "foobar"
112 112 abort: not at a branch head (use -f to force)
113 113 [255]
114 114 $ hg tag -f "foobar"
115 115 $ cat .hgtags
116 116 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
117 117 $ cat .hg/localtags
118 118 d4f0d2909abc9290e2773c08837d70c1794e3f5a bleah1
119 119 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
120 120
121 121 $ hg tag -l 'xx
122 122 > newline'
123 123 abort: '\n' cannot be used in a name
124 124 [255]
125 125 $ hg tag -l 'xx:xx'
126 126 abort: ':' cannot be used in a name
127 127 [255]
128 128
129 129 cloning local tags
130 130
131 131 $ cd ..
132 132 $ hg -R test log -r0:5
133 133 changeset: 0:acb14030fe0a
134 134 tag: bleah
135 135 tag: bleah bleah
136 136 tag: bleah0
137 137 tag: bleahbleah
138 138 tag: foobar
139 139 tag: localblah
140 140 user: test
141 141 date: Thu Jan 01 00:00:00 1970 +0000
142 142 summary: test
143 143
144 144 changeset: 1:d4f0d2909abc
145 145 tag: bleah1
146 146 user: test
147 147 date: Thu Jan 01 00:00:00 1970 +0000
148 148 summary: Added tag bleah for changeset acb14030fe0a
149 149
150 150 changeset: 2:336fccc858a4
151 151 tag: gawk
152 152 user: test
153 153 date: Thu Jan 01 00:00:00 1970 +0000
154 154 summary: Added tag bleah0 for changeset acb14030fe0a
155 155
156 156 changeset: 3:799667b6f2d9
157 157 user: test
158 158 date: Thu Jan 01 00:00:00 1970 +0000
159 159 summary: Added tag gack, gawk, gorp for changeset 336fccc858a4
160 160
161 161 changeset: 4:154eeb7c0138
162 162 user: test
163 163 date: Thu Jan 01 00:00:00 1970 +0000
164 164 summary: Added tag gack for changeset 799667b6f2d9
165 165
166 166 changeset: 5:b4bb47aaff09
167 167 user: test
168 168 date: Thu Jan 01 00:00:00 1970 +0000
169 169 summary: Removed tag gack, gorp
170 170
171 171 $ hg clone -q -rbleah1 test test1
172 172 $ hg -R test1 parents --style=compact
173 173 1[tip] d4f0d2909abc 1970-01-01 00:00 +0000 test
174 174 Added tag bleah for changeset acb14030fe0a
175 175
176 176 $ hg clone -q -r5 test#bleah1 test2
177 177 $ hg -R test2 parents --style=compact
178 178 5[tip] b4bb47aaff09 1970-01-01 00:00 +0000 test
179 179 Removed tag gack, gorp
180 180
181 181 $ hg clone -q -U test#bleah1 test3
182 182 $ hg -R test3 parents --style=compact
183 183
184 184 $ cd test
185 185
186 186 Issue601: hg tag doesn't do the right thing if .hgtags or localtags
187 187 doesn't end with EOL
188 188
189 189 $ python << EOF
190 190 > f = file('.hg/localtags'); last = f.readlines()[-1][:-1]; f.close()
191 191 > f = file('.hg/localtags', 'w'); f.write(last); f.close()
192 192 > EOF
193 193 $ cat .hg/localtags; echo
194 194 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
195 195 $ hg tag -l localnewline
196 196 $ cat .hg/localtags; echo
197 197 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
198 198 c2899151f4e76890c602a2597a650a72666681bf localnewline
199 199
200 200
201 201 $ python << EOF
202 202 > f = file('.hgtags'); last = f.readlines()[-1][:-1]; f.close()
203 203 > f = file('.hgtags', 'w'); f.write(last); f.close()
204 204 > EOF
205 205 $ hg ci -m'broken manual edit of .hgtags'
206 206 $ cat .hgtags; echo
207 207 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
208 208 $ hg tag newline
209 209 $ cat .hgtags; echo
210 210 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
211 211 a0eea09de1eeec777b46f2085260a373b2fbc293 newline
212 212
213 213
214 214 tag and branch using same name
215 215
216 216 $ hg branch tag-and-branch-same-name
217 217 marked working directory as branch tag-and-branch-same-name
218 218 (branches are permanent and global, did you want a bookmark?)
219 219 $ hg ci -m"discouraged"
220 220 $ hg tag tag-and-branch-same-name
221 221 warning: tag tag-and-branch-same-name conflicts with existing branch name
222 222
223 223 test custom commit messages
224 224
225 225 $ cat > editor.sh << '__EOF__'
226 226 > echo "==== before editing"
227 227 > cat "$1"
228 228 > echo "===="
229 229 > echo "custom tag message" > "$1"
230 230 > echo "second line" >> "$1"
231 231 > __EOF__
232 232
233 233 at first, test saving last-message.txt
234 234
235 235 (test that editor is not invoked before transaction starting)
236 236
237 237 $ cat > .hg/hgrc << '__EOF__'
238 238 > [hooks]
239 239 > # this failure occurs before editor invocation
240 240 > pretag.test-saving-lastmessage = false
241 241 > __EOF__
242 242 $ rm -f .hg/last-message.txt
243 243 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e
244 244 abort: pretag.test-saving-lastmessage hook exited with status 1
245 245 [255]
246 246 $ test -f .hg/last-message.txt
247 247 [1]
248 248
249 249 (test that editor is invoked and commit message is saved into
250 250 "last-message.txt")
251 251
252 252 $ cat >> .hg/hgrc << '__EOF__'
253 253 > [hooks]
254 254 > pretag.test-saving-lastmessage =
255 255 > # this failure occurs after editor invocation
256 256 > pretxncommit.unexpectedabort = false
257 257 > __EOF__
258 258
259 259 (this tests also that editor is invoked, if '--edit' is specified,
260 260 regardless of '--message')
261 261
262 262 $ rm -f .hg/last-message.txt
263 263 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e -m "foo bar"
264 264 ==== before editing
265 265 foo bar
266 266
267 267
268 268 HG: Enter commit message. Lines beginning with 'HG:' are removed.
269 269 HG: Leave message empty to abort commit.
270 270 HG: --
271 271 HG: user: test
272 272 HG: branch 'tag-and-branch-same-name'
273 273 HG: changed .hgtags
274 274 ====
275 275 transaction abort!
276 276 rollback completed
277 277 note: commit message saved in .hg/last-message.txt
278 278 abort: pretxncommit.unexpectedabort hook exited with status 1
279 279 [255]
280 280 $ cat .hg/last-message.txt
281 281 custom tag message
282 282 second line
283 283
284 284 $ cat >> .hg/hgrc << '__EOF__'
285 285 > [hooks]
286 286 > pretxncommit.unexpectedabort =
287 287 > __EOF__
288 288 $ hg status .hgtags
289 289 M .hgtags
290 290 $ hg revert --no-backup -q .hgtags
291 291
292 292 then, test custom commit message itself
293 293
294 294 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e
295 295 ==== before editing
296 296 Added tag custom-tag for changeset 75a534207be6
297 297
298 298
299 299 HG: Enter commit message. Lines beginning with 'HG:' are removed.
300 300 HG: Leave message empty to abort commit.
301 301 HG: --
302 302 HG: user: test
303 303 HG: branch 'tag-and-branch-same-name'
304 304 HG: changed .hgtags
305 305 ====
306 306 $ hg log -l1 --template "{desc}\n"
307 307 custom tag message
308 308 second line
309 309
310 310
311 311 local tag with .hgtags modified
312 312
313 313 $ hg tag hgtags-modified
314 314 $ hg rollback
315 315 repository tip rolled back to revision 13 (undo commit)
316 316 working directory now based on revision 13
317 317 $ hg st
318 318 M .hgtags
319 319 ? .hgtags.orig
320 320 ? editor.sh
321 321 $ hg tag --local baz
322 322 $ hg revert --no-backup .hgtags
323 323
324 324
325 325 tagging when at named-branch-head that's not a topo-head
326 326
327 327 $ hg up default
328 328 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
329 329 $ hg merge -t internal:local
330 330 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
331 331 (branch merge, don't forget to commit)
332 332 $ hg ci -m 'merge named branch'
333 333 $ hg up 13
334 334 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
335 335 $ hg tag new-topo-head
336 336
337 337 tagging on null rev
338 338
339 339 $ hg up null
340 340 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
341 341 $ hg tag nullrev
342 342 abort: not at a branch head (use -f to force)
343 343 [255]
344 344
345 345 $ hg init empty
346 346 $ hg tag -R empty nullrev
347 347 abort: cannot tag null revision
348 348 [255]
349 349
350 350 $ hg tag -R empty -r 00000000000 -f nulltag
351 351 abort: cannot tag null revision
352 352 [255]
353 353
354 354 $ cd ..
355 355
356 356 tagging on an uncommitted merge (issue2542)
357 357
358 358 $ hg init repo-tag-uncommitted-merge
359 359 $ cd repo-tag-uncommitted-merge
360 360 $ echo c1 > f1
361 361 $ hg ci -Am0
362 362 adding f1
363 363 $ echo c2 > f2
364 364 $ hg ci -Am1
365 365 adding f2
366 366 $ hg co -q 0
367 367 $ hg branch b1
368 368 marked working directory as branch b1
369 369 (branches are permanent and global, did you want a bookmark?)
370 370 $ hg ci -m2
371 371 $ hg up default
372 372 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
373 373 $ hg merge b1
374 374 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
375 375 (branch merge, don't forget to commit)
376 376
377 377 $ hg tag t1
378 378 abort: uncommitted merge
379 379 [255]
380 380 $ hg status
381 381 $ hg tag --rev 1 t2
382 382 abort: uncommitted merge
383 383 [255]
384 384 $ hg tag --rev 1 --local t3
385 385 $ hg tags -v
386 386 tip 2:2a156e8887cc
387 387 t3 1:c3adabd1a5f4 local
388 388
389 389 $ cd ..
390 390
391 391 commit hook on tag used to be run without write lock - issue3344
392 392
393 393 $ hg init repo-tag
394 394 $ touch repo-tag/test
395 395 $ hg -R repo-tag commit -A -m "test"
396 396 adding test
397 397 $ hg init repo-tag-target
398 398 $ cat > "$TESTTMP/issue3344.sh" <<EOF
399 399 > hg push "$TESTTMP/repo-tag-target"
400 400 > EOF
401 401 $ hg -R repo-tag --config hooks.commit="sh ../issue3344.sh" tag tag
402 402 pushing to $TESTTMP/repo-tag-target (glob)
403 403 searching for changes
404 404 adding changesets
405 405 adding manifests
406 406 adding file changes
407 407 added 2 changesets with 2 changes to 2 files
408 408
409 409 automatically merge resolvable tag conflicts (i.e. tags that differ in rank)
410 410 create two clones with some different tags as well as some common tags
411 411 check that we can merge tags that differ in rank
412 412
413 413 $ hg init repo-automatic-tag-merge
414 414 $ cd repo-automatic-tag-merge
415 415 $ echo c0 > f0
416 416 $ hg ci -A -m0
417 417 adding f0
418 418 $ hg tag tbase
419 419 $ hg up -qr '.^'
420 420 $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
421 421 1
422 422 $ hg up -q
423 423 $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
424 424 2
425 425 $ cd ..
426 426 $ hg clone repo-automatic-tag-merge repo-automatic-tag-merge-clone
427 427 updating to branch default
428 428 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
429 429 $ cd repo-automatic-tag-merge-clone
430 430 $ echo c1 > f1
431 431 $ hg ci -A -m1
432 432 adding f1
433 433 $ hg tag t1 t2 t3
434 434 $ hg tag --remove t2
435 435 $ hg tag t5
436 436 $ echo c2 > f2
437 437 $ hg ci -A -m2
438 438 adding f2
439 439 $ hg tag -f t3
440 440
441 441 $ cd ../repo-automatic-tag-merge
442 442 $ echo c3 > f3
443 443 $ hg ci -A -m3
444 444 adding f3
445 445 $ hg tag -f t4 t5 t6
446 446
447 447 $ hg up -q '.^'
448 448 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
449 449 1 changes since t4:t5:t6
450 450 $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
451 451 0 changes since t4:t5:t6
452 452 $ echo c5 > f3
453 453 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
454 454 1 changes since t4:t5:t6
455 455 $ hg up -qC
456 456
457 457 $ hg tag --remove t5
458 458 $ echo c4 > f4
459 459 $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
460 460 2 changes since t4:t6
461 $ hg log -r '.' -T "{latesttag % '{latesttag}\n'}"
462 t4
463 t6
461 464 $ hg ci -A -m4
462 465 adding f4
463 466 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
464 467 4 changes since t4:t6
465 468 $ hg tag t2
466 469 $ hg tag -f t6
467 470
468 471 $ cd ../repo-automatic-tag-merge-clone
469 472 $ hg pull
470 473 pulling from $TESTTMP/repo-automatic-tag-merge (glob)
471 474 searching for changes
472 475 adding changesets
473 476 adding manifests
474 477 adding file changes
475 478 added 6 changesets with 6 changes to 3 files (+1 heads)
476 479 (run 'hg heads' to see heads, 'hg merge' to merge)
477 480 $ hg merge --tool internal:tagmerge
478 481 merging .hgtags
479 482 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
480 483 (branch merge, don't forget to commit)
481 484 $ hg status
482 485 M .hgtags
483 486 M f3
484 487 M f4
485 488 $ hg resolve -l
486 489 R .hgtags
487 490 $ cat .hgtags
488 491 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
489 492 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
490 493 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
491 494 09af2ce14077a94effef208b49a718f4836d4338 t6
492 495 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
493 496 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
494 497 929bca7b18d067cbf3844c3896319a940059d748 t2
495 498 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
496 499 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
497 500 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
498 501 0000000000000000000000000000000000000000 t2
499 502 875517b4806a848f942811a315a5bce30804ae85 t5
500 503 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
501 504 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
502 505 0000000000000000000000000000000000000000 t5
503 506 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
504 507 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
505 508
506 509 check that the merge tried to minimize the diff with the first merge parent
507 510
508 511 $ hg diff --git -r 'p1()' .hgtags
509 512 diff --git a/.hgtags b/.hgtags
510 513 --- a/.hgtags
511 514 +++ b/.hgtags
512 515 @@ -1,9 +1,17 @@
513 516 +9aa4e1292a27a248f8d07339bed9931d54907be7 t4
514 517 +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
515 518 +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
516 519 +09af2ce14077a94effef208b49a718f4836d4338 t6
517 520 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
518 521 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
519 522 +929bca7b18d067cbf3844c3896319a940059d748 t2
520 523 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
521 524 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
522 525 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
523 526 0000000000000000000000000000000000000000 t2
524 527 875517b4806a848f942811a315a5bce30804ae85 t5
525 528 +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
526 529 +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
527 530 +0000000000000000000000000000000000000000 t5
528 531 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
529 532 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
530 533
531 534 detect merge tag conflicts
532 535
533 536 $ hg update -C -r tip
534 537 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
535 538 $ hg tag t7
536 539 $ hg update -C -r 'first(sort(head()))'
537 540 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
538 541 $ printf "%s %s\n" `hg log -r . --template "{node} t7"` >> .hgtags
539 542 $ hg commit -m "manually add conflicting t7 tag"
540 543 $ hg merge --tool internal:tagmerge
541 544 merging .hgtags
542 545 automatic .hgtags merge failed
543 546 the following 1 tags are in conflict: t7
544 547 automatic tag merging of .hgtags failed! (use 'hg resolve --tool :merge' or another merge tool of your choice)
545 548 2 files updated, 0 files merged, 0 files removed, 1 files unresolved
546 549 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
547 550 [1]
548 551 $ hg resolve -l
549 552 U .hgtags
550 553 $ cat .hgtags
551 554 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
552 555 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
553 556 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
554 557 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
555 558 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
556 559 0000000000000000000000000000000000000000 t2
557 560 875517b4806a848f942811a315a5bce30804ae85 t5
558 561 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
559 562 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
560 563 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
561 564
562 565 $ cd ..
563 566
564 567 handle the loss of tags
565 568
566 569 $ hg clone repo-automatic-tag-merge-clone repo-merge-lost-tags
567 570 updating to branch default
568 571 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
569 572 $ cd repo-merge-lost-tags
570 573 $ echo c5 > f5
571 574 $ hg ci -A -m5
572 575 adding f5
573 576 $ hg tag -f t7
574 577 $ hg update -r 'p1(t7)'
575 578 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
576 579 $ printf '' > .hgtags
577 580 $ hg commit -m 'delete all tags'
578 581 created new head
579 582 $ hg log -r 'max(t7::)'
580 583 changeset: 17:ffe462b50880
581 584 user: test
582 585 date: Thu Jan 01 00:00:00 1970 +0000
583 586 summary: Added tag t7 for changeset fd3a9e394ce3
584 587
585 588 $ hg update -r 'max(t7::)'
586 589 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
587 590 $ hg merge -r tip --tool internal:tagmerge
588 591 merging .hgtags
589 592 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
590 593 (branch merge, don't forget to commit)
591 594 $ hg resolve -l
592 595 R .hgtags
593 596 $ cat .hgtags
594 597 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
595 598 0000000000000000000000000000000000000000 tbase
596 599 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
597 600 0000000000000000000000000000000000000000 t1
598 601 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
599 602 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
600 603 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
601 604 0000000000000000000000000000000000000000 t2
602 605 875517b4806a848f942811a315a5bce30804ae85 t5
603 606 0000000000000000000000000000000000000000 t5
604 607 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
605 608 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
606 609 0000000000000000000000000000000000000000 t3
607 610 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
608 611 0000000000000000000000000000000000000000 t7
609 612 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
610 613 fd3a9e394ce3afb354a496323bf68ac1755a30de t7
611 614
612 615 also check that we minimize the diff with the 1st merge parent
613 616
614 617 $ hg diff --git -r 'p1()' .hgtags
615 618 diff --git a/.hgtags b/.hgtags
616 619 --- a/.hgtags
617 620 +++ b/.hgtags
618 621 @@ -1,12 +1,17 @@
619 622 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
620 623 +0000000000000000000000000000000000000000 tbase
621 624 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
622 625 +0000000000000000000000000000000000000000 t1
623 626 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
624 627 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
625 628 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
626 629 0000000000000000000000000000000000000000 t2
627 630 875517b4806a848f942811a315a5bce30804ae85 t5
628 631 +0000000000000000000000000000000000000000 t5
629 632 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
630 633 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
631 634 +0000000000000000000000000000000000000000 t3
632 635 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
633 636 +0000000000000000000000000000000000000000 t7
634 637 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
635 638 fd3a9e394ce3afb354a496323bf68ac1755a30de t7
636 639
General Comments 0
You need to be logged in to leave comments. Login now