##// END OF EJS Templates
templates: adjust white space amount in the output of {whyunstable}...
av6 -
r37725:24fee31f default
parent child Browse files
Show More
@@ -1,827 +1,828 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 __future__ import absolute_import
9 9
10 10 from .i18n import _
11 11 from .node import (
12 12 hex,
13 13 nullid,
14 14 )
15 15
16 16 from . import (
17 17 encoding,
18 18 error,
19 19 hbisect,
20 20 i18n,
21 21 obsutil,
22 22 patch,
23 23 pycompat,
24 24 registrar,
25 25 scmutil,
26 26 templateutil,
27 27 util,
28 28 )
29 29 from .utils import (
30 30 stringutil,
31 31 )
32 32
33 33 _hybrid = templateutil.hybrid
34 34 _mappable = templateutil.mappable
35 35 hybriddict = templateutil.hybriddict
36 36 hybridlist = templateutil.hybridlist
37 37 compatdict = templateutil.compatdict
38 38 compatlist = templateutil.compatlist
39 39 _showcompatlist = templateutil._showcompatlist
40 40
41 41 def _showlist(name, values, templ, mapping, plural=None, separator=' '):
42 42 ui = mapping.get('ui')
43 43 if ui:
44 44 ui.deprecwarn("templatekw._showlist() is deprecated, use "
45 45 "templateutil._showcompatlist()", '4.6')
46 46 context = templ # this is actually a template context, not a templater
47 47 return _showcompatlist(context, mapping, name, values, plural, separator)
48 48
49 49 def showdict(name, data, mapping, plural=None, key='key', value='value',
50 50 fmt=None, separator=' '):
51 51 ui = mapping.get('ui')
52 52 if ui:
53 53 ui.deprecwarn("templatekw.showdict() is deprecated, use "
54 54 "templateutil.compatdict()", '4.6')
55 55 c = [{key: k, value: v} for k, v in data.iteritems()]
56 56 f = _showlist(name, c, mapping['templ'], mapping, plural, separator)
57 57 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
58 58
59 59 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
60 60 ui = mapping.get('ui')
61 61 if ui:
62 62 ui.deprecwarn("templatekw.showlist() is deprecated, use "
63 63 "templateutil.compatlist()", '4.6')
64 64 if not element:
65 65 element = name
66 66 f = _showlist(name, values, mapping['templ'], mapping, plural, separator)
67 67 return hybridlist(values, name=element, gen=f)
68 68
69 69 def getlatesttags(context, mapping, pattern=None):
70 70 '''return date, distance and name for the latest tag of rev'''
71 71 repo = context.resource(mapping, 'repo')
72 72 ctx = context.resource(mapping, 'ctx')
73 73 cache = context.resource(mapping, 'cache')
74 74
75 75 cachename = 'latesttags'
76 76 if pattern is not None:
77 77 cachename += '-' + pattern
78 78 match = stringutil.stringmatcher(pattern)[2]
79 79 else:
80 80 match = util.always
81 81
82 82 if cachename not in cache:
83 83 # Cache mapping from rev to a tuple with tag date, tag
84 84 # distance and tag name
85 85 cache[cachename] = {-1: (0, 0, ['null'])}
86 86 latesttags = cache[cachename]
87 87
88 88 rev = ctx.rev()
89 89 todo = [rev]
90 90 while todo:
91 91 rev = todo.pop()
92 92 if rev in latesttags:
93 93 continue
94 94 ctx = repo[rev]
95 95 tags = [t for t in ctx.tags()
96 96 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
97 97 and match(t))]
98 98 if tags:
99 99 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
100 100 continue
101 101 try:
102 102 ptags = [latesttags[p.rev()] for p in ctx.parents()]
103 103 if len(ptags) > 1:
104 104 if ptags[0][2] == ptags[1][2]:
105 105 # The tuples are laid out so the right one can be found by
106 106 # comparison in this case.
107 107 pdate, pdist, ptag = max(ptags)
108 108 else:
109 109 def key(x):
110 110 changessincetag = len(repo.revs('only(%d, %s)',
111 111 ctx.rev(), x[2][0]))
112 112 # Smallest number of changes since tag wins. Date is
113 113 # used as tiebreaker.
114 114 return [-changessincetag, x[0]]
115 115 pdate, pdist, ptag = max(ptags, key=key)
116 116 else:
117 117 pdate, pdist, ptag = ptags[0]
118 118 except KeyError:
119 119 # Cache miss - recurse
120 120 todo.append(rev)
121 121 todo.extend(p.rev() for p in ctx.parents())
122 122 continue
123 123 latesttags[rev] = pdate, pdist + 1, ptag
124 124 return latesttags[rev]
125 125
126 126 def getrenamedfn(repo, endrev=None):
127 127 rcache = {}
128 128 if endrev is None:
129 129 endrev = len(repo)
130 130
131 131 def getrenamed(fn, rev):
132 132 '''looks up all renames for a file (up to endrev) the first
133 133 time the file is given. It indexes on the changerev and only
134 134 parses the manifest if linkrev != changerev.
135 135 Returns rename info for fn at changerev rev.'''
136 136 if fn not in rcache:
137 137 rcache[fn] = {}
138 138 fl = repo.file(fn)
139 139 for i in fl:
140 140 lr = fl.linkrev(i)
141 141 renamed = fl.renamed(fl.node(i))
142 142 rcache[fn][lr] = renamed
143 143 if lr >= endrev:
144 144 break
145 145 if rev in rcache[fn]:
146 146 return rcache[fn][rev]
147 147
148 148 # If linkrev != rev (i.e. rev not found in rcache) fallback to
149 149 # filectx logic.
150 150 try:
151 151 return repo[rev][fn].renamed()
152 152 except error.LookupError:
153 153 return None
154 154
155 155 return getrenamed
156 156
157 157 def getlogcolumns():
158 158 """Return a dict of log column labels"""
159 159 _ = pycompat.identity # temporarily disable gettext
160 160 # i18n: column positioning for "hg log"
161 161 columns = _('bookmark: %s\n'
162 162 'branch: %s\n'
163 163 'changeset: %s\n'
164 164 'copies: %s\n'
165 165 'date: %s\n'
166 166 'extra: %s=%s\n'
167 167 'files+: %s\n'
168 168 'files-: %s\n'
169 169 'files: %s\n'
170 170 'instability: %s\n'
171 171 'manifest: %s\n'
172 172 'obsolete: %s\n'
173 173 'parent: %s\n'
174 174 'phase: %s\n'
175 175 'summary: %s\n'
176 176 'tag: %s\n'
177 177 'user: %s\n')
178 178 return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
179 179 i18n._(columns).splitlines(True)))
180 180
181 181 # default templates internally used for rendering of lists
182 182 defaulttempl = {
183 183 'parent': '{rev}:{node|formatnode} ',
184 184 'manifest': '{rev}:{node|formatnode}',
185 185 'file_copy': '{name} ({source})',
186 186 'envvar': '{key}={value}',
187 187 'extra': '{key}={value|stringescape}'
188 188 }
189 189 # filecopy is preserved for compatibility reasons
190 190 defaulttempl['filecopy'] = defaulttempl['file_copy']
191 191
192 192 # keywords are callables (see registrar.templatekeyword for details)
193 193 keywords = {}
194 194 templatekeyword = registrar.templatekeyword(keywords)
195 195
196 196 @templatekeyword('author', requires={'ctx'})
197 197 def showauthor(context, mapping):
198 198 """String. The unmodified author of the changeset."""
199 199 ctx = context.resource(mapping, 'ctx')
200 200 return ctx.user()
201 201
202 202 @templatekeyword('bisect', requires={'repo', 'ctx'})
203 203 def showbisect(context, mapping):
204 204 """String. The changeset bisection status."""
205 205 repo = context.resource(mapping, 'repo')
206 206 ctx = context.resource(mapping, 'ctx')
207 207 return hbisect.label(repo, ctx.node())
208 208
209 209 @templatekeyword('branch', requires={'ctx'})
210 210 def showbranch(context, mapping):
211 211 """String. The name of the branch on which the changeset was
212 212 committed.
213 213 """
214 214 ctx = context.resource(mapping, 'ctx')
215 215 return ctx.branch()
216 216
217 217 @templatekeyword('branches', requires={'ctx'})
218 218 def showbranches(context, mapping):
219 219 """List of strings. The name of the branch on which the
220 220 changeset was committed. Will be empty if the branch name was
221 221 default. (DEPRECATED)
222 222 """
223 223 ctx = context.resource(mapping, 'ctx')
224 224 branch = ctx.branch()
225 225 if branch != 'default':
226 226 return compatlist(context, mapping, 'branch', [branch],
227 227 plural='branches')
228 228 return compatlist(context, mapping, 'branch', [], plural='branches')
229 229
230 230 @templatekeyword('bookmarks', requires={'repo', 'ctx'})
231 231 def showbookmarks(context, mapping):
232 232 """List of strings. Any bookmarks associated with the
233 233 changeset. Also sets 'active', the name of the active bookmark.
234 234 """
235 235 repo = context.resource(mapping, 'repo')
236 236 ctx = context.resource(mapping, 'ctx')
237 237 bookmarks = ctx.bookmarks()
238 238 active = repo._activebookmark
239 239 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
240 240 f = _showcompatlist(context, mapping, 'bookmark', bookmarks)
241 241 return _hybrid(f, bookmarks, makemap, pycompat.identity)
242 242
243 243 @templatekeyword('children', requires={'ctx'})
244 244 def showchildren(context, mapping):
245 245 """List of strings. The children of the changeset."""
246 246 ctx = context.resource(mapping, 'ctx')
247 247 childrevs = ['%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
248 248 return compatlist(context, mapping, 'children', childrevs, element='child')
249 249
250 250 # Deprecated, but kept alive for help generation a purpose.
251 251 @templatekeyword('currentbookmark', requires={'repo', 'ctx'})
252 252 def showcurrentbookmark(context, mapping):
253 253 """String. The active bookmark, if it is associated with the changeset.
254 254 (DEPRECATED)"""
255 255 return showactivebookmark(context, mapping)
256 256
257 257 @templatekeyword('activebookmark', requires={'repo', 'ctx'})
258 258 def showactivebookmark(context, mapping):
259 259 """String. The active bookmark, if it is associated with the changeset."""
260 260 repo = context.resource(mapping, 'repo')
261 261 ctx = context.resource(mapping, 'ctx')
262 262 active = repo._activebookmark
263 263 if active and active in ctx.bookmarks():
264 264 return active
265 265 return ''
266 266
267 267 @templatekeyword('date', requires={'ctx'})
268 268 def showdate(context, mapping):
269 269 """Date information. The date when the changeset was committed."""
270 270 ctx = context.resource(mapping, 'ctx')
271 271 return ctx.date()
272 272
273 273 @templatekeyword('desc', requires={'ctx'})
274 274 def showdescription(context, mapping):
275 275 """String. The text of the changeset description."""
276 276 ctx = context.resource(mapping, 'ctx')
277 277 s = ctx.description()
278 278 if isinstance(s, encoding.localstr):
279 279 # try hard to preserve utf-8 bytes
280 280 return encoding.tolocal(encoding.fromlocal(s).strip())
281 281 else:
282 282 return s.strip()
283 283
284 284 @templatekeyword('diffstat', requires={'ctx'})
285 285 def showdiffstat(context, mapping):
286 286 """String. Statistics of changes with the following format:
287 287 "modified files: +added/-removed lines"
288 288 """
289 289 ctx = context.resource(mapping, 'ctx')
290 290 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
291 291 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
292 292 return '%d: +%d/-%d' % (len(stats), adds, removes)
293 293
294 294 @templatekeyword('envvars', requires={'ui'})
295 295 def showenvvars(context, mapping):
296 296 """A dictionary of environment variables. (EXPERIMENTAL)"""
297 297 ui = context.resource(mapping, 'ui')
298 298 env = ui.exportableenviron()
299 299 env = util.sortdict((k, env[k]) for k in sorted(env))
300 300 return compatdict(context, mapping, 'envvar', env, plural='envvars')
301 301
302 302 @templatekeyword('extras', requires={'ctx'})
303 303 def showextras(context, mapping):
304 304 """List of dicts with key, value entries of the 'extras'
305 305 field of this changeset."""
306 306 ctx = context.resource(mapping, 'ctx')
307 307 extras = ctx.extra()
308 308 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
309 309 makemap = lambda k: {'key': k, 'value': extras[k]}
310 310 c = [makemap(k) for k in extras]
311 311 f = _showcompatlist(context, mapping, 'extra', c, plural='extras')
312 312 return _hybrid(f, extras, makemap,
313 313 lambda k: '%s=%s' % (k, stringutil.escapestr(extras[k])))
314 314
315 315 def _showfilesbystat(context, mapping, name, index):
316 316 repo = context.resource(mapping, 'repo')
317 317 ctx = context.resource(mapping, 'ctx')
318 318 revcache = context.resource(mapping, 'revcache')
319 319 if 'files' not in revcache:
320 320 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
321 321 files = revcache['files'][index]
322 322 return compatlist(context, mapping, name, files, element='file')
323 323
324 324 @templatekeyword('file_adds', requires={'repo', 'ctx', 'revcache'})
325 325 def showfileadds(context, mapping):
326 326 """List of strings. Files added by this changeset."""
327 327 return _showfilesbystat(context, mapping, 'file_add', 1)
328 328
329 329 @templatekeyword('file_copies',
330 330 requires={'repo', 'ctx', 'cache', 'revcache'})
331 331 def showfilecopies(context, mapping):
332 332 """List of strings. Files copied in this changeset with
333 333 their sources.
334 334 """
335 335 repo = context.resource(mapping, 'repo')
336 336 ctx = context.resource(mapping, 'ctx')
337 337 cache = context.resource(mapping, 'cache')
338 338 copies = context.resource(mapping, 'revcache').get('copies')
339 339 if copies is None:
340 340 if 'getrenamed' not in cache:
341 341 cache['getrenamed'] = getrenamedfn(repo)
342 342 copies = []
343 343 getrenamed = cache['getrenamed']
344 344 for fn in ctx.files():
345 345 rename = getrenamed(fn, ctx.rev())
346 346 if rename:
347 347 copies.append((fn, rename[0]))
348 348
349 349 copies = util.sortdict(copies)
350 350 return compatdict(context, mapping, 'file_copy', copies,
351 351 key='name', value='source', fmt='%s (%s)',
352 352 plural='file_copies')
353 353
354 354 # showfilecopiesswitch() displays file copies only if copy records are
355 355 # provided before calling the templater, usually with a --copies
356 356 # command line switch.
357 357 @templatekeyword('file_copies_switch', requires={'revcache'})
358 358 def showfilecopiesswitch(context, mapping):
359 359 """List of strings. Like "file_copies" but displayed
360 360 only if the --copied switch is set.
361 361 """
362 362 copies = context.resource(mapping, 'revcache').get('copies') or []
363 363 copies = util.sortdict(copies)
364 364 return compatdict(context, mapping, 'file_copy', copies,
365 365 key='name', value='source', fmt='%s (%s)',
366 366 plural='file_copies')
367 367
368 368 @templatekeyword('file_dels', requires={'repo', 'ctx', 'revcache'})
369 369 def showfiledels(context, mapping):
370 370 """List of strings. Files removed by this changeset."""
371 371 return _showfilesbystat(context, mapping, 'file_del', 2)
372 372
373 373 @templatekeyword('file_mods', requires={'repo', 'ctx', 'revcache'})
374 374 def showfilemods(context, mapping):
375 375 """List of strings. Files modified by this changeset."""
376 376 return _showfilesbystat(context, mapping, 'file_mod', 0)
377 377
378 378 @templatekeyword('files', requires={'ctx'})
379 379 def showfiles(context, mapping):
380 380 """List of strings. All files modified, added, or removed by this
381 381 changeset.
382 382 """
383 383 ctx = context.resource(mapping, 'ctx')
384 384 return compatlist(context, mapping, 'file', ctx.files())
385 385
386 386 @templatekeyword('graphnode', requires={'repo', 'ctx'})
387 387 def showgraphnode(context, mapping):
388 388 """String. The character representing the changeset node in an ASCII
389 389 revision graph."""
390 390 repo = context.resource(mapping, 'repo')
391 391 ctx = context.resource(mapping, 'ctx')
392 392 return getgraphnode(repo, ctx)
393 393
394 394 def getgraphnode(repo, ctx):
395 395 wpnodes = repo.dirstate.parents()
396 396 if wpnodes[1] == nullid:
397 397 wpnodes = wpnodes[:1]
398 398 if ctx.node() in wpnodes:
399 399 return '@'
400 400 elif ctx.obsolete():
401 401 return 'x'
402 402 elif ctx.isunstable():
403 403 return '*'
404 404 elif ctx.closesbranch():
405 405 return '_'
406 406 else:
407 407 return 'o'
408 408
409 409 @templatekeyword('graphwidth', requires=())
410 410 def showgraphwidth(context, mapping):
411 411 """Integer. The width of the graph drawn by 'log --graph' or zero."""
412 412 # just hosts documentation; should be overridden by template mapping
413 413 return 0
414 414
415 415 @templatekeyword('index', requires=())
416 416 def showindex(context, mapping):
417 417 """Integer. The current iteration of the loop. (0 indexed)"""
418 418 # just hosts documentation; should be overridden by template mapping
419 419 raise error.Abort(_("can't use index in this context"))
420 420
421 421 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
422 422 def showlatesttag(context, mapping):
423 423 """List of strings. The global tags on the most recent globally
424 424 tagged ancestor of this changeset. If no such tags exist, the list
425 425 consists of the single string "null".
426 426 """
427 427 return showlatesttags(context, mapping, None)
428 428
429 429 def showlatesttags(context, mapping, pattern):
430 430 """helper method for the latesttag keyword and function"""
431 431 latesttags = getlatesttags(context, mapping, pattern)
432 432
433 433 # latesttag[0] is an implementation detail for sorting csets on different
434 434 # branches in a stable manner- it is the date the tagged cset was created,
435 435 # not the date the tag was created. Therefore it isn't made visible here.
436 436 makemap = lambda v: {
437 437 'changes': _showchangessincetag,
438 438 'distance': latesttags[1],
439 439 'latesttag': v, # BC with {latesttag % '{latesttag}'}
440 440 'tag': v
441 441 }
442 442
443 443 tags = latesttags[2]
444 444 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
445 445 return _hybrid(f, tags, makemap, pycompat.identity)
446 446
447 447 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
448 448 def showlatesttagdistance(context, mapping):
449 449 """Integer. Longest path to the latest tag."""
450 450 return getlatesttags(context, mapping)[1]
451 451
452 452 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
453 453 def showchangessincelatesttag(context, mapping):
454 454 """Integer. All ancestors not in the latest tag."""
455 455 tag = getlatesttags(context, mapping)[2][0]
456 456 mapping = context.overlaymap(mapping, {'tag': tag})
457 457 return _showchangessincetag(context, mapping)
458 458
459 459 def _showchangessincetag(context, mapping):
460 460 repo = context.resource(mapping, 'repo')
461 461 ctx = context.resource(mapping, 'ctx')
462 462 offset = 0
463 463 revs = [ctx.rev()]
464 464 tag = context.symbol(mapping, 'tag')
465 465
466 466 # The only() revset doesn't currently support wdir()
467 467 if ctx.rev() is None:
468 468 offset = 1
469 469 revs = [p.rev() for p in ctx.parents()]
470 470
471 471 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
472 472
473 473 # teach templater latesttags.changes is switched to (context, mapping) API
474 474 _showchangessincetag._requires = {'repo', 'ctx'}
475 475
476 476 @templatekeyword('manifest', requires={'repo', 'ctx'})
477 477 def showmanifest(context, mapping):
478 478 repo = context.resource(mapping, 'repo')
479 479 ctx = context.resource(mapping, 'ctx')
480 480 mnode = ctx.manifestnode()
481 481 if mnode is None:
482 482 # just avoid crash, we might want to use the 'ff...' hash in future
483 483 return
484 484 mrev = repo.manifestlog._revlog.rev(mnode)
485 485 mhex = hex(mnode)
486 486 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
487 487 f = context.process('manifest', mapping)
488 488 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
489 489 # rev and node are completely different from changeset's.
490 490 return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
491 491
492 492 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
493 493 def showobsfate(context, mapping):
494 494 # this function returns a list containing pre-formatted obsfate strings.
495 495 #
496 496 # This function will be replaced by templates fragments when we will have
497 497 # the verbosity templatekw available.
498 498 succsandmarkers = showsuccsandmarkers(context, mapping)
499 499
500 500 ui = context.resource(mapping, 'ui')
501 501 repo = context.resource(mapping, 'repo')
502 502 values = []
503 503
504 504 for x in succsandmarkers.tovalue(context, mapping):
505 505 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
506 506 scmutil.formatchangeid)
507 507 values.append(v)
508 508
509 509 return compatlist(context, mapping, "fate", values)
510 510
511 511 def shownames(context, mapping, namespace):
512 512 """helper method to generate a template keyword for a namespace"""
513 513 repo = context.resource(mapping, 'repo')
514 514 ctx = context.resource(mapping, 'ctx')
515 515 ns = repo.names[namespace]
516 516 names = ns.names(repo, ctx.node())
517 517 return compatlist(context, mapping, ns.templatename, names,
518 518 plural=namespace)
519 519
520 520 @templatekeyword('namespaces', requires={'repo', 'ctx'})
521 521 def shownamespaces(context, mapping):
522 522 """Dict of lists. Names attached to this changeset per
523 523 namespace."""
524 524 repo = context.resource(mapping, 'repo')
525 525 ctx = context.resource(mapping, 'ctx')
526 526
527 527 namespaces = util.sortdict()
528 528 def makensmapfn(ns):
529 529 # 'name' for iterating over namespaces, templatename for local reference
530 530 return lambda v: {'name': v, ns.templatename: v}
531 531
532 532 for k, ns in repo.names.iteritems():
533 533 names = ns.names(repo, ctx.node())
534 534 f = _showcompatlist(context, mapping, 'name', names)
535 535 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
536 536
537 537 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
538 538
539 539 def makemap(ns):
540 540 return {
541 541 'namespace': ns,
542 542 'names': namespaces[ns],
543 543 'builtin': repo.names[ns].builtin,
544 544 'colorname': repo.names[ns].colorname,
545 545 }
546 546
547 547 return _hybrid(f, namespaces, makemap, pycompat.identity)
548 548
549 549 @templatekeyword('node', requires={'ctx'})
550 550 def shownode(context, mapping):
551 551 """String. The changeset identification hash, as a 40 hexadecimal
552 552 digit string.
553 553 """
554 554 ctx = context.resource(mapping, 'ctx')
555 555 return ctx.hex()
556 556
557 557 @templatekeyword('obsolete', requires={'ctx'})
558 558 def showobsolete(context, mapping):
559 559 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
560 560 ctx = context.resource(mapping, 'ctx')
561 561 if ctx.obsolete():
562 562 return 'obsolete'
563 563 return ''
564 564
565 565 @templatekeyword('peerurls', requires={'repo'})
566 566 def showpeerurls(context, mapping):
567 567 """A dictionary of repository locations defined in the [paths] section
568 568 of your configuration file."""
569 569 repo = context.resource(mapping, 'repo')
570 570 # see commands.paths() for naming of dictionary keys
571 571 paths = repo.ui.paths
572 572 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
573 573 def makemap(k):
574 574 p = paths[k]
575 575 d = {'name': k, 'url': p.rawloc}
576 576 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
577 577 return d
578 578 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
579 579
580 580 @templatekeyword("predecessors", requires={'repo', 'ctx'})
581 581 def showpredecessors(context, mapping):
582 582 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
583 583 repo = context.resource(mapping, 'repo')
584 584 ctx = context.resource(mapping, 'ctx')
585 585 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
586 586 predecessors = map(hex, predecessors)
587 587
588 588 return _hybrid(None, predecessors,
589 589 lambda x: {'ctx': repo[x]},
590 590 lambda x: scmutil.formatchangeid(repo[x]))
591 591
592 592 @templatekeyword('reporoot', requires={'repo'})
593 593 def showreporoot(context, mapping):
594 594 """String. The root directory of the current repository."""
595 595 repo = context.resource(mapping, 'repo')
596 596 return repo.root
597 597
598 598 @templatekeyword("successorssets", requires={'repo', 'ctx'})
599 599 def showsuccessorssets(context, mapping):
600 600 """Returns a string of sets of successors for a changectx. Format used
601 601 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
602 602 while also diverged into ctx3. (EXPERIMENTAL)"""
603 603 repo = context.resource(mapping, 'repo')
604 604 ctx = context.resource(mapping, 'ctx')
605 605 if not ctx.obsolete():
606 606 return ''
607 607
608 608 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
609 609 ssets = [[hex(n) for n in ss] for ss in ssets]
610 610
611 611 data = []
612 612 for ss in ssets:
613 613 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
614 614 lambda x: scmutil.formatchangeid(repo[x]))
615 615 data.append(h)
616 616
617 617 # Format the successorssets
618 618 def render(d):
619 619 return templateutil.stringify(context, mapping, d)
620 620
621 621 def gen(data):
622 622 yield "; ".join(render(d) for d in data)
623 623
624 624 return _hybrid(gen(data), data, lambda x: {'successorset': x},
625 625 pycompat.identity)
626 626
627 627 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
628 628 def showsuccsandmarkers(context, mapping):
629 629 """Returns a list of dict for each final successor of ctx. The dict
630 630 contains successors node id in "successors" keys and the list of
631 631 obs-markers from ctx to the set of successors in "markers".
632 632 (EXPERIMENTAL)
633 633 """
634 634 repo = context.resource(mapping, 'repo')
635 635 ctx = context.resource(mapping, 'ctx')
636 636
637 637 values = obsutil.successorsandmarkers(repo, ctx)
638 638
639 639 if values is None:
640 640 values = []
641 641
642 642 # Format successors and markers to avoid exposing binary to templates
643 643 data = []
644 644 for i in values:
645 645 # Format successors
646 646 successors = i['successors']
647 647
648 648 successors = [hex(n) for n in successors]
649 649 successors = _hybrid(None, successors,
650 650 lambda x: {'ctx': repo[x]},
651 651 lambda x: scmutil.formatchangeid(repo[x]))
652 652
653 653 # Format markers
654 654 finalmarkers = []
655 655 for m in i['markers']:
656 656 hexprec = hex(m[0])
657 657 hexsucs = tuple(hex(n) for n in m[1])
658 658 hexparents = None
659 659 if m[5] is not None:
660 660 hexparents = tuple(hex(n) for n in m[5])
661 661 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
662 662 finalmarkers.append(newmarker)
663 663
664 664 data.append({'successors': successors, 'markers': finalmarkers})
665 665
666 666 return templateutil.mappinglist(data)
667 667
668 668 @templatekeyword('p1rev', requires={'ctx'})
669 669 def showp1rev(context, mapping):
670 670 """Integer. The repository-local revision number of the changeset's
671 671 first parent, or -1 if the changeset has no parents."""
672 672 ctx = context.resource(mapping, 'ctx')
673 673 return ctx.p1().rev()
674 674
675 675 @templatekeyword('p2rev', requires={'ctx'})
676 676 def showp2rev(context, mapping):
677 677 """Integer. The repository-local revision number of the changeset's
678 678 second parent, or -1 if the changeset has no second parent."""
679 679 ctx = context.resource(mapping, 'ctx')
680 680 return ctx.p2().rev()
681 681
682 682 @templatekeyword('p1node', requires={'ctx'})
683 683 def showp1node(context, mapping):
684 684 """String. The identification hash of the changeset's first parent,
685 685 as a 40 digit hexadecimal string. If the changeset has no parents, all
686 686 digits are 0."""
687 687 ctx = context.resource(mapping, 'ctx')
688 688 return ctx.p1().hex()
689 689
690 690 @templatekeyword('p2node', requires={'ctx'})
691 691 def showp2node(context, mapping):
692 692 """String. The identification hash of the changeset's second
693 693 parent, as a 40 digit hexadecimal string. If the changeset has no second
694 694 parent, all digits are 0."""
695 695 ctx = context.resource(mapping, 'ctx')
696 696 return ctx.p2().hex()
697 697
698 698 @templatekeyword('parents', requires={'repo', 'ctx'})
699 699 def showparents(context, mapping):
700 700 """List of strings. The parents of the changeset in "rev:node"
701 701 format. If the changeset has only one "natural" parent (the predecessor
702 702 revision) nothing is shown."""
703 703 repo = context.resource(mapping, 'repo')
704 704 ctx = context.resource(mapping, 'ctx')
705 705 pctxs = scmutil.meaningfulparents(repo, ctx)
706 706 prevs = [p.rev() for p in pctxs]
707 707 parents = [[('rev', p.rev()),
708 708 ('node', p.hex()),
709 709 ('phase', p.phasestr())]
710 710 for p in pctxs]
711 711 f = _showcompatlist(context, mapping, 'parent', parents)
712 712 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
713 713 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
714 714
715 715 @templatekeyword('phase', requires={'ctx'})
716 716 def showphase(context, mapping):
717 717 """String. The changeset phase name."""
718 718 ctx = context.resource(mapping, 'ctx')
719 719 return ctx.phasestr()
720 720
721 721 @templatekeyword('phaseidx', requires={'ctx'})
722 722 def showphaseidx(context, mapping):
723 723 """Integer. The changeset phase index. (ADVANCED)"""
724 724 ctx = context.resource(mapping, 'ctx')
725 725 return ctx.phase()
726 726
727 727 @templatekeyword('rev', requires={'ctx'})
728 728 def showrev(context, mapping):
729 729 """Integer. The repository-local changeset revision number."""
730 730 ctx = context.resource(mapping, 'ctx')
731 731 return scmutil.intrev(ctx)
732 732
733 733 def showrevslist(context, mapping, name, revs):
734 734 """helper to generate a list of revisions in which a mapped template will
735 735 be evaluated"""
736 736 repo = context.resource(mapping, 'repo')
737 737 f = _showcompatlist(context, mapping, name, ['%d' % r for r in revs])
738 738 return _hybrid(f, revs,
739 739 lambda x: {name: x, 'ctx': repo[x]},
740 740 pycompat.identity, keytype=int)
741 741
742 742 @templatekeyword('subrepos', requires={'ctx'})
743 743 def showsubrepos(context, mapping):
744 744 """List of strings. Updated subrepositories in the changeset."""
745 745 ctx = context.resource(mapping, 'ctx')
746 746 substate = ctx.substate
747 747 if not substate:
748 748 return compatlist(context, mapping, 'subrepo', [])
749 749 psubstate = ctx.parents()[0].substate or {}
750 750 subrepos = []
751 751 for sub in substate:
752 752 if sub not in psubstate or substate[sub] != psubstate[sub]:
753 753 subrepos.append(sub) # modified or newly added in ctx
754 754 for sub in psubstate:
755 755 if sub not in substate:
756 756 subrepos.append(sub) # removed in ctx
757 757 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
758 758
759 759 # don't remove "showtags" definition, even though namespaces will put
760 760 # a helper function for "tags" keyword into "keywords" map automatically,
761 761 # because online help text is built without namespaces initialization
762 762 @templatekeyword('tags', requires={'repo', 'ctx'})
763 763 def showtags(context, mapping):
764 764 """List of strings. Any tags associated with the changeset."""
765 765 return shownames(context, mapping, 'tags')
766 766
767 767 @templatekeyword('termwidth', requires={'ui'})
768 768 def showtermwidth(context, mapping):
769 769 """Integer. The width of the current terminal."""
770 770 ui = context.resource(mapping, 'ui')
771 771 return ui.termwidth()
772 772
773 773 @templatekeyword('instabilities', requires={'ctx'})
774 774 def showinstabilities(context, mapping):
775 775 """List of strings. Evolution instabilities affecting the changeset.
776 776 (EXPERIMENTAL)
777 777 """
778 778 ctx = context.resource(mapping, 'ctx')
779 779 return compatlist(context, mapping, 'instability', ctx.instabilities(),
780 780 plural='instabilities')
781 781
782 782 @templatekeyword('verbosity', requires={'ui'})
783 783 def showverbosity(context, mapping):
784 784 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
785 785 or ''."""
786 786 ui = context.resource(mapping, 'ui')
787 787 # see logcmdutil.changesettemplater for priority of these flags
788 788 if ui.debugflag:
789 789 return 'debug'
790 790 elif ui.quiet:
791 791 return 'quiet'
792 792 elif ui.verbose:
793 793 return 'verbose'
794 794 return ''
795 795
796 796 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
797 797 def showwhyunstable(context, mapping):
798 798 """List of dicts explaining all instabilities of a changeset.
799 799 (EXPERIMENTAL)
800 800 """
801 801 repo = context.resource(mapping, 'repo')
802 802 ctx = context.resource(mapping, 'ctx')
803 803
804 804 def formatnode(ctx):
805 return ' %s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
805 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
806 806
807 807 entries = obsutil.whyunstable(repo, ctx)
808 808
809 809 for entry in entries:
810 810 if entry.get('divergentnodes'):
811 811 dnodes = entry['divergentnodes']
812 812 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
813 813 lambda x: {'ctx': repo[x]},
814 814 lambda x: formatnode(repo[x]))
815 815 entry['divergentnodes'] = dnhybrid
816 816
817 tmpl = '{instability}:{divergentnodes} {reason} {node|short}'
817 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
818 '{reason} {node|short}')
818 819 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
819 820
820 821 def loadkeyword(ui, extname, registrarobj):
821 822 """Load template keyword from specified registrarobj
822 823 """
823 824 for name, func in registrarobj._table.iteritems():
824 825 keywords[name] = func
825 826
826 827 # tell hggettext to extract docstrings from these functions:
827 828 i18nfunctions = keywords.values()
@@ -1,756 +1,766 b''
1 1 Test file dedicated to testing the divergent troubles from obsolete changeset.
2 2
3 3 This is the most complex troubles from far so we isolate it in a dedicated
4 4 file.
5 5
6 6 Enable obsolete
7 7
8 8 $ cat >> $HGRCPATH << EOF
9 9 > [ui]
10 10 > logtemplate = {rev}:{node|short} {desc}{if(obsfate, " [{join(obsfate, "; ")}]")}\n
11 11 > [experimental]
12 12 > evolution.createmarkers=True
13 13 > [extensions]
14 14 > drawdag=$TESTDIR/drawdag.py
15 15 > [alias]
16 16 > debugobsolete = debugobsolete -d '0 0'
17 17 > [phases]
18 18 > publish=False
19 19 > [templates]
20 > wuentryshort = '{instability}:{divergentnodes} {reason} {node|shortest}\n'
20 > wuentryshort = '{instability}:{if(divergentnodes, " ")}{divergentnodes} {reason} {node|shortest}\n'
21 21 > whyunstableshort = '{whyunstable % wuentryshort}'
22 22 > wuentryshorter = '{instability}:{divergentnodes % " {node|shortest} ({phase})"} {reason} {node|shortest}\n'
23 23 > whyunstableshorter = '{whyunstable % wuentryshorter}'
24 24 > EOF
25 25
26 26
27 27 $ mkcommit() {
28 28 > echo "$1" > "$1"
29 29 > hg add "$1"
30 30 > hg ci -m "$1"
31 31 > }
32 32 $ getid() {
33 33 > hg log --hidden -r "desc('$1')" -T '{node}\n'
34 34 > }
35 35
36 36 setup repo
37 37
38 38 $ hg init reference
39 39 $ cd reference
40 40 $ mkcommit base
41 41 $ mkcommit A_0
42 42 $ hg up 0
43 43 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
44 44 $ mkcommit A_1
45 45 created new head
46 46 $ hg up 0
47 47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
48 48 $ mkcommit A_2
49 49 created new head
50 50 $ hg up 0
51 51 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
52 52 $ cd ..
53 53
54 54
55 55 $ newcase() {
56 56 > hg clone -u 0 -q reference $1
57 57 > cd $1
58 58 > }
59 59
60 60 direct divergence
61 61 -----------------
62 62
63 63 A_1 have two direct and divergent successors A_1 and A_1
64 64
65 65 $ newcase direct
66 66 $ hg debugobsolete `getid A_0` `getid A_1`
67 67 obsoleted 1 changesets
68 68 $ hg debugobsolete `getid A_0` `getid A_2`
69 69 2 new content-divergent changesets
70 70 $ hg log -G --hidden
71 71 * 3:392fd25390da A_2
72 72 |
73 73 | * 2:82623d38b9ba A_1
74 74 |/
75 75 | x 1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
76 76 |/
77 77 @ 0:d20a80d4def3 base
78 78
79 79 $ hg debugsuccessorssets --hidden 'all()'
80 80 d20a80d4def3
81 81 d20a80d4def3
82 82 007dc284c1f8
83 83 82623d38b9ba
84 84 392fd25390da
85 85 82623d38b9ba
86 86 82623d38b9ba
87 87 392fd25390da
88 88 392fd25390da
89 89 $ hg log -r 'contentdivergent()'
90 90 2:82623d38b9ba A_1
91 91 3:392fd25390da A_2
92 92 $ hg debugsuccessorssets 'all()' --closest
93 93 d20a80d4def3
94 94 d20a80d4def3
95 95 82623d38b9ba
96 96 82623d38b9ba
97 97 392fd25390da
98 98 392fd25390da
99 99 $ hg debugsuccessorssets 'all()' --closest --hidden
100 100 d20a80d4def3
101 101 d20a80d4def3
102 102 007dc284c1f8
103 103 82623d38b9ba
104 104 392fd25390da
105 105 82623d38b9ba
106 106 82623d38b9ba
107 107 392fd25390da
108 108 392fd25390da
109 109
110 110 check that mercurial refuse to push
111 111
112 112 $ hg init ../other
113 113 $ hg push ../other
114 114 pushing to ../other
115 115 searching for changes
116 116 abort: push includes content-divergent changeset: 392fd25390da!
117 117 [255]
118 118
119 119 $ cd ..
120 120
121 121
122 122 indirect divergence with known changeset
123 123 -------------------------------------------
124 124
125 125 $ newcase indirect_known
126 126 $ hg debugobsolete `getid A_0` `getid A_1`
127 127 obsoleted 1 changesets
128 128 $ hg debugobsolete `getid A_0` `getid A_2`
129 129 2 new content-divergent changesets
130 130 $ mkcommit A_3
131 131 created new head
132 132 $ hg debugobsolete `getid A_2` `getid A_3`
133 133 obsoleted 1 changesets
134 134 $ hg log -G --hidden
135 135 @ 4:01f36c5a8fda A_3
136 136 |
137 137 | x 3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda]
138 138 |/
139 139 | * 2:82623d38b9ba A_1
140 140 |/
141 141 | x 1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
142 142 |/
143 143 o 0:d20a80d4def3 base
144 144
145 145 $ hg debugsuccessorssets --hidden 'all()'
146 146 d20a80d4def3
147 147 d20a80d4def3
148 148 007dc284c1f8
149 149 82623d38b9ba
150 150 01f36c5a8fda
151 151 82623d38b9ba
152 152 82623d38b9ba
153 153 392fd25390da
154 154 01f36c5a8fda
155 155 01f36c5a8fda
156 156 01f36c5a8fda
157 157 $ hg log -r 'contentdivergent()'
158 158 2:82623d38b9ba A_1
159 159 4:01f36c5a8fda A_3
160 160 $ hg debugsuccessorssets 'all()' --closest
161 161 d20a80d4def3
162 162 d20a80d4def3
163 163 82623d38b9ba
164 164 82623d38b9ba
165 165 01f36c5a8fda
166 166 01f36c5a8fda
167 167 $ hg debugsuccessorssets 'all()' --closest --hidden
168 168 d20a80d4def3
169 169 d20a80d4def3
170 170 007dc284c1f8
171 171 82623d38b9ba
172 172 392fd25390da
173 173 82623d38b9ba
174 174 82623d38b9ba
175 175 392fd25390da
176 176 392fd25390da
177 177 01f36c5a8fda
178 178 01f36c5a8fda
179 179 $ cd ..
180 180
181 181
182 182 indirect divergence with known changeset
183 183 -------------------------------------------
184 184
185 185 $ newcase indirect_unknown
186 186 $ hg debugobsolete `getid A_0` aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
187 187 obsoleted 1 changesets
188 188 $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa `getid A_1`
189 189 $ hg debugobsolete `getid A_0` `getid A_2`
190 190 2 new content-divergent changesets
191 191 $ hg log -G --hidden
192 192 * 3:392fd25390da A_2
193 193 |
194 194 | * 2:82623d38b9ba A_1
195 195 |/
196 196 | x 1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
197 197 |/
198 198 @ 0:d20a80d4def3 base
199 199
200 200 $ hg debugsuccessorssets --hidden 'all()'
201 201 d20a80d4def3
202 202 d20a80d4def3
203 203 007dc284c1f8
204 204 82623d38b9ba
205 205 392fd25390da
206 206 82623d38b9ba
207 207 82623d38b9ba
208 208 392fd25390da
209 209 392fd25390da
210 210 $ hg log -r 'contentdivergent()'
211 211 2:82623d38b9ba A_1
212 212 3:392fd25390da A_2
213 213 $ hg debugsuccessorssets 'all()' --closest
214 214 d20a80d4def3
215 215 d20a80d4def3
216 216 82623d38b9ba
217 217 82623d38b9ba
218 218 392fd25390da
219 219 392fd25390da
220 220 $ hg debugsuccessorssets 'all()' --closest --hidden
221 221 d20a80d4def3
222 222 d20a80d4def3
223 223 007dc284c1f8
224 224 82623d38b9ba
225 225 392fd25390da
226 226 82623d38b9ba
227 227 82623d38b9ba
228 228 392fd25390da
229 229 392fd25390da
230 230 $ cd ..
231 231
232 232 do not take unknown node in account if they are final
233 233 -----------------------------------------------------
234 234
235 235 $ newcase final-unknown
236 236 $ hg debugobsolete `getid A_0` `getid A_1`
237 237 obsoleted 1 changesets
238 238 $ hg debugobsolete `getid A_1` `getid A_2`
239 239 obsoleted 1 changesets
240 240 $ hg debugobsolete `getid A_0` bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
241 241 $ hg debugobsolete bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccc
242 242 $ hg debugobsolete `getid A_1` dddddddddddddddddddddddddddddddddddddddd
243 243
244 244 $ hg debugsuccessorssets --hidden 'desc('A_0')'
245 245 007dc284c1f8
246 246 392fd25390da
247 247 $ hg debugsuccessorssets 'desc('A_0')' --closest
248 248 $ hg debugsuccessorssets 'desc('A_0')' --closest --hidden
249 249 007dc284c1f8
250 250 82623d38b9ba
251 251
252 252 $ cd ..
253 253
254 254 divergence that converge again is not divergence anymore
255 255 -----------------------------------------------------
256 256
257 257 $ newcase converged_divergence
258 258 $ hg debugobsolete `getid A_0` `getid A_1`
259 259 obsoleted 1 changesets
260 260 $ hg debugobsolete `getid A_0` `getid A_2`
261 261 2 new content-divergent changesets
262 262 $ mkcommit A_3
263 263 created new head
264 264 $ hg debugobsolete `getid A_1` `getid A_3`
265 265 obsoleted 1 changesets
266 266 $ hg debugobsolete `getid A_2` `getid A_3`
267 267 obsoleted 1 changesets
268 268 $ hg log -G --hidden
269 269 @ 4:01f36c5a8fda A_3
270 270 |
271 271 | x 3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda]
272 272 |/
273 273 | x 2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
274 274 |/
275 275 | x 1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
276 276 |/
277 277 o 0:d20a80d4def3 base
278 278
279 279 $ hg debugsuccessorssets --hidden 'all()'
280 280 d20a80d4def3
281 281 d20a80d4def3
282 282 007dc284c1f8
283 283 01f36c5a8fda
284 284 82623d38b9ba
285 285 01f36c5a8fda
286 286 392fd25390da
287 287 01f36c5a8fda
288 288 01f36c5a8fda
289 289 01f36c5a8fda
290 290 $ hg log -r 'contentdivergent()'
291 291 $ hg debugsuccessorssets 'all()' --closest
292 292 d20a80d4def3
293 293 d20a80d4def3
294 294 01f36c5a8fda
295 295 01f36c5a8fda
296 296 $ hg debugsuccessorssets 'all()' --closest --hidden
297 297 d20a80d4def3
298 298 d20a80d4def3
299 299 007dc284c1f8
300 300 82623d38b9ba
301 301 392fd25390da
302 302 82623d38b9ba
303 303 82623d38b9ba
304 304 392fd25390da
305 305 392fd25390da
306 306 01f36c5a8fda
307 307 01f36c5a8fda
308 308 $ cd ..
309 309
310 310 split is not divergences
311 311 -----------------------------
312 312
313 313 $ newcase split
314 314 $ hg debugobsolete `getid A_0` `getid A_1` `getid A_2`
315 315 obsoleted 1 changesets
316 316 $ hg log -G --hidden
317 317 o 3:392fd25390da A_2
318 318 |
319 319 | o 2:82623d38b9ba A_1
320 320 |/
321 321 | x 1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
322 322 |/
323 323 @ 0:d20a80d4def3 base
324 324
325 325 $ hg debugsuccessorssets --hidden 'all()'
326 326 d20a80d4def3
327 327 d20a80d4def3
328 328 007dc284c1f8
329 329 82623d38b9ba 392fd25390da
330 330 82623d38b9ba
331 331 82623d38b9ba
332 332 392fd25390da
333 333 392fd25390da
334 334 $ hg log -r 'contentdivergent()'
335 335 $ hg debugsuccessorssets 'all()' --closest
336 336 d20a80d4def3
337 337 d20a80d4def3
338 338 82623d38b9ba
339 339 82623d38b9ba
340 340 392fd25390da
341 341 392fd25390da
342 342 $ hg debugsuccessorssets 'all()' --closest --hidden
343 343 d20a80d4def3
344 344 d20a80d4def3
345 345 007dc284c1f8
346 346 82623d38b9ba 392fd25390da
347 347 82623d38b9ba
348 348 82623d38b9ba
349 349 392fd25390da
350 350 392fd25390da
351 351
352 352 Even when subsequent rewriting happen
353 353
354 354 $ mkcommit A_3
355 355 created new head
356 356 $ hg debugobsolete `getid A_1` `getid A_3`
357 357 obsoleted 1 changesets
358 358 $ hg up 0
359 359 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
360 360 $ mkcommit A_4
361 361 created new head
362 362 $ hg debugobsolete `getid A_2` `getid A_4`
363 363 obsoleted 1 changesets
364 364 $ hg up 0
365 365 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
366 366 $ mkcommit A_5
367 367 created new head
368 368 $ hg debugobsolete `getid A_4` `getid A_5`
369 369 obsoleted 1 changesets
370 370 $ hg log -G --hidden
371 371 @ 6:e442cfc57690 A_5
372 372 |
373 373 | x 5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
374 374 |/
375 375 | o 4:01f36c5a8fda A_3
376 376 |/
377 377 | x 3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
378 378 |/
379 379 | x 2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
380 380 |/
381 381 | x 1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
382 382 |/
383 383 o 0:d20a80d4def3 base
384 384
385 385 $ hg debugsuccessorssets --hidden 'all()'
386 386 d20a80d4def3
387 387 d20a80d4def3
388 388 007dc284c1f8
389 389 01f36c5a8fda e442cfc57690
390 390 82623d38b9ba
391 391 01f36c5a8fda
392 392 392fd25390da
393 393 e442cfc57690
394 394 01f36c5a8fda
395 395 01f36c5a8fda
396 396 6a411f0d7a0a
397 397 e442cfc57690
398 398 e442cfc57690
399 399 e442cfc57690
400 400 $ hg debugsuccessorssets 'all()' --closest
401 401 d20a80d4def3
402 402 d20a80d4def3
403 403 01f36c5a8fda
404 404 01f36c5a8fda
405 405 e442cfc57690
406 406 e442cfc57690
407 407 $ hg debugsuccessorssets 'all()' --closest --hidden
408 408 d20a80d4def3
409 409 d20a80d4def3
410 410 007dc284c1f8
411 411 82623d38b9ba 392fd25390da
412 412 82623d38b9ba
413 413 82623d38b9ba
414 414 392fd25390da
415 415 392fd25390da
416 416 01f36c5a8fda
417 417 01f36c5a8fda
418 418 6a411f0d7a0a
419 419 e442cfc57690
420 420 e442cfc57690
421 421 e442cfc57690
422 422 $ hg log -r 'contentdivergent()'
423 423
424 424 Check more complex obsolescence graft (with divergence)
425 425
426 426 $ mkcommit B_0; hg up 0
427 427 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
428 428 $ hg debugobsolete `getid B_0` `getid A_2`
429 429 obsoleted 1 changesets
430 430 $ mkcommit A_7; hg up 0
431 431 created new head
432 432 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
433 433 $ mkcommit A_8; hg up 0
434 434 created new head
435 435 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
436 436 $ hg debugobsolete `getid A_5` `getid A_7` `getid A_8`
437 437 obsoleted 1 changesets
438 438 $ mkcommit A_9; hg up 0
439 439 created new head
440 440 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
441 441 $ hg debugobsolete `getid A_5` `getid A_9`
442 442 4 new content-divergent changesets
443 443 $ hg log -G --hidden
444 444 * 10:bed64f5d2f5a A_9
445 445 |
446 446 | * 9:14608b260df8 A_8
447 447 |/
448 448 | * 8:7ae126973a96 A_7
449 449 |/
450 450 | x 7:3750ebee865d B_0 [rewritten as 3:392fd25390da]
451 451 | |
452 452 | x 6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a; split as 8:7ae126973a96, 9:14608b260df8]
453 453 |/
454 454 | x 5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
455 455 |/
456 456 | * 4:01f36c5a8fda A_3
457 457 |/
458 458 | x 3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
459 459 |/
460 460 | x 2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
461 461 |/
462 462 | x 1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
463 463 |/
464 464 @ 0:d20a80d4def3 base
465 465
466 466 $ hg debugsuccessorssets --hidden 'all()'
467 467 d20a80d4def3
468 468 d20a80d4def3
469 469 007dc284c1f8
470 470 01f36c5a8fda bed64f5d2f5a
471 471 01f36c5a8fda 7ae126973a96 14608b260df8
472 472 82623d38b9ba
473 473 01f36c5a8fda
474 474 392fd25390da
475 475 bed64f5d2f5a
476 476 7ae126973a96 14608b260df8
477 477 01f36c5a8fda
478 478 01f36c5a8fda
479 479 6a411f0d7a0a
480 480 bed64f5d2f5a
481 481 7ae126973a96 14608b260df8
482 482 e442cfc57690
483 483 bed64f5d2f5a
484 484 7ae126973a96 14608b260df8
485 485 3750ebee865d
486 486 bed64f5d2f5a
487 487 7ae126973a96 14608b260df8
488 488 7ae126973a96
489 489 7ae126973a96
490 490 14608b260df8
491 491 14608b260df8
492 492 bed64f5d2f5a
493 493 bed64f5d2f5a
494 494 $ hg debugsuccessorssets 'all()' --closest
495 495 d20a80d4def3
496 496 d20a80d4def3
497 497 01f36c5a8fda
498 498 01f36c5a8fda
499 499 7ae126973a96
500 500 7ae126973a96
501 501 14608b260df8
502 502 14608b260df8
503 503 bed64f5d2f5a
504 504 bed64f5d2f5a
505 505 $ hg debugsuccessorssets 'all()' --closest --hidden
506 506 d20a80d4def3
507 507 d20a80d4def3
508 508 007dc284c1f8
509 509 82623d38b9ba 392fd25390da
510 510 82623d38b9ba
511 511 82623d38b9ba
512 512 392fd25390da
513 513 392fd25390da
514 514 01f36c5a8fda
515 515 01f36c5a8fda
516 516 6a411f0d7a0a
517 517 e442cfc57690
518 518 e442cfc57690
519 519 e442cfc57690
520 520 3750ebee865d
521 521 392fd25390da
522 522 7ae126973a96
523 523 7ae126973a96
524 524 14608b260df8
525 525 14608b260df8
526 526 bed64f5d2f5a
527 527 bed64f5d2f5a
528 528 $ hg log -r 'contentdivergent()'
529 529 4:01f36c5a8fda A_3
530 530 8:7ae126973a96 A_7
531 531 9:14608b260df8 A_8
532 532 10:bed64f5d2f5a A_9
533 533
534 $ hg log -r bed64f5d2f5a -T '{whyunstable}\n' | sort
535 content-divergent: 4:01f36c5a8fda (draft) 8:7ae126973a96 (draft) 9:14608b260df8 (draft) predecessor 007dc284c1f8
536 content-divergent: 8:7ae126973a96 (draft) 9:14608b260df8 (draft) predecessor e442cfc57690
537 $ hg log -r bed64f5d2f5a -T whyunstableshort | sort
538 content-divergent: 4:01f36c5a8fda (draft) 8:7ae126973a96 (draft) 9:14608b260df8 (draft) predecessor 007d
539 content-divergent: 8:7ae126973a96 (draft) 9:14608b260df8 (draft) predecessor e442
540 $ hg log -r bed64f5d2f5a -T whyunstableshorter | sort
541 content-divergent: 01f3 (draft) 7ae1 (draft) 1460 (draft) predecessor 007d
542 content-divergent: 7ae1 (draft) 1460 (draft) predecessor e442
543
534 544 fix the divergence
535 545
536 546 $ mkcommit A_A; hg up 0
537 547 created new head
538 548 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
539 549 $ hg debugobsolete `getid A_9` `getid A_A`
540 550 obsoleted 1 changesets
541 551 $ hg debugobsolete `getid A_7` `getid A_A`
542 552 obsoleted 1 changesets
543 553 $ hg debugobsolete `getid A_8` `getid A_A`
544 554 obsoleted 1 changesets
545 555 $ hg log -G --hidden
546 556 o 11:a139f71be9da A_A
547 557 |
548 558 | x 10:bed64f5d2f5a A_9 [rewritten as 11:a139f71be9da]
549 559 |/
550 560 | x 9:14608b260df8 A_8 [rewritten as 11:a139f71be9da]
551 561 |/
552 562 | x 8:7ae126973a96 A_7 [rewritten as 11:a139f71be9da]
553 563 |/
554 564 | x 7:3750ebee865d B_0 [rewritten as 3:392fd25390da]
555 565 | |
556 566 | x 6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a; split as 8:7ae126973a96, 9:14608b260df8]
557 567 |/
558 568 | x 5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
559 569 |/
560 570 | o 4:01f36c5a8fda A_3
561 571 |/
562 572 | x 3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
563 573 |/
564 574 | x 2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
565 575 |/
566 576 | x 1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
567 577 |/
568 578 @ 0:d20a80d4def3 base
569 579
570 580 $ hg debugsuccessorssets --hidden 'all()'
571 581 d20a80d4def3
572 582 d20a80d4def3
573 583 007dc284c1f8
574 584 01f36c5a8fda a139f71be9da
575 585 82623d38b9ba
576 586 01f36c5a8fda
577 587 392fd25390da
578 588 a139f71be9da
579 589 01f36c5a8fda
580 590 01f36c5a8fda
581 591 6a411f0d7a0a
582 592 a139f71be9da
583 593 e442cfc57690
584 594 a139f71be9da
585 595 3750ebee865d
586 596 a139f71be9da
587 597 7ae126973a96
588 598 a139f71be9da
589 599 14608b260df8
590 600 a139f71be9da
591 601 bed64f5d2f5a
592 602 a139f71be9da
593 603 a139f71be9da
594 604 a139f71be9da
595 605 $ hg debugsuccessorssets 'all()' --closest
596 606 d20a80d4def3
597 607 d20a80d4def3
598 608 01f36c5a8fda
599 609 01f36c5a8fda
600 610 a139f71be9da
601 611 a139f71be9da
602 612 $ hg debugsuccessorssets 'all()' --closest --hidden
603 613 d20a80d4def3
604 614 d20a80d4def3
605 615 007dc284c1f8
606 616 82623d38b9ba 392fd25390da
607 617 82623d38b9ba
608 618 82623d38b9ba
609 619 392fd25390da
610 620 392fd25390da
611 621 01f36c5a8fda
612 622 01f36c5a8fda
613 623 6a411f0d7a0a
614 624 e442cfc57690
615 625 e442cfc57690
616 626 e442cfc57690
617 627 3750ebee865d
618 628 392fd25390da
619 629 7ae126973a96
620 630 a139f71be9da
621 631 14608b260df8
622 632 a139f71be9da
623 633 bed64f5d2f5a
624 634 a139f71be9da
625 635 a139f71be9da
626 636 a139f71be9da
627 637 $ hg log -r 'contentdivergent()'
628 638
629 639 #if serve
630 640
631 641 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid --config web.view=all \
632 642 > -A access.log -E errors.log
633 643 $ cat hg.pid >> $DAEMON_PIDS
634 644
635 645 check an obsolete changeset that was rewritten and also split
636 646
637 647 $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=paper' | egrep 'rewritten|split'
638 648 <td>rewritten as <a href="/rev/bed64f5d2f5a?style=paper">bed64f5d2f5a</a> by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span><br>
639 649 split as <a href="/rev/7ae126973a96?style=paper">7ae126973a96</a> <a href="/rev/14608b260df8?style=paper">14608b260df8</a> by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
640 650 $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=coal' | egrep 'rewritten|split'
641 651 <td>rewritten as <a href="/rev/bed64f5d2f5a?style=coal">bed64f5d2f5a</a> by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span><br>
642 652 split as <a href="/rev/7ae126973a96?style=coal">7ae126973a96</a> <a href="/rev/14608b260df8?style=coal">14608b260df8</a> by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
643 653 $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=gitweb' | egrep 'rewritten|split'
644 654 <td>rewritten as <a class="list" href="/rev/bed64f5d2f5a?style=gitweb">bed64f5d2f5a</a> by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
645 655 <td>split as <a class="list" href="/rev/7ae126973a96?style=gitweb">7ae126973a96</a> <a class="list" href="/rev/14608b260df8?style=gitweb">14608b260df8</a> by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
646 656 $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=monoblue' | egrep 'rewritten|split'
647 657 <dd>rewritten as <a href="/rev/bed64f5d2f5a?style=monoblue">bed64f5d2f5a</a> by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></dd>
648 658 <dd>split as <a href="/rev/7ae126973a96?style=monoblue">7ae126973a96</a> <a href="/rev/14608b260df8?style=monoblue">14608b260df8</a> by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></dd>
649 659 $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=spartan' | egrep 'rewritten|split'
650 660 <td class="obsolete">rewritten as <a href="/rev/bed64f5d2f5a?style=spartan">bed64f5d2f5a</a> by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
651 661 <td class="obsolete">split as <a href="/rev/7ae126973a96?style=spartan">7ae126973a96</a> <a href="/rev/14608b260df8?style=spartan">14608b260df8</a> by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
652 662
653 663 $ killdaemons.py
654 664
655 665 #endif
656 666
657 667 $ cd ..
658 668
659 669
660 670 Subset does not diverge
661 671 ------------------------------
662 672
663 673 Do not report divergent successors-set if it is a subset of another
664 674 successors-set. (report [A,B] not [A] + [A,B])
665 675
666 676 $ newcase subset
667 677 $ hg debugobsolete `getid A_0` `getid A_2`
668 678 obsoleted 1 changesets
669 679 $ hg debugobsolete `getid A_0` `getid A_1` `getid A_2`
670 680 $ hg debugsuccessorssets --hidden 'desc('A_0')'
671 681 007dc284c1f8
672 682 82623d38b9ba 392fd25390da
673 683 $ hg debugsuccessorssets 'desc('A_0')' --closest
674 684 $ hg debugsuccessorssets 'desc('A_0')' --closest --hidden
675 685 007dc284c1f8
676 686 82623d38b9ba 392fd25390da
677 687
678 688 $ cd ..
679 689
680 690 Use scmutil.cleanupnodes API to create divergence
681 691
682 692 $ hg init cleanupnodes
683 693 $ cd cleanupnodes
684 694 $ hg debugdrawdag <<'EOS'
685 695 > B1 B3 B4
686 696 > | \|
687 697 > A Z
688 698 > EOS
689 699
690 700 $ hg update -q B1
691 701 $ echo 3 >> B
692 702 $ hg commit --amend -m B2
693 703 $ cat > $TESTTMP/scmutilcleanup.py <<EOF
694 704 > from mercurial import registrar, scmutil
695 705 > cmdtable = {}
696 706 > command = registrar.command(cmdtable)
697 707 > @command('cleanup')
698 708 > def cleanup(ui, repo):
699 709 > def node(expr):
700 710 > unfi = repo.unfiltered()
701 711 > rev = unfi.revs(expr).first()
702 712 > return unfi.changelog.node(rev)
703 713 > with repo.wlock(), repo.lock(), repo.transaction('delayedstrip'):
704 714 > mapping = {node('desc(B1)'): [node('desc(B3)')],
705 715 > node('desc(B3)'): [node('desc(B4)')]}
706 716 > scmutil.cleanupnodes(repo, mapping, 'test')
707 717 > EOF
708 718
709 719 $ rm .hg/localtags
710 720 $ hg cleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
711 721 2 new content-divergent changesets
712 722 $ hg log -G -T '{rev}:{node|short} {desc} {instabilities}' -r 'sort(all(), topo)'
713 723 @ 5:1a2a9b5b0030 B2 content-divergent
714 724 |
715 725 | * 4:70d5a63ca112 B4 content-divergent
716 726 | |
717 727 | o 1:48b9aae0607f Z
718 728 |
719 729 o 0:426bada5c675 A
720 730
721 731 $ hg debugobsolete
722 732 a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
723 733 a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'test', 'user': 'test'}
724 734 ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'test', 'user': 'test'}
725 735
726 736 $ hg debugwhyunstable 1a2a9b5b0030
727 737 content-divergent: 70d5a63ca112acb3764bc1d7320ca90ea688d671 (draft) predecessor a178212c3433c4e77b573f6011e29affb8aefa33
728 738
729 739 $ hg log -r 1a2a9b5b0030 -T '{whyunstable}\n'
730 740 content-divergent: 4:70d5a63ca112 (draft) predecessor a178212c3433
731 741 $ hg log -r 1a2a9b5b0030 -T whyunstableshort
732 742 content-divergent: 4:70d5a63ca112 (draft) predecessor a178
733 743 $ hg log -r 1a2a9b5b0030 -T whyunstableshorter
734 744 content-divergent: 70d5 (draft) predecessor a178
735 745
736 746 #if serve
737 747
738 748 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
739 749 $ cat hg.pid >> $DAEMON_PIDS
740 750
741 751 check explanation for a content-divergent changeset
742 752
743 753 $ get-with-headers.py localhost:$HGPORT 'rev/1a2a9b5b0030?style=paper' | grep divergent:
744 754 <td>content-divergent: <a href="/rev/70d5a63ca112?style=paper">70d5a63ca112</a> (draft) predecessor <a href="/rev/a178212c3433?style=paper">a178212c3433</a></td>
745 755 $ get-with-headers.py localhost:$HGPORT 'rev/1a2a9b5b0030?style=coal' | grep divergent:
746 756 <td>content-divergent: <a href="/rev/70d5a63ca112?style=coal">70d5a63ca112</a> (draft) predecessor <a href="/rev/a178212c3433?style=coal">a178212c3433</a></td>
747 757 $ get-with-headers.py localhost:$HGPORT 'rev/1a2a9b5b0030?style=gitweb' | grep divergent:
748 758 <td>content-divergent: <a class="list" href="/rev/70d5a63ca112?style=gitweb">70d5a63ca112</a> (draft) predecessor <a class="list" href="/rev/a178212c3433?style=gitweb">a178212c3433</a></td>
749 759 $ get-with-headers.py localhost:$HGPORT 'rev/1a2a9b5b0030?style=monoblue' | grep divergent:
750 760 <dd>content-divergent: <a href="/rev/70d5a63ca112?style=monoblue">70d5a63ca112</a> (draft) predecessor <a href="/rev/a178212c3433?style=monoblue">a178212c3433</a></dd>
751 761 $ get-with-headers.py localhost:$HGPORT 'rev/1a2a9b5b0030?style=spartan' | grep divergent:
752 762 <td class="unstable">content-divergent: <a href="/rev/70d5a63ca112?style=spartan">70d5a63ca112</a> (draft) predecessor <a href="/rev/a178212c3433?style=spartan">a178212c3433</a></td>
753 763
754 764 $ killdaemons.py
755 765
756 766 #endif
General Comments 0
You need to be logged in to leave comments. Login now