##// END OF EJS Templates
templatekw: introduce the changessincelatesttag keyword...
Matt Harbison -
r25724:4474a750 default
parent child Browse files
Show More
@@ -1,471 +1,485 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, **args):
44 44 if not element:
45 45 element = name
46 46 f = _showlist(name, values, plural, **args)
47 47 return _hybrid(f, values, lambda x: {element: x})
48 48
49 49 def _showlist(name, values, plural=None, **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
55 55 expansion works like this, given name 'foo'.
56 56
57 57 if values is empty, expand 'no_foos'.
58 58
59 59 if 'foo' not in template map, return values as a string,
60 60 joined by space.
61 61
62 62 expand 'start_foos'.
63 63
64 64 for each value, expand 'foo'. if 'last_foo' in template
65 65 map, expand it instead of 'foo' for last key.
66 66
67 67 expand 'end_foos'.
68 68 '''
69 69 templ = args['templ']
70 70 if plural:
71 71 names = plural
72 72 else: names = name + 's'
73 73 if not values:
74 74 noname = 'no_' + names
75 75 if noname in templ:
76 76 yield templ(noname, **args)
77 77 return
78 78 if name not in templ:
79 79 if isinstance(values[0], str):
80 80 yield ' '.join(values)
81 81 else:
82 82 for v in values:
83 83 yield dict(v, **args)
84 84 return
85 85 startname = 'start_' + names
86 86 if startname in templ:
87 87 yield templ(startname, **args)
88 88 vargs = args.copy()
89 89 def one(v, tag=name):
90 90 try:
91 91 vargs.update(v)
92 92 except (AttributeError, ValueError):
93 93 try:
94 94 for a, b in v:
95 95 vargs[a] = b
96 96 except ValueError:
97 97 vargs[name] = v
98 98 return templ(tag, **vargs)
99 99 lastname = 'last_' + name
100 100 if lastname in templ:
101 101 last = values.pop()
102 102 else:
103 103 last = None
104 104 for v in values:
105 105 yield one(v)
106 106 if last is not None:
107 107 yield one(last, tag=lastname)
108 108 endname = 'end_' + names
109 109 if endname in templ:
110 110 yield templ(endname, **args)
111 111
112 112 def getfiles(repo, ctx, revcache):
113 113 if 'files' not in revcache:
114 114 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
115 115 return revcache['files']
116 116
117 117 def getlatesttags(repo, ctx, cache):
118 118 '''return date, distance and name for the latest tag of rev'''
119 119
120 120 if 'latesttags' not in cache:
121 121 # Cache mapping from rev to a tuple with tag date, tag
122 122 # distance and tag name
123 123 cache['latesttags'] = {-1: (0, 0, ['null'])}
124 124 latesttags = cache['latesttags']
125 125
126 126 rev = ctx.rev()
127 127 todo = [rev]
128 128 while todo:
129 129 rev = todo.pop()
130 130 if rev in latesttags:
131 131 continue
132 132 ctx = repo[rev]
133 133 tags = [t for t in ctx.tags()
134 134 if (repo.tagtype(t) and repo.tagtype(t) != 'local')]
135 135 if tags:
136 136 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
137 137 continue
138 138 try:
139 139 # The tuples are laid out so the right one can be found by
140 140 # comparison.
141 141 pdate, pdist, ptag = max(
142 142 latesttags[p.rev()] for p in ctx.parents())
143 143 except KeyError:
144 144 # Cache miss - recurse
145 145 todo.append(rev)
146 146 todo.extend(p.rev() for p in ctx.parents())
147 147 continue
148 148 latesttags[rev] = pdate, pdist + 1, ptag
149 149 return latesttags[rev]
150 150
151 151 def getrenamedfn(repo, endrev=None):
152 152 rcache = {}
153 153 if endrev is None:
154 154 endrev = len(repo)
155 155
156 156 def getrenamed(fn, rev):
157 157 '''looks up all renames for a file (up to endrev) the first
158 158 time the file is given. It indexes on the changerev and only
159 159 parses the manifest if linkrev != changerev.
160 160 Returns rename info for fn at changerev rev.'''
161 161 if fn not in rcache:
162 162 rcache[fn] = {}
163 163 fl = repo.file(fn)
164 164 for i in fl:
165 165 lr = fl.linkrev(i)
166 166 renamed = fl.renamed(fl.node(i))
167 167 rcache[fn][lr] = renamed
168 168 if lr >= endrev:
169 169 break
170 170 if rev in rcache[fn]:
171 171 return rcache[fn][rev]
172 172
173 173 # If linkrev != rev (i.e. rev not found in rcache) fallback to
174 174 # filectx logic.
175 175 try:
176 176 return repo[rev][fn].renamed()
177 177 except error.LookupError:
178 178 return None
179 179
180 180 return getrenamed
181 181
182 182
183 183 def showauthor(repo, ctx, templ, **args):
184 184 """:author: String. The unmodified author of the changeset."""
185 185 return ctx.user()
186 186
187 187 def showbisect(repo, ctx, templ, **args):
188 188 """:bisect: String. The changeset bisection status."""
189 189 return hbisect.label(repo, ctx.node())
190 190
191 191 def showbranch(**args):
192 192 """:branch: String. The name of the branch on which the changeset was
193 193 committed.
194 194 """
195 195 return args['ctx'].branch()
196 196
197 197 def showbranches(**args):
198 198 """:branches: List of strings. The name of the branch on which the
199 199 changeset was committed. Will be empty if the branch name was
200 200 default.
201 201 """
202 202 branch = args['ctx'].branch()
203 203 if branch != 'default':
204 204 return showlist('branch', [branch], plural='branches', **args)
205 205 return showlist('branch', [], plural='branches', **args)
206 206
207 207 def showbookmarks(**args):
208 208 """:bookmarks: List of strings. Any bookmarks associated with the
209 209 changeset. Also sets 'active', the name of the active bookmark.
210 210 """
211 211 repo = args['ctx']._repo
212 212 bookmarks = args['ctx'].bookmarks()
213 213 active = repo._activebookmark
214 214 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
215 215 f = _showlist('bookmark', bookmarks, **args)
216 216 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
217 217
218 218 def showchildren(**args):
219 219 """:children: List of strings. The children of the changeset."""
220 220 ctx = args['ctx']
221 221 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
222 222 return showlist('children', childrevs, element='child', **args)
223 223
224 224 # Deprecated, but kept alive for help generation a purpose.
225 225 def showcurrentbookmark(**args):
226 226 """:currentbookmark: String. The active bookmark, if it is
227 227 associated with the changeset (DEPRECATED)"""
228 228 return showactivebookmark(**args)
229 229
230 230 def showactivebookmark(**args):
231 231 """:activebookmark: String. The active bookmark, if it is
232 232 associated with the changeset"""
233 233 active = args['repo']._activebookmark
234 234 if active and active in args['ctx'].bookmarks():
235 235 return active
236 236 return ''
237 237
238 238 def showdate(repo, ctx, templ, **args):
239 239 """:date: Date information. The date when the changeset was committed."""
240 240 return ctx.date()
241 241
242 242 def showdescription(repo, ctx, templ, **args):
243 243 """:desc: String. The text of the changeset description."""
244 244 return ctx.description().strip()
245 245
246 246 def showdiffstat(repo, ctx, templ, **args):
247 247 """:diffstat: String. Statistics of changes with the following format:
248 248 "modified files: +added/-removed lines"
249 249 """
250 250 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
251 251 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
252 252 return '%s: +%s/-%s' % (len(stats), adds, removes)
253 253
254 254 def showextras(**args):
255 255 """:extras: List of dicts with key, value entries of the 'extras'
256 256 field of this changeset."""
257 257 extras = args['ctx'].extra()
258 258 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
259 259 makemap = lambda k: {'key': k, 'value': extras[k]}
260 260 c = [makemap(k) for k in extras]
261 261 f = _showlist('extra', c, plural='extras', **args)
262 262 return _hybrid(f, extras, makemap,
263 263 lambda x: '%s=%s' % (x['key'], x['value']))
264 264
265 265 def showfileadds(**args):
266 266 """:file_adds: List of strings. Files added by this changeset."""
267 267 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
268 268 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
269 269 element='file', **args)
270 270
271 271 def showfilecopies(**args):
272 272 """:file_copies: List of strings. Files copied in this changeset with
273 273 their sources.
274 274 """
275 275 cache, ctx = args['cache'], args['ctx']
276 276 copies = args['revcache'].get('copies')
277 277 if copies is None:
278 278 if 'getrenamed' not in cache:
279 279 cache['getrenamed'] = getrenamedfn(args['repo'])
280 280 copies = []
281 281 getrenamed = cache['getrenamed']
282 282 for fn in ctx.files():
283 283 rename = getrenamed(fn, ctx.rev())
284 284 if rename:
285 285 copies.append((fn, rename[0]))
286 286
287 287 copies = util.sortdict(copies)
288 288 makemap = lambda k: {'name': k, 'source': copies[k]}
289 289 c = [makemap(k) for k in copies]
290 290 f = _showlist('file_copy', c, plural='file_copies', **args)
291 291 return _hybrid(f, copies, makemap,
292 292 lambda x: '%s (%s)' % (x['name'], x['source']))
293 293
294 294 # showfilecopiesswitch() displays file copies only if copy records are
295 295 # provided before calling the templater, usually with a --copies
296 296 # command line switch.
297 297 def showfilecopiesswitch(**args):
298 298 """:file_copies_switch: List of strings. Like "file_copies" but displayed
299 299 only if the --copied switch is set.
300 300 """
301 301 copies = args['revcache'].get('copies') or []
302 302 copies = util.sortdict(copies)
303 303 makemap = lambda k: {'name': k, 'source': copies[k]}
304 304 c = [makemap(k) for k in copies]
305 305 f = _showlist('file_copy', c, plural='file_copies', **args)
306 306 return _hybrid(f, copies, makemap,
307 307 lambda x: '%s (%s)' % (x['name'], x['source']))
308 308
309 309 def showfiledels(**args):
310 310 """:file_dels: List of strings. Files removed by this changeset."""
311 311 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
312 312 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
313 313 element='file', **args)
314 314
315 315 def showfilemods(**args):
316 316 """:file_mods: List of strings. Files modified by this changeset."""
317 317 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
318 318 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
319 319 element='file', **args)
320 320
321 321 def showfiles(**args):
322 322 """:files: List of strings. All files modified, added, or removed by this
323 323 changeset.
324 324 """
325 325 return showlist('file', args['ctx'].files(), **args)
326 326
327 327 def showlatesttag(repo, ctx, templ, cache, **args):
328 328 """:latesttag: String. Most recent global tag in the ancestors of this
329 329 changeset.
330 330 """
331 331 return ':'.join(getlatesttags(repo, ctx, cache)[2])
332 332
333 333 def showlatesttagdistance(repo, ctx, templ, cache, **args):
334 334 """:latesttagdistance: Integer. Longest path to the latest tag."""
335 335 return getlatesttags(repo, ctx, cache)[1]
336 336
337 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
338 """:changessincelatesttag: Integer. All ancestors not in the latest tag."""
339 latesttag = getlatesttags(repo, ctx, cache)[2][0]
340 offset = 0
341 revs = [ctx.rev()]
342
343 # The only() revset doesn't currently support wdir()
344 if ctx.rev() is None:
345 offset = 1
346 revs = [p.rev() for p in ctx.parents()]
347
348 return len(repo.revs('only(%ld, %s)', revs, latesttag)) + offset
349
337 350 def showmanifest(**args):
338 351 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
339 352 mnode = ctx.manifestnode()
340 353 args = args.copy()
341 354 args.update({'rev': repo.manifest.rev(mnode), 'node': hex(mnode)})
342 355 return templ('manifest', **args)
343 356
344 357 def shownode(repo, ctx, templ, **args):
345 358 """:node: String. The changeset identification hash, as a 40 hexadecimal
346 359 digit string.
347 360 """
348 361 return ctx.hex()
349 362
350 363 def showp1rev(repo, ctx, templ, **args):
351 364 """:p1rev: Integer. The repository-local revision number of the changeset's
352 365 first parent, or -1 if the changeset has no parents."""
353 366 return ctx.p1().rev()
354 367
355 368 def showp2rev(repo, ctx, templ, **args):
356 369 """:p2rev: Integer. The repository-local revision number of the changeset's
357 370 second parent, or -1 if the changeset has no second parent."""
358 371 return ctx.p2().rev()
359 372
360 373 def showp1node(repo, ctx, templ, **args):
361 374 """:p1node: String. The identification hash of the changeset's first parent,
362 375 as a 40 digit hexadecimal string. If the changeset has no parents, all
363 376 digits are 0."""
364 377 return ctx.p1().hex()
365 378
366 379 def showp2node(repo, ctx, templ, **args):
367 380 """:p2node: String. The identification hash of the changeset's second
368 381 parent, as a 40 digit hexadecimal string. If the changeset has no second
369 382 parent, all digits are 0."""
370 383 return ctx.p2().hex()
371 384
372 385 def showphase(repo, ctx, templ, **args):
373 386 """:phase: String. The changeset phase name."""
374 387 return ctx.phasestr()
375 388
376 389 def showphaseidx(repo, ctx, templ, **args):
377 390 """:phaseidx: Integer. The changeset phase index."""
378 391 return ctx.phase()
379 392
380 393 def showrev(repo, ctx, templ, **args):
381 394 """:rev: Integer. The repository-local changeset revision number."""
382 395 return ctx.rev()
383 396
384 397 def showsubrepos(**args):
385 398 """:subrepos: List of strings. Updated subrepositories in the changeset."""
386 399 ctx = args['ctx']
387 400 substate = ctx.substate
388 401 if not substate:
389 402 return showlist('subrepo', [], **args)
390 403 psubstate = ctx.parents()[0].substate or {}
391 404 subrepos = []
392 405 for sub in substate:
393 406 if sub not in psubstate or substate[sub] != psubstate[sub]:
394 407 subrepos.append(sub) # modified or newly added in ctx
395 408 for sub in psubstate:
396 409 if sub not in substate:
397 410 subrepos.append(sub) # removed in ctx
398 411 return showlist('subrepo', sorted(subrepos), **args)
399 412
400 413 def shownames(namespace, **args):
401 414 """helper method to generate a template keyword for a namespace"""
402 415 ctx = args['ctx']
403 416 repo = ctx.repo()
404 417 ns = repo.names[namespace]
405 418 names = ns.names(repo, ctx.node())
406 419 return showlist(ns.templatename, names, plural=namespace, **args)
407 420
408 421 # don't remove "showtags" definition, even though namespaces will put
409 422 # a helper function for "tags" keyword into "keywords" map automatically,
410 423 # because online help text is built without namespaces initialization
411 424 def showtags(**args):
412 425 """:tags: List of strings. Any tags associated with the changeset."""
413 426 return shownames('tags', **args)
414 427
415 428 # keywords are callables like:
416 429 # fn(repo, ctx, templ, cache, revcache, **args)
417 430 # with:
418 431 # repo - current repository instance
419 432 # ctx - the changectx being displayed
420 433 # templ - the templater instance
421 434 # cache - a cache dictionary for the whole templater run
422 435 # revcache - a cache dictionary for the current revision
423 436 keywords = {
424 437 'activebookmark': showactivebookmark,
425 438 'author': showauthor,
426 439 'bisect': showbisect,
427 440 'branch': showbranch,
428 441 'branches': showbranches,
429 442 'bookmarks': showbookmarks,
443 'changessincelatesttag': showchangessincelatesttag,
430 444 'children': showchildren,
431 445 # currentbookmark is deprecated
432 446 'currentbookmark': showcurrentbookmark,
433 447 'date': showdate,
434 448 'desc': showdescription,
435 449 'diffstat': showdiffstat,
436 450 'extras': showextras,
437 451 'file_adds': showfileadds,
438 452 'file_copies': showfilecopies,
439 453 'file_copies_switch': showfilecopiesswitch,
440 454 'file_dels': showfiledels,
441 455 'file_mods': showfilemods,
442 456 'files': showfiles,
443 457 'latesttag': showlatesttag,
444 458 'latesttagdistance': showlatesttagdistance,
445 459 'manifest': showmanifest,
446 460 'node': shownode,
447 461 'p1rev': showp1rev,
448 462 'p1node': showp1node,
449 463 'p2rev': showp2rev,
450 464 'p2node': showp2node,
451 465 'phase': showphase,
452 466 'phaseidx': showphaseidx,
453 467 'rev': showrev,
454 468 'subrepos': showsubrepos,
455 469 'tags': showtags,
456 470 }
457 471
458 472 def _showparents(**args):
459 473 """:parents: List of strings. The parents of the changeset in "rev:node"
460 474 format. If the changeset has only one "natural" parent (the predecessor
461 475 revision) nothing is shown."""
462 476 pass
463 477
464 478 dockeywords = {
465 479 'parents': _showparents,
466 480 }
467 481 dockeywords.update(keywords)
468 482 del dockeywords['branches']
469 483
470 484 # tell hggettext to extract docstrings from these functions:
471 485 i18nfunctions = dockeywords.values()
@@ -1,623 +1,636 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
447 $ hg up -q '.^'
448 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
449 1 changes since t4:t5:t6
450 $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
451 0 changes since t4:t5:t6
452 $ echo c5 > f3
453 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
454 1 changes since t4:t5:t6
455 $ hg up -qC
456
446 457 $ hg tag --remove t5
447 458 $ echo c4 > f4
448 $ hg log -r '.' -T "{latesttag}\n"
449 t4:t6
459 $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
460 2 changes since t4:t6
450 461 $ hg ci -A -m4
451 462 adding f4
463 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
464 4 changes since t4:t6
452 465 $ hg tag t2
453 466 $ hg tag -f t6
454 467
455 468 $ cd ../repo-automatic-tag-merge-clone
456 469 $ hg pull
457 470 pulling from $TESTTMP/repo-automatic-tag-merge (glob)
458 471 searching for changes
459 472 adding changesets
460 473 adding manifests
461 474 adding file changes
462 475 added 6 changesets with 6 changes to 3 files (+1 heads)
463 476 (run 'hg heads' to see heads, 'hg merge' to merge)
464 477 $ hg merge --tool internal:tagmerge
465 478 merging .hgtags
466 479 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
467 480 (branch merge, don't forget to commit)
468 481 $ hg status
469 482 M .hgtags
470 483 M f3
471 484 M f4
472 485 $ hg resolve -l
473 486 R .hgtags
474 487 $ cat .hgtags
475 488 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
476 489 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
477 490 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
478 491 09af2ce14077a94effef208b49a718f4836d4338 t6
479 492 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
480 493 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
481 494 929bca7b18d067cbf3844c3896319a940059d748 t2
482 495 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
483 496 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
484 497 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
485 498 0000000000000000000000000000000000000000 t2
486 499 875517b4806a848f942811a315a5bce30804ae85 t5
487 500 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
488 501 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
489 502 0000000000000000000000000000000000000000 t5
490 503 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
491 504 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
492 505
493 506 check that the merge tried to minimize the diff with the first merge parent
494 507
495 508 $ hg diff --git -r 'p1()' .hgtags
496 509 diff --git a/.hgtags b/.hgtags
497 510 --- a/.hgtags
498 511 +++ b/.hgtags
499 512 @@ -1,9 +1,17 @@
500 513 +9aa4e1292a27a248f8d07339bed9931d54907be7 t4
501 514 +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
502 515 +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
503 516 +09af2ce14077a94effef208b49a718f4836d4338 t6
504 517 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
505 518 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
506 519 +929bca7b18d067cbf3844c3896319a940059d748 t2
507 520 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
508 521 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
509 522 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
510 523 0000000000000000000000000000000000000000 t2
511 524 875517b4806a848f942811a315a5bce30804ae85 t5
512 525 +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
513 526 +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
514 527 +0000000000000000000000000000000000000000 t5
515 528 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
516 529 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
517 530
518 531 detect merge tag conflicts
519 532
520 533 $ hg update -C -r tip
521 534 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
522 535 $ hg tag t7
523 536 $ hg update -C -r 'first(sort(head()))'
524 537 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
525 538 $ printf "%s %s\n" `hg log -r . --template "{node} t7"` >> .hgtags
526 539 $ hg commit -m "manually add conflicting t7 tag"
527 540 $ hg merge --tool internal:tagmerge
528 541 merging .hgtags
529 542 automatic .hgtags merge failed
530 543 the following 1 tags are in conflict: t7
531 544 automatic tag merging of .hgtags failed! (use 'hg resolve --tool :merge' or another merge tool of your choice)
532 545 2 files updated, 0 files merged, 0 files removed, 1 files unresolved
533 546 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
534 547 [1]
535 548 $ hg resolve -l
536 549 U .hgtags
537 550 $ cat .hgtags
538 551 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
539 552 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
540 553 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
541 554 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
542 555 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
543 556 0000000000000000000000000000000000000000 t2
544 557 875517b4806a848f942811a315a5bce30804ae85 t5
545 558 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
546 559 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
547 560 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
548 561
549 562 $ cd ..
550 563
551 564 handle the loss of tags
552 565
553 566 $ hg clone repo-automatic-tag-merge-clone repo-merge-lost-tags
554 567 updating to branch default
555 568 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
556 569 $ cd repo-merge-lost-tags
557 570 $ echo c5 > f5
558 571 $ hg ci -A -m5
559 572 adding f5
560 573 $ hg tag -f t7
561 574 $ hg update -r 'p1(t7)'
562 575 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
563 576 $ printf '' > .hgtags
564 577 $ hg commit -m 'delete all tags'
565 578 created new head
566 579 $ hg log -r 'max(t7::)'
567 580 changeset: 17:ffe462b50880
568 581 user: test
569 582 date: Thu Jan 01 00:00:00 1970 +0000
570 583 summary: Added tag t7 for changeset fd3a9e394ce3
571 584
572 585 $ hg update -r 'max(t7::)'
573 586 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
574 587 $ hg merge -r tip --tool internal:tagmerge
575 588 merging .hgtags
576 589 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
577 590 (branch merge, don't forget to commit)
578 591 $ hg resolve -l
579 592 R .hgtags
580 593 $ cat .hgtags
581 594 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
582 595 0000000000000000000000000000000000000000 tbase
583 596 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
584 597 0000000000000000000000000000000000000000 t1
585 598 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
586 599 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
587 600 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
588 601 0000000000000000000000000000000000000000 t2
589 602 875517b4806a848f942811a315a5bce30804ae85 t5
590 603 0000000000000000000000000000000000000000 t5
591 604 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
592 605 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
593 606 0000000000000000000000000000000000000000 t3
594 607 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
595 608 0000000000000000000000000000000000000000 t7
596 609 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
597 610 fd3a9e394ce3afb354a496323bf68ac1755a30de t7
598 611
599 612 also check that we minimize the diff with the 1st merge parent
600 613
601 614 $ hg diff --git -r 'p1()' .hgtags
602 615 diff --git a/.hgtags b/.hgtags
603 616 --- a/.hgtags
604 617 +++ b/.hgtags
605 618 @@ -1,12 +1,17 @@
606 619 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
607 620 +0000000000000000000000000000000000000000 tbase
608 621 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
609 622 +0000000000000000000000000000000000000000 t1
610 623 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
611 624 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
612 625 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
613 626 0000000000000000000000000000000000000000 t2
614 627 875517b4806a848f942811a315a5bce30804ae85 t5
615 628 +0000000000000000000000000000000000000000 t5
616 629 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
617 630 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
618 631 +0000000000000000000000000000000000000000 t3
619 632 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
620 633 +0000000000000000000000000000000000000000 t7
621 634 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
622 635 fd3a9e394ce3afb354a496323bf68ac1755a30de t7
623 636
General Comments 0
You need to be logged in to leave comments. Login now