##// END OF EJS Templates
templates: make divergentnodes in whyunstable keyword be a hybrid list
av6 -
r37704:b1b1b062 default
parent child Browse files
Show More
@@ -1,825 +1,827 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 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 entry['divergentnodes'] = ''.join(formatnode(dnode)
813 for dnode in dnodes)
812 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
813 lambda x: {'ctx': repo[x]},
814 lambda x: formatnode(repo[x]))
815 entry['divergentnodes'] = dnhybrid
814 816
815 817 tmpl = '{instability}:{divergentnodes} {reason} {node|short}'
816 818 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
817 819
818 820 def loadkeyword(ui, extname, registrarobj):
819 821 """Load template keyword from specified registrarobj
820 822 """
821 823 for name, func in registrarobj._table.iteritems():
822 824 keywords[name] = func
823 825
824 826 # tell hggettext to extract docstrings from these functions:
825 827 i18nfunctions = keywords.values()
@@ -1,749 +1,751 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 > EOF
20 20
21 21
22 22 $ mkcommit() {
23 23 > echo "$1" > "$1"
24 24 > hg add "$1"
25 25 > hg ci -m "$1"
26 26 > }
27 27 $ getid() {
28 28 > hg log --hidden -r "desc('$1')" -T '{node}\n'
29 29 > }
30 30
31 31 setup repo
32 32
33 33 $ hg init reference
34 34 $ cd reference
35 35 $ mkcommit base
36 36 $ mkcommit A_0
37 37 $ hg up 0
38 38 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
39 39 $ mkcommit A_1
40 40 created new head
41 41 $ hg up 0
42 42 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
43 43 $ mkcommit A_2
44 44 created new head
45 45 $ hg up 0
46 46 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
47 47 $ cd ..
48 48
49 49
50 50 $ newcase() {
51 51 > hg clone -u 0 -q reference $1
52 52 > cd $1
53 53 > }
54 54
55 55 direct divergence
56 56 -----------------
57 57
58 58 A_1 have two direct and divergent successors A_1 and A_1
59 59
60 60 $ newcase direct
61 61 $ hg debugobsolete `getid A_0` `getid A_1`
62 62 obsoleted 1 changesets
63 63 $ hg debugobsolete `getid A_0` `getid A_2`
64 64 2 new content-divergent changesets
65 65 $ hg log -G --hidden
66 66 * 3:392fd25390da A_2
67 67 |
68 68 | * 2:82623d38b9ba A_1
69 69 |/
70 70 | x 1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
71 71 |/
72 72 @ 0:d20a80d4def3 base
73 73
74 74 $ hg debugsuccessorssets --hidden 'all()'
75 75 d20a80d4def3
76 76 d20a80d4def3
77 77 007dc284c1f8
78 78 82623d38b9ba
79 79 392fd25390da
80 80 82623d38b9ba
81 81 82623d38b9ba
82 82 392fd25390da
83 83 392fd25390da
84 84 $ hg log -r 'contentdivergent()'
85 85 2:82623d38b9ba A_1
86 86 3:392fd25390da A_2
87 87 $ hg debugsuccessorssets 'all()' --closest
88 88 d20a80d4def3
89 89 d20a80d4def3
90 90 82623d38b9ba
91 91 82623d38b9ba
92 92 392fd25390da
93 93 392fd25390da
94 94 $ hg debugsuccessorssets 'all()' --closest --hidden
95 95 d20a80d4def3
96 96 d20a80d4def3
97 97 007dc284c1f8
98 98 82623d38b9ba
99 99 392fd25390da
100 100 82623d38b9ba
101 101 82623d38b9ba
102 102 392fd25390da
103 103 392fd25390da
104 104
105 105 check that mercurial refuse to push
106 106
107 107 $ hg init ../other
108 108 $ hg push ../other
109 109 pushing to ../other
110 110 searching for changes
111 111 abort: push includes content-divergent changeset: 392fd25390da!
112 112 [255]
113 113
114 114 $ cd ..
115 115
116 116
117 117 indirect divergence with known changeset
118 118 -------------------------------------------
119 119
120 120 $ newcase indirect_known
121 121 $ hg debugobsolete `getid A_0` `getid A_1`
122 122 obsoleted 1 changesets
123 123 $ hg debugobsolete `getid A_0` `getid A_2`
124 124 2 new content-divergent changesets
125 125 $ mkcommit A_3
126 126 created new head
127 127 $ hg debugobsolete `getid A_2` `getid A_3`
128 128 obsoleted 1 changesets
129 129 $ hg log -G --hidden
130 130 @ 4:01f36c5a8fda A_3
131 131 |
132 132 | x 3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda]
133 133 |/
134 134 | * 2:82623d38b9ba A_1
135 135 |/
136 136 | x 1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
137 137 |/
138 138 o 0:d20a80d4def3 base
139 139
140 140 $ hg debugsuccessorssets --hidden 'all()'
141 141 d20a80d4def3
142 142 d20a80d4def3
143 143 007dc284c1f8
144 144 82623d38b9ba
145 145 01f36c5a8fda
146 146 82623d38b9ba
147 147 82623d38b9ba
148 148 392fd25390da
149 149 01f36c5a8fda
150 150 01f36c5a8fda
151 151 01f36c5a8fda
152 152 $ hg log -r 'contentdivergent()'
153 153 2:82623d38b9ba A_1
154 154 4:01f36c5a8fda A_3
155 155 $ hg debugsuccessorssets 'all()' --closest
156 156 d20a80d4def3
157 157 d20a80d4def3
158 158 82623d38b9ba
159 159 82623d38b9ba
160 160 01f36c5a8fda
161 161 01f36c5a8fda
162 162 $ hg debugsuccessorssets 'all()' --closest --hidden
163 163 d20a80d4def3
164 164 d20a80d4def3
165 165 007dc284c1f8
166 166 82623d38b9ba
167 167 392fd25390da
168 168 82623d38b9ba
169 169 82623d38b9ba
170 170 392fd25390da
171 171 392fd25390da
172 172 01f36c5a8fda
173 173 01f36c5a8fda
174 174 $ cd ..
175 175
176 176
177 177 indirect divergence with known changeset
178 178 -------------------------------------------
179 179
180 180 $ newcase indirect_unknown
181 181 $ hg debugobsolete `getid A_0` aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
182 182 obsoleted 1 changesets
183 183 $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa `getid A_1`
184 184 $ hg debugobsolete `getid A_0` `getid A_2`
185 185 2 new content-divergent changesets
186 186 $ hg log -G --hidden
187 187 * 3:392fd25390da A_2
188 188 |
189 189 | * 2:82623d38b9ba A_1
190 190 |/
191 191 | x 1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
192 192 |/
193 193 @ 0:d20a80d4def3 base
194 194
195 195 $ hg debugsuccessorssets --hidden 'all()'
196 196 d20a80d4def3
197 197 d20a80d4def3
198 198 007dc284c1f8
199 199 82623d38b9ba
200 200 392fd25390da
201 201 82623d38b9ba
202 202 82623d38b9ba
203 203 392fd25390da
204 204 392fd25390da
205 205 $ hg log -r 'contentdivergent()'
206 206 2:82623d38b9ba A_1
207 207 3:392fd25390da A_2
208 208 $ hg debugsuccessorssets 'all()' --closest
209 209 d20a80d4def3
210 210 d20a80d4def3
211 211 82623d38b9ba
212 212 82623d38b9ba
213 213 392fd25390da
214 214 392fd25390da
215 215 $ hg debugsuccessorssets 'all()' --closest --hidden
216 216 d20a80d4def3
217 217 d20a80d4def3
218 218 007dc284c1f8
219 219 82623d38b9ba
220 220 392fd25390da
221 221 82623d38b9ba
222 222 82623d38b9ba
223 223 392fd25390da
224 224 392fd25390da
225 225 $ cd ..
226 226
227 227 do not take unknown node in account if they are final
228 228 -----------------------------------------------------
229 229
230 230 $ newcase final-unknown
231 231 $ hg debugobsolete `getid A_0` `getid A_1`
232 232 obsoleted 1 changesets
233 233 $ hg debugobsolete `getid A_1` `getid A_2`
234 234 obsoleted 1 changesets
235 235 $ hg debugobsolete `getid A_0` bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
236 236 $ hg debugobsolete bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccc
237 237 $ hg debugobsolete `getid A_1` dddddddddddddddddddddddddddddddddddddddd
238 238
239 239 $ hg debugsuccessorssets --hidden 'desc('A_0')'
240 240 007dc284c1f8
241 241 392fd25390da
242 242 $ hg debugsuccessorssets 'desc('A_0')' --closest
243 243 $ hg debugsuccessorssets 'desc('A_0')' --closest --hidden
244 244 007dc284c1f8
245 245 82623d38b9ba
246 246
247 247 $ cd ..
248 248
249 249 divergence that converge again is not divergence anymore
250 250 -----------------------------------------------------
251 251
252 252 $ newcase converged_divergence
253 253 $ hg debugobsolete `getid A_0` `getid A_1`
254 254 obsoleted 1 changesets
255 255 $ hg debugobsolete `getid A_0` `getid A_2`
256 256 2 new content-divergent changesets
257 257 $ mkcommit A_3
258 258 created new head
259 259 $ hg debugobsolete `getid A_1` `getid A_3`
260 260 obsoleted 1 changesets
261 261 $ hg debugobsolete `getid A_2` `getid A_3`
262 262 obsoleted 1 changesets
263 263 $ hg log -G --hidden
264 264 @ 4:01f36c5a8fda A_3
265 265 |
266 266 | x 3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda]
267 267 |/
268 268 | x 2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
269 269 |/
270 270 | x 1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
271 271 |/
272 272 o 0:d20a80d4def3 base
273 273
274 274 $ hg debugsuccessorssets --hidden 'all()'
275 275 d20a80d4def3
276 276 d20a80d4def3
277 277 007dc284c1f8
278 278 01f36c5a8fda
279 279 82623d38b9ba
280 280 01f36c5a8fda
281 281 392fd25390da
282 282 01f36c5a8fda
283 283 01f36c5a8fda
284 284 01f36c5a8fda
285 285 $ hg log -r 'contentdivergent()'
286 286 $ hg debugsuccessorssets 'all()' --closest
287 287 d20a80d4def3
288 288 d20a80d4def3
289 289 01f36c5a8fda
290 290 01f36c5a8fda
291 291 $ hg debugsuccessorssets 'all()' --closest --hidden
292 292 d20a80d4def3
293 293 d20a80d4def3
294 294 007dc284c1f8
295 295 82623d38b9ba
296 296 392fd25390da
297 297 82623d38b9ba
298 298 82623d38b9ba
299 299 392fd25390da
300 300 392fd25390da
301 301 01f36c5a8fda
302 302 01f36c5a8fda
303 303 $ cd ..
304 304
305 305 split is not divergences
306 306 -----------------------------
307 307
308 308 $ newcase split
309 309 $ hg debugobsolete `getid A_0` `getid A_1` `getid A_2`
310 310 obsoleted 1 changesets
311 311 $ hg log -G --hidden
312 312 o 3:392fd25390da A_2
313 313 |
314 314 | o 2:82623d38b9ba A_1
315 315 |/
316 316 | x 1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
317 317 |/
318 318 @ 0:d20a80d4def3 base
319 319
320 320 $ hg debugsuccessorssets --hidden 'all()'
321 321 d20a80d4def3
322 322 d20a80d4def3
323 323 007dc284c1f8
324 324 82623d38b9ba 392fd25390da
325 325 82623d38b9ba
326 326 82623d38b9ba
327 327 392fd25390da
328 328 392fd25390da
329 329 $ hg log -r 'contentdivergent()'
330 330 $ hg debugsuccessorssets 'all()' --closest
331 331 d20a80d4def3
332 332 d20a80d4def3
333 333 82623d38b9ba
334 334 82623d38b9ba
335 335 392fd25390da
336 336 392fd25390da
337 337 $ hg debugsuccessorssets 'all()' --closest --hidden
338 338 d20a80d4def3
339 339 d20a80d4def3
340 340 007dc284c1f8
341 341 82623d38b9ba 392fd25390da
342 342 82623d38b9ba
343 343 82623d38b9ba
344 344 392fd25390da
345 345 392fd25390da
346 346
347 347 Even when subsequent rewriting happen
348 348
349 349 $ mkcommit A_3
350 350 created new head
351 351 $ hg debugobsolete `getid A_1` `getid A_3`
352 352 obsoleted 1 changesets
353 353 $ hg up 0
354 354 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
355 355 $ mkcommit A_4
356 356 created new head
357 357 $ hg debugobsolete `getid A_2` `getid A_4`
358 358 obsoleted 1 changesets
359 359 $ hg up 0
360 360 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
361 361 $ mkcommit A_5
362 362 created new head
363 363 $ hg debugobsolete `getid A_4` `getid A_5`
364 364 obsoleted 1 changesets
365 365 $ hg log -G --hidden
366 366 @ 6:e442cfc57690 A_5
367 367 |
368 368 | x 5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
369 369 |/
370 370 | o 4:01f36c5a8fda A_3
371 371 |/
372 372 | x 3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
373 373 |/
374 374 | x 2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
375 375 |/
376 376 | x 1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
377 377 |/
378 378 o 0:d20a80d4def3 base
379 379
380 380 $ hg debugsuccessorssets --hidden 'all()'
381 381 d20a80d4def3
382 382 d20a80d4def3
383 383 007dc284c1f8
384 384 01f36c5a8fda e442cfc57690
385 385 82623d38b9ba
386 386 01f36c5a8fda
387 387 392fd25390da
388 388 e442cfc57690
389 389 01f36c5a8fda
390 390 01f36c5a8fda
391 391 6a411f0d7a0a
392 392 e442cfc57690
393 393 e442cfc57690
394 394 e442cfc57690
395 395 $ hg debugsuccessorssets 'all()' --closest
396 396 d20a80d4def3
397 397 d20a80d4def3
398 398 01f36c5a8fda
399 399 01f36c5a8fda
400 400 e442cfc57690
401 401 e442cfc57690
402 402 $ hg debugsuccessorssets 'all()' --closest --hidden
403 403 d20a80d4def3
404 404 d20a80d4def3
405 405 007dc284c1f8
406 406 82623d38b9ba 392fd25390da
407 407 82623d38b9ba
408 408 82623d38b9ba
409 409 392fd25390da
410 410 392fd25390da
411 411 01f36c5a8fda
412 412 01f36c5a8fda
413 413 6a411f0d7a0a
414 414 e442cfc57690
415 415 e442cfc57690
416 416 e442cfc57690
417 417 $ hg log -r 'contentdivergent()'
418 418
419 419 Check more complex obsolescence graft (with divergence)
420 420
421 421 $ mkcommit B_0; hg up 0
422 422 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
423 423 $ hg debugobsolete `getid B_0` `getid A_2`
424 424 obsoleted 1 changesets
425 425 $ mkcommit A_7; hg up 0
426 426 created new head
427 427 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
428 428 $ mkcommit A_8; hg up 0
429 429 created new head
430 430 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
431 431 $ hg debugobsolete `getid A_5` `getid A_7` `getid A_8`
432 432 obsoleted 1 changesets
433 433 $ mkcommit A_9; 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_9`
437 437 4 new content-divergent changesets
438 438 $ hg log -G --hidden
439 439 * 10:bed64f5d2f5a A_9
440 440 |
441 441 | * 9:14608b260df8 A_8
442 442 |/
443 443 | * 8:7ae126973a96 A_7
444 444 |/
445 445 | x 7:3750ebee865d B_0 [rewritten as 3:392fd25390da]
446 446 | |
447 447 | x 6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a; split as 8:7ae126973a96, 9:14608b260df8]
448 448 |/
449 449 | x 5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
450 450 |/
451 451 | * 4:01f36c5a8fda A_3
452 452 |/
453 453 | x 3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
454 454 |/
455 455 | x 2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
456 456 |/
457 457 | x 1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
458 458 |/
459 459 @ 0:d20a80d4def3 base
460 460
461 461 $ hg debugsuccessorssets --hidden 'all()'
462 462 d20a80d4def3
463 463 d20a80d4def3
464 464 007dc284c1f8
465 465 01f36c5a8fda bed64f5d2f5a
466 466 01f36c5a8fda 7ae126973a96 14608b260df8
467 467 82623d38b9ba
468 468 01f36c5a8fda
469 469 392fd25390da
470 470 bed64f5d2f5a
471 471 7ae126973a96 14608b260df8
472 472 01f36c5a8fda
473 473 01f36c5a8fda
474 474 6a411f0d7a0a
475 475 bed64f5d2f5a
476 476 7ae126973a96 14608b260df8
477 477 e442cfc57690
478 478 bed64f5d2f5a
479 479 7ae126973a96 14608b260df8
480 480 3750ebee865d
481 481 bed64f5d2f5a
482 482 7ae126973a96 14608b260df8
483 483 7ae126973a96
484 484 7ae126973a96
485 485 14608b260df8
486 486 14608b260df8
487 487 bed64f5d2f5a
488 488 bed64f5d2f5a
489 489 $ hg debugsuccessorssets 'all()' --closest
490 490 d20a80d4def3
491 491 d20a80d4def3
492 492 01f36c5a8fda
493 493 01f36c5a8fda
494 494 7ae126973a96
495 495 7ae126973a96
496 496 14608b260df8
497 497 14608b260df8
498 498 bed64f5d2f5a
499 499 bed64f5d2f5a
500 500 $ hg debugsuccessorssets 'all()' --closest --hidden
501 501 d20a80d4def3
502 502 d20a80d4def3
503 503 007dc284c1f8
504 504 82623d38b9ba 392fd25390da
505 505 82623d38b9ba
506 506 82623d38b9ba
507 507 392fd25390da
508 508 392fd25390da
509 509 01f36c5a8fda
510 510 01f36c5a8fda
511 511 6a411f0d7a0a
512 512 e442cfc57690
513 513 e442cfc57690
514 514 e442cfc57690
515 515 3750ebee865d
516 516 392fd25390da
517 517 7ae126973a96
518 518 7ae126973a96
519 519 14608b260df8
520 520 14608b260df8
521 521 bed64f5d2f5a
522 522 bed64f5d2f5a
523 523 $ hg log -r 'contentdivergent()'
524 524 4:01f36c5a8fda A_3
525 525 8:7ae126973a96 A_7
526 526 9:14608b260df8 A_8
527 527 10:bed64f5d2f5a A_9
528 528
529 529 fix the divergence
530 530
531 531 $ mkcommit A_A; hg up 0
532 532 created new head
533 533 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
534 534 $ hg debugobsolete `getid A_9` `getid A_A`
535 535 obsoleted 1 changesets
536 536 $ hg debugobsolete `getid A_7` `getid A_A`
537 537 obsoleted 1 changesets
538 538 $ hg debugobsolete `getid A_8` `getid A_A`
539 539 obsoleted 1 changesets
540 540 $ hg log -G --hidden
541 541 o 11:a139f71be9da A_A
542 542 |
543 543 | x 10:bed64f5d2f5a A_9 [rewritten as 11:a139f71be9da]
544 544 |/
545 545 | x 9:14608b260df8 A_8 [rewritten as 11:a139f71be9da]
546 546 |/
547 547 | x 8:7ae126973a96 A_7 [rewritten as 11:a139f71be9da]
548 548 |/
549 549 | x 7:3750ebee865d B_0 [rewritten as 3:392fd25390da]
550 550 | |
551 551 | x 6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a; split as 8:7ae126973a96, 9:14608b260df8]
552 552 |/
553 553 | x 5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
554 554 |/
555 555 | o 4:01f36c5a8fda A_3
556 556 |/
557 557 | x 3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
558 558 |/
559 559 | x 2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
560 560 |/
561 561 | x 1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
562 562 |/
563 563 @ 0:d20a80d4def3 base
564 564
565 565 $ hg debugsuccessorssets --hidden 'all()'
566 566 d20a80d4def3
567 567 d20a80d4def3
568 568 007dc284c1f8
569 569 01f36c5a8fda a139f71be9da
570 570 82623d38b9ba
571 571 01f36c5a8fda
572 572 392fd25390da
573 573 a139f71be9da
574 574 01f36c5a8fda
575 575 01f36c5a8fda
576 576 6a411f0d7a0a
577 577 a139f71be9da
578 578 e442cfc57690
579 579 a139f71be9da
580 580 3750ebee865d
581 581 a139f71be9da
582 582 7ae126973a96
583 583 a139f71be9da
584 584 14608b260df8
585 585 a139f71be9da
586 586 bed64f5d2f5a
587 587 a139f71be9da
588 588 a139f71be9da
589 589 a139f71be9da
590 590 $ hg debugsuccessorssets 'all()' --closest
591 591 d20a80d4def3
592 592 d20a80d4def3
593 593 01f36c5a8fda
594 594 01f36c5a8fda
595 595 a139f71be9da
596 596 a139f71be9da
597 597 $ hg debugsuccessorssets 'all()' --closest --hidden
598 598 d20a80d4def3
599 599 d20a80d4def3
600 600 007dc284c1f8
601 601 82623d38b9ba 392fd25390da
602 602 82623d38b9ba
603 603 82623d38b9ba
604 604 392fd25390da
605 605 392fd25390da
606 606 01f36c5a8fda
607 607 01f36c5a8fda
608 608 6a411f0d7a0a
609 609 e442cfc57690
610 610 e442cfc57690
611 611 e442cfc57690
612 612 3750ebee865d
613 613 392fd25390da
614 614 7ae126973a96
615 615 a139f71be9da
616 616 14608b260df8
617 617 a139f71be9da
618 618 bed64f5d2f5a
619 619 a139f71be9da
620 620 a139f71be9da
621 621 a139f71be9da
622 622 $ hg log -r 'contentdivergent()'
623 623
624 624 #if serve
625 625
626 626 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid --config web.view=all \
627 627 > -A access.log -E errors.log
628 628 $ cat hg.pid >> $DAEMON_PIDS
629 629
630 630 check an obsolete changeset that was rewritten and also split
631 631
632 632 $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=paper' | egrep 'rewritten|split'
633 633 <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>
634 634 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>
635 635 $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=coal' | egrep 'rewritten|split'
636 636 <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>
637 637 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>
638 638 $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=gitweb' | egrep 'rewritten|split'
639 639 <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>
640 640 <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>
641 641 $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=monoblue' | egrep 'rewritten|split'
642 642 <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>
643 643 <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>
644 644 $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=spartan' | egrep 'rewritten|split'
645 645 <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>
646 646 <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>
647 647
648 648 $ killdaemons.py
649 649
650 650 #endif
651 651
652 652 $ cd ..
653 653
654 654
655 655 Subset does not diverge
656 656 ------------------------------
657 657
658 658 Do not report divergent successors-set if it is a subset of another
659 659 successors-set. (report [A,B] not [A] + [A,B])
660 660
661 661 $ newcase subset
662 662 $ hg debugobsolete `getid A_0` `getid A_2`
663 663 obsoleted 1 changesets
664 664 $ hg debugobsolete `getid A_0` `getid A_1` `getid A_2`
665 665 $ hg debugsuccessorssets --hidden 'desc('A_0')'
666 666 007dc284c1f8
667 667 82623d38b9ba 392fd25390da
668 668 $ hg debugsuccessorssets 'desc('A_0')' --closest
669 669 $ hg debugsuccessorssets 'desc('A_0')' --closest --hidden
670 670 007dc284c1f8
671 671 82623d38b9ba 392fd25390da
672 672
673 673 $ cd ..
674 674
675 675 Use scmutil.cleanupnodes API to create divergence
676 676
677 677 $ hg init cleanupnodes
678 678 $ cd cleanupnodes
679 679 $ hg debugdrawdag <<'EOS'
680 680 > B1 B3 B4
681 681 > | \|
682 682 > A Z
683 683 > EOS
684 684
685 685 $ hg update -q B1
686 686 $ echo 3 >> B
687 687 $ hg commit --amend -m B2
688 688 $ cat > $TESTTMP/scmutilcleanup.py <<EOF
689 689 > from mercurial import registrar, scmutil
690 690 > cmdtable = {}
691 691 > command = registrar.command(cmdtable)
692 692 > @command('cleanup')
693 693 > def cleanup(ui, repo):
694 694 > def node(expr):
695 695 > unfi = repo.unfiltered()
696 696 > rev = unfi.revs(expr).first()
697 697 > return unfi.changelog.node(rev)
698 698 > with repo.wlock(), repo.lock(), repo.transaction('delayedstrip'):
699 699 > mapping = {node('desc(B1)'): [node('desc(B3)')],
700 700 > node('desc(B3)'): [node('desc(B4)')]}
701 701 > scmutil.cleanupnodes(repo, mapping, 'test')
702 702 > EOF
703 703
704 704 $ rm .hg/localtags
705 705 $ hg cleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
706 706 2 new content-divergent changesets
707 707 $ hg log -G -T '{rev}:{node|short} {desc} {instabilities}' -r 'sort(all(), topo)'
708 708 @ 5:1a2a9b5b0030 B2 content-divergent
709 709 |
710 710 | * 4:70d5a63ca112 B4 content-divergent
711 711 | |
712 712 | o 1:48b9aae0607f Z
713 713 |
714 714 o 0:426bada5c675 A
715 715
716 716 $ hg debugobsolete
717 717 a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
718 718 a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'test', 'user': 'test'}
719 719 ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'test', 'user': 'test'}
720 720
721 721 $ hg debugwhyunstable 1a2a9b5b0030
722 722 content-divergent: 70d5a63ca112acb3764bc1d7320ca90ea688d671 (draft) predecessor a178212c3433c4e77b573f6011e29affb8aefa33
723 723
724 724 $ hg log -r 1a2a9b5b0030 --hidden -T '{whyunstable}\n'
725 725 content-divergent: 4:70d5a63ca112 (draft) predecessor a178212c3433
726 726 $ hg log -r 1a2a9b5b0030 --hidden -T '{whyunstable%"{instability}:{divergentnodes} {reason} {node}\n"}'
727 727 content-divergent: 4:70d5a63ca112 (draft) predecessor a178212c3433c4e77b573f6011e29affb8aefa33
728 $ hg log -r 1a2a9b5b0030 --hidden -T '{whyunstable%"{instability}:{divergentnodes % " {node} ({phase})"} {reason} {node}\n"}'
729 content-divergent: 70d5a63ca112acb3764bc1d7320ca90ea688d671 (draft) predecessor a178212c3433c4e77b573f6011e29affb8aefa33
728 730
729 731 #if serve
730 732
731 733 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
732 734 $ cat hg.pid >> $DAEMON_PIDS
733 735
734 736 check explanation for a content-divergent changeset
735 737
736 738 $ get-with-headers.py localhost:$HGPORT 'rev/1a2a9b5b0030?style=paper' | grep divergent:
737 739 <td>content-divergent: <a href="/rev/70d5a63ca112?style=paper">70d5a63ca112</a> (draft) predecessor <a href="/rev/a178212c3433?style=paper">a178212c3433</a></td>
738 740 $ get-with-headers.py localhost:$HGPORT 'rev/1a2a9b5b0030?style=coal' | grep divergent:
739 741 <td>content-divergent: <a href="/rev/70d5a63ca112?style=coal">70d5a63ca112</a> (draft) predecessor <a href="/rev/a178212c3433?style=coal">a178212c3433</a></td>
740 742 $ get-with-headers.py localhost:$HGPORT 'rev/1a2a9b5b0030?style=gitweb' | grep divergent:
741 743 <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>
742 744 $ get-with-headers.py localhost:$HGPORT 'rev/1a2a9b5b0030?style=monoblue' | grep divergent:
743 745 <dd>content-divergent: <a href="/rev/70d5a63ca112?style=monoblue">70d5a63ca112</a> (draft) predecessor <a href="/rev/a178212c3433?style=monoblue">a178212c3433</a></dd>
744 746 $ get-with-headers.py localhost:$HGPORT 'rev/1a2a9b5b0030?style=spartan' | grep divergent:
745 747 <td class="unstable">content-divergent: <a href="/rev/70d5a63ca112?style=spartan">70d5a63ca112</a> (draft) predecessor <a href="/rev/a178212c3433?style=spartan">a178212c3433</a></td>
746 748
747 749 $ killdaemons.py
748 750
749 751 #endif
General Comments 0
You need to be logged in to leave comments. Login now