##// END OF EJS Templates
template: compute verb in obsfateverb...
Boris Feld -
r33995:3d0f8918 default
parent child Browse files
Show More
@@ -1,592 +1,603
1 1 # obsutil.py - utility functions for obsolescence
2 2 #
3 3 # Copyright 2017 Boris Feld <boris.feld@octobus.net>
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 . import (
11 11 phases,
12 12 util
13 13 )
14 14
15 15 class marker(object):
16 16 """Wrap obsolete marker raw data"""
17 17
18 18 def __init__(self, repo, data):
19 19 # the repo argument will be used to create changectx in later version
20 20 self._repo = repo
21 21 self._data = data
22 22 self._decodedmeta = None
23 23
24 24 def __hash__(self):
25 25 return hash(self._data)
26 26
27 27 def __eq__(self, other):
28 28 if type(other) != type(self):
29 29 return False
30 30 return self._data == other._data
31 31
32 32 def precnode(self):
33 33 msg = ("'marker.precnode' is deprecated, "
34 34 "use 'marker.prednode'")
35 35 util.nouideprecwarn(msg, '4.4')
36 36 return self.prednode()
37 37
38 38 def prednode(self):
39 39 """Predecessor changeset node identifier"""
40 40 return self._data[0]
41 41
42 42 def succnodes(self):
43 43 """List of successor changesets node identifiers"""
44 44 return self._data[1]
45 45
46 46 def parentnodes(self):
47 47 """Parents of the predecessors (None if not recorded)"""
48 48 return self._data[5]
49 49
50 50 def metadata(self):
51 51 """Decoded metadata dictionary"""
52 52 return dict(self._data[3])
53 53
54 54 def date(self):
55 55 """Creation date as (unixtime, offset)"""
56 56 return self._data[4]
57 57
58 58 def flags(self):
59 59 """The flags field of the marker"""
60 60 return self._data[2]
61 61
62 62 def getmarkers(repo, nodes=None, exclusive=False):
63 63 """returns markers known in a repository
64 64
65 65 If <nodes> is specified, only markers "relevant" to those nodes are are
66 66 returned"""
67 67 if nodes is None:
68 68 rawmarkers = repo.obsstore
69 69 elif exclusive:
70 70 rawmarkers = exclusivemarkers(repo, nodes)
71 71 else:
72 72 rawmarkers = repo.obsstore.relevantmarkers(nodes)
73 73
74 74 for markerdata in rawmarkers:
75 75 yield marker(repo, markerdata)
76 76
77 77 def closestpredecessors(repo, nodeid):
78 78 """yield the list of next predecessors pointing on visible changectx nodes
79 79
80 80 This function respect the repoview filtering, filtered revision will be
81 81 considered missing.
82 82 """
83 83
84 84 precursors = repo.obsstore.predecessors
85 85 stack = [nodeid]
86 86 seen = set(stack)
87 87
88 88 while stack:
89 89 current = stack.pop()
90 90 currentpreccs = precursors.get(current, ())
91 91
92 92 for prec in currentpreccs:
93 93 precnodeid = prec[0]
94 94
95 95 # Basic cycle protection
96 96 if precnodeid in seen:
97 97 continue
98 98 seen.add(precnodeid)
99 99
100 100 if precnodeid in repo:
101 101 yield precnodeid
102 102 else:
103 103 stack.append(precnodeid)
104 104
105 105 def allprecursors(*args, **kwargs):
106 106 """ (DEPRECATED)
107 107 """
108 108 msg = ("'obsutil.allprecursors' is deprecated, "
109 109 "use 'obsutil.allpredecessors'")
110 110 util.nouideprecwarn(msg, '4.4')
111 111
112 112 return allpredecessors(*args, **kwargs)
113 113
114 114 def allpredecessors(obsstore, nodes, ignoreflags=0):
115 115 """Yield node for every precursors of <nodes>.
116 116
117 117 Some precursors may be unknown locally.
118 118
119 119 This is a linear yield unsuited to detecting folded changesets. It includes
120 120 initial nodes too."""
121 121
122 122 remaining = set(nodes)
123 123 seen = set(remaining)
124 124 while remaining:
125 125 current = remaining.pop()
126 126 yield current
127 127 for mark in obsstore.predecessors.get(current, ()):
128 128 # ignore marker flagged with specified flag
129 129 if mark[2] & ignoreflags:
130 130 continue
131 131 suc = mark[0]
132 132 if suc not in seen:
133 133 seen.add(suc)
134 134 remaining.add(suc)
135 135
136 136 def allsuccessors(obsstore, nodes, ignoreflags=0):
137 137 """Yield node for every successor of <nodes>.
138 138
139 139 Some successors may be unknown locally.
140 140
141 141 This is a linear yield unsuited to detecting split changesets. It includes
142 142 initial nodes too."""
143 143 remaining = set(nodes)
144 144 seen = set(remaining)
145 145 while remaining:
146 146 current = remaining.pop()
147 147 yield current
148 148 for mark in obsstore.successors.get(current, ()):
149 149 # ignore marker flagged with specified flag
150 150 if mark[2] & ignoreflags:
151 151 continue
152 152 for suc in mark[1]:
153 153 if suc not in seen:
154 154 seen.add(suc)
155 155 remaining.add(suc)
156 156
157 157 def _filterprunes(markers):
158 158 """return a set with no prune markers"""
159 159 return set(m for m in markers if m[1])
160 160
161 161 def exclusivemarkers(repo, nodes):
162 162 """set of markers relevant to "nodes" but no other locally-known nodes
163 163
164 164 This function compute the set of markers "exclusive" to a locally-known
165 165 node. This means we walk the markers starting from <nodes> until we reach a
166 166 locally-known precursors outside of <nodes>. Element of <nodes> with
167 167 locally-known successors outside of <nodes> are ignored (since their
168 168 precursors markers are also relevant to these successors).
169 169
170 170 For example:
171 171
172 172 # (A0 rewritten as A1)
173 173 #
174 174 # A0 <-1- A1 # Marker "1" is exclusive to A1
175 175
176 176 or
177 177
178 178 # (A0 rewritten as AX; AX rewritten as A1; AX is unkown locally)
179 179 #
180 180 # <-1- A0 <-2- AX <-3- A1 # Marker "2,3" are exclusive to A1
181 181
182 182 or
183 183
184 184 # (A0 has unknown precursors, A0 rewritten as A1 and A2 (divergence))
185 185 #
186 186 # <-2- A1 # Marker "2" is exclusive to A0,A1
187 187 # /
188 188 # <-1- A0
189 189 # \
190 190 # <-3- A2 # Marker "3" is exclusive to A0,A2
191 191 #
192 192 # in addition:
193 193 #
194 194 # Markers "2,3" are exclusive to A1,A2
195 195 # Markers "1,2,3" are exclusive to A0,A1,A2
196 196
197 197 See test/test-obsolete-bundle-strip.t for more examples.
198 198
199 199 An example usage is strip. When stripping a changeset, we also want to
200 200 strip the markers exclusive to this changeset. Otherwise we would have
201 201 "dangling"" obsolescence markers from its precursors: Obsolescence markers
202 202 marking a node as obsolete without any successors available locally.
203 203
204 204 As for relevant markers, the prune markers for children will be followed.
205 205 Of course, they will only be followed if the pruned children is
206 206 locally-known. Since the prune markers are relevant to the pruned node.
207 207 However, while prune markers are considered relevant to the parent of the
208 208 pruned changesets, prune markers for locally-known changeset (with no
209 209 successors) are considered exclusive to the pruned nodes. This allows
210 210 to strip the prune markers (with the rest of the exclusive chain) alongside
211 211 the pruned changesets.
212 212 """
213 213 # running on a filtered repository would be dangerous as markers could be
214 214 # reported as exclusive when they are relevant for other filtered nodes.
215 215 unfi = repo.unfiltered()
216 216
217 217 # shortcut to various useful item
218 218 nm = unfi.changelog.nodemap
219 219 precursorsmarkers = unfi.obsstore.predecessors
220 220 successormarkers = unfi.obsstore.successors
221 221 childrenmarkers = unfi.obsstore.children
222 222
223 223 # exclusive markers (return of the function)
224 224 exclmarkers = set()
225 225 # we need fast membership testing
226 226 nodes = set(nodes)
227 227 # looking for head in the obshistory
228 228 #
229 229 # XXX we are ignoring all issues in regard with cycle for now.
230 230 stack = [n for n in nodes if not _filterprunes(successormarkers.get(n, ()))]
231 231 stack.sort()
232 232 # nodes already stacked
233 233 seennodes = set(stack)
234 234 while stack:
235 235 current = stack.pop()
236 236 # fetch precursors markers
237 237 markers = list(precursorsmarkers.get(current, ()))
238 238 # extend the list with prune markers
239 239 for mark in successormarkers.get(current, ()):
240 240 if not mark[1]:
241 241 markers.append(mark)
242 242 # and markers from children (looking for prune)
243 243 for mark in childrenmarkers.get(current, ()):
244 244 if not mark[1]:
245 245 markers.append(mark)
246 246 # traverse the markers
247 247 for mark in markers:
248 248 if mark in exclmarkers:
249 249 # markers already selected
250 250 continue
251 251
252 252 # If the markers is about the current node, select it
253 253 #
254 254 # (this delay the addition of markers from children)
255 255 if mark[1] or mark[0] == current:
256 256 exclmarkers.add(mark)
257 257
258 258 # should we keep traversing through the precursors?
259 259 prec = mark[0]
260 260
261 261 # nodes in the stack or already processed
262 262 if prec in seennodes:
263 263 continue
264 264
265 265 # is this a locally known node ?
266 266 known = prec in nm
267 267 # if locally-known and not in the <nodes> set the traversal
268 268 # stop here.
269 269 if known and prec not in nodes:
270 270 continue
271 271
272 272 # do not keep going if there are unselected markers pointing to this
273 273 # nodes. If we end up traversing these unselected markers later the
274 274 # node will be taken care of at that point.
275 275 precmarkers = _filterprunes(successormarkers.get(prec))
276 276 if precmarkers.issubset(exclmarkers):
277 277 seennodes.add(prec)
278 278 stack.append(prec)
279 279
280 280 return exclmarkers
281 281
282 282 def foreground(repo, nodes):
283 283 """return all nodes in the "foreground" of other node
284 284
285 285 The foreground of a revision is anything reachable using parent -> children
286 286 or precursor -> successor relation. It is very similar to "descendant" but
287 287 augmented with obsolescence information.
288 288
289 289 Beware that possible obsolescence cycle may result if complex situation.
290 290 """
291 291 repo = repo.unfiltered()
292 292 foreground = set(repo.set('%ln::', nodes))
293 293 if repo.obsstore:
294 294 # We only need this complicated logic if there is obsolescence
295 295 # XXX will probably deserve an optimised revset.
296 296 nm = repo.changelog.nodemap
297 297 plen = -1
298 298 # compute the whole set of successors or descendants
299 299 while len(foreground) != plen:
300 300 plen = len(foreground)
301 301 succs = set(c.node() for c in foreground)
302 302 mutable = [c.node() for c in foreground if c.mutable()]
303 303 succs.update(allsuccessors(repo.obsstore, mutable))
304 304 known = (n for n in succs if n in nm)
305 305 foreground = set(repo.set('%ln::', known))
306 306 return set(c.node() for c in foreground)
307 307
308 308 def getobsoleted(repo, tr):
309 309 """return the set of pre-existing revisions obsoleted by a transaction"""
310 310 torev = repo.unfiltered().changelog.nodemap.get
311 311 phase = repo._phasecache.phase
312 312 succsmarkers = repo.obsstore.successors.get
313 313 public = phases.public
314 314 addedmarkers = tr.changes.get('obsmarkers')
315 315 addedrevs = tr.changes.get('revs')
316 316 seenrevs = set(addedrevs)
317 317 obsoleted = set()
318 318 for mark in addedmarkers:
319 319 node = mark[0]
320 320 rev = torev(node)
321 321 if rev is None or rev in seenrevs:
322 322 continue
323 323 seenrevs.add(rev)
324 324 if phase(repo, rev) == public:
325 325 continue
326 326 if set(succsmarkers(node) or []).issubset(addedmarkers):
327 327 obsoleted.add(rev)
328 328 return obsoleted
329 329
330 330 class _succs(list):
331 331 """small class to represent a successors with some metadata about it"""
332 332
333 333 def __init__(self, *args, **kwargs):
334 334 super(_succs, self).__init__(*args, **kwargs)
335 335 self.markers = set()
336 336
337 337 def copy(self):
338 338 new = _succs(self)
339 339 new.markers = self.markers.copy()
340 340 return new
341 341
342 342 @util.propertycache
343 343 def _set(self):
344 344 # immutable
345 345 return set(self)
346 346
347 347 def canmerge(self, other):
348 348 return self._set.issubset(other._set)
349 349
350 350 def successorssets(repo, initialnode, closest=False, cache=None):
351 351 """Return set of all latest successors of initial nodes
352 352
353 353 The successors set of a changeset A are the group of revisions that succeed
354 354 A. It succeeds A as a consistent whole, each revision being only a partial
355 355 replacement. By default, the successors set contains non-obsolete
356 356 changesets only, walking the obsolescence graph until reaching a leaf. If
357 357 'closest' is set to True, closest successors-sets are return (the
358 358 obsolescence walk stops on known changesets).
359 359
360 360 This function returns the full list of successor sets which is why it
361 361 returns a list of tuples and not just a single tuple. Each tuple is a valid
362 362 successors set. Note that (A,) may be a valid successors set for changeset A
363 363 (see below).
364 364
365 365 In most cases, a changeset A will have a single element (e.g. the changeset
366 366 A is replaced by A') in its successors set. Though, it is also common for a
367 367 changeset A to have no elements in its successor set (e.g. the changeset
368 368 has been pruned). Therefore, the returned list of successors sets will be
369 369 [(A',)] or [], respectively.
370 370
371 371 When a changeset A is split into A' and B', however, it will result in a
372 372 successors set containing more than a single element, i.e. [(A',B')].
373 373 Divergent changesets will result in multiple successors sets, i.e. [(A',),
374 374 (A'')].
375 375
376 376 If a changeset A is not obsolete, then it will conceptually have no
377 377 successors set. To distinguish this from a pruned changeset, the successor
378 378 set will contain itself only, i.e. [(A,)].
379 379
380 380 Finally, final successors unknown locally are considered to be pruned
381 381 (pruned: obsoleted without any successors). (Final: successors not affected
382 382 by markers).
383 383
384 384 The 'closest' mode respect the repoview filtering. For example, without
385 385 filter it will stop at the first locally known changeset, with 'visible'
386 386 filter it will stop on visible changesets).
387 387
388 388 The optional `cache` parameter is a dictionary that may contains
389 389 precomputed successors sets. It is meant to reuse the computation of a
390 390 previous call to `successorssets` when multiple calls are made at the same
391 391 time. The cache dictionary is updated in place. The caller is responsible
392 392 for its life span. Code that makes multiple calls to `successorssets`
393 393 *should* use this cache mechanism or risk a performance hit.
394 394
395 395 Since results are different depending of the 'closest' most, the same cache
396 396 cannot be reused for both mode.
397 397 """
398 398
399 399 succmarkers = repo.obsstore.successors
400 400
401 401 # Stack of nodes we search successors sets for
402 402 toproceed = [initialnode]
403 403 # set version of above list for fast loop detection
404 404 # element added to "toproceed" must be added here
405 405 stackedset = set(toproceed)
406 406 if cache is None:
407 407 cache = {}
408 408
409 409 # This while loop is the flattened version of a recursive search for
410 410 # successors sets
411 411 #
412 412 # def successorssets(x):
413 413 # successors = directsuccessors(x)
414 414 # ss = [[]]
415 415 # for succ in directsuccessors(x):
416 416 # # product as in itertools cartesian product
417 417 # ss = product(ss, successorssets(succ))
418 418 # return ss
419 419 #
420 420 # But we can not use plain recursive calls here:
421 421 # - that would blow the python call stack
422 422 # - obsolescence markers may have cycles, we need to handle them.
423 423 #
424 424 # The `toproceed` list act as our call stack. Every node we search
425 425 # successors set for are stacked there.
426 426 #
427 427 # The `stackedset` is set version of this stack used to check if a node is
428 428 # already stacked. This check is used to detect cycles and prevent infinite
429 429 # loop.
430 430 #
431 431 # successors set of all nodes are stored in the `cache` dictionary.
432 432 #
433 433 # After this while loop ends we use the cache to return the successors sets
434 434 # for the node requested by the caller.
435 435 while toproceed:
436 436 # Every iteration tries to compute the successors sets of the topmost
437 437 # node of the stack: CURRENT.
438 438 #
439 439 # There are four possible outcomes:
440 440 #
441 441 # 1) We already know the successors sets of CURRENT:
442 442 # -> mission accomplished, pop it from the stack.
443 443 # 2) Stop the walk:
444 444 # default case: Node is not obsolete
445 445 # closest case: Node is known at this repo filter level
446 446 # -> the node is its own successors sets. Add it to the cache.
447 447 # 3) We do not know successors set of direct successors of CURRENT:
448 448 # -> We add those successors to the stack.
449 449 # 4) We know successors sets of all direct successors of CURRENT:
450 450 # -> We can compute CURRENT successors set and add it to the
451 451 # cache.
452 452 #
453 453 current = toproceed[-1]
454 454
455 455 # case 2 condition is a bit hairy because of closest,
456 456 # we compute it on its own
457 457 case2condition = ((current not in succmarkers)
458 458 or (closest and current != initialnode
459 459 and current in repo))
460 460
461 461 if current in cache:
462 462 # case (1): We already know the successors sets
463 463 stackedset.remove(toproceed.pop())
464 464 elif case2condition:
465 465 # case (2): end of walk.
466 466 if current in repo:
467 467 # We have a valid successors.
468 468 cache[current] = [_succs((current,))]
469 469 else:
470 470 # Final obsolete version is unknown locally.
471 471 # Do not count that as a valid successors
472 472 cache[current] = []
473 473 else:
474 474 # cases (3) and (4)
475 475 #
476 476 # We proceed in two phases. Phase 1 aims to distinguish case (3)
477 477 # from case (4):
478 478 #
479 479 # For each direct successors of CURRENT, we check whether its
480 480 # successors sets are known. If they are not, we stack the
481 481 # unknown node and proceed to the next iteration of the while
482 482 # loop. (case 3)
483 483 #
484 484 # During this step, we may detect obsolescence cycles: a node
485 485 # with unknown successors sets but already in the call stack.
486 486 # In such a situation, we arbitrary set the successors sets of
487 487 # the node to nothing (node pruned) to break the cycle.
488 488 #
489 489 # If no break was encountered we proceed to phase 2.
490 490 #
491 491 # Phase 2 computes successors sets of CURRENT (case 4); see details
492 492 # in phase 2 itself.
493 493 #
494 494 # Note the two levels of iteration in each phase.
495 495 # - The first one handles obsolescence markers using CURRENT as
496 496 # precursor (successors markers of CURRENT).
497 497 #
498 498 # Having multiple entry here means divergence.
499 499 #
500 500 # - The second one handles successors defined in each marker.
501 501 #
502 502 # Having none means pruned node, multiple successors means split,
503 503 # single successors are standard replacement.
504 504 #
505 505 for mark in sorted(succmarkers[current]):
506 506 for suc in mark[1]:
507 507 if suc not in cache:
508 508 if suc in stackedset:
509 509 # cycle breaking
510 510 cache[suc] = []
511 511 else:
512 512 # case (3) If we have not computed successors sets
513 513 # of one of those successors we add it to the
514 514 # `toproceed` stack and stop all work for this
515 515 # iteration.
516 516 toproceed.append(suc)
517 517 stackedset.add(suc)
518 518 break
519 519 else:
520 520 continue
521 521 break
522 522 else:
523 523 # case (4): we know all successors sets of all direct
524 524 # successors
525 525 #
526 526 # Successors set contributed by each marker depends on the
527 527 # successors sets of all its "successors" node.
528 528 #
529 529 # Each different marker is a divergence in the obsolescence
530 530 # history. It contributes successors sets distinct from other
531 531 # markers.
532 532 #
533 533 # Within a marker, a successor may have divergent successors
534 534 # sets. In such a case, the marker will contribute multiple
535 535 # divergent successors sets. If multiple successors have
536 536 # divergent successors sets, a Cartesian product is used.
537 537 #
538 538 # At the end we post-process successors sets to remove
539 539 # duplicated entry and successors set that are strict subset of
540 540 # another one.
541 541 succssets = []
542 542 for mark in sorted(succmarkers[current]):
543 543 # successors sets contributed by this marker
544 544 base = _succs()
545 545 base.markers.add(mark)
546 546 markss = [base]
547 547 for suc in mark[1]:
548 548 # cardinal product with previous successors
549 549 productresult = []
550 550 for prefix in markss:
551 551 for suffix in cache[suc]:
552 552 newss = prefix.copy()
553 553 newss.markers.update(suffix.markers)
554 554 for part in suffix:
555 555 # do not duplicated entry in successors set
556 556 # first entry wins.
557 557 if part not in newss:
558 558 newss.append(part)
559 559 productresult.append(newss)
560 560 markss = productresult
561 561 succssets.extend(markss)
562 562 # remove duplicated and subset
563 563 seen = []
564 564 final = []
565 565 candidates = sorted((s for s in succssets if s),
566 566 key=len, reverse=True)
567 567 for cand in candidates:
568 568 for seensuccs in seen:
569 569 if cand.canmerge(seensuccs):
570 570 seensuccs.markers.update(cand.markers)
571 571 break
572 572 else:
573 573 final.append(cand)
574 574 seen.append(cand)
575 575 final.reverse() # put small successors set first
576 576 cache[current] = final
577 577 return cache[initialnode]
578 578
579 579 def successorsandmarkers(repo, ctx):
580 580 """compute the raw data needed for computing obsfate
581 581 Returns a list of dict, one dict per successors set
582 582 """
583 583 if not ctx.obsolete():
584 584 return None
585 585
586 586 ssets = successorssets(repo, ctx.node(), closest=True)
587 587
588 588 values = []
589 589 for sset in ssets:
590 590 values.append({'successors': sset, 'markers': sset.markers})
591 591
592 592 return values
593
594 def successorsetverb(successorset):
595 """ Return the verb summarizing the successorset
596 """
597 if not successorset:
598 verb = 'pruned'
599 elif len(successorset) == 1:
600 verb = 'rewritten'
601 else:
602 verb = 'split'
603 return verb
@@ -1,1380 +1,1397
1 1 # templater.py - template expansion for output
2 2 #
3 3 # Copyright 2005, 2006 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 import os
11 11 import re
12 12 import types
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 color,
17 17 config,
18 18 encoding,
19 19 error,
20 20 minirst,
21 obsutil,
21 22 parser,
22 23 pycompat,
23 24 registrar,
24 25 revset as revsetmod,
25 26 revsetlang,
26 27 templatefilters,
27 28 templatekw,
28 29 util,
29 30 )
30 31
31 32 # template parsing
32 33
33 34 elements = {
34 35 # token-type: binding-strength, primary, prefix, infix, suffix
35 36 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
36 37 "%": (16, None, None, ("%", 16), None),
37 38 "|": (15, None, None, ("|", 15), None),
38 39 "*": (5, None, None, ("*", 5), None),
39 40 "/": (5, None, None, ("/", 5), None),
40 41 "+": (4, None, None, ("+", 4), None),
41 42 "-": (4, None, ("negate", 19), ("-", 4), None),
42 43 "=": (3, None, None, ("keyvalue", 3), None),
43 44 ",": (2, None, None, ("list", 2), None),
44 45 ")": (0, None, None, None, None),
45 46 "integer": (0, "integer", None, None, None),
46 47 "symbol": (0, "symbol", None, None, None),
47 48 "string": (0, "string", None, None, None),
48 49 "template": (0, "template", None, None, None),
49 50 "end": (0, None, None, None, None),
50 51 }
51 52
52 53 def tokenize(program, start, end, term=None):
53 54 """Parse a template expression into a stream of tokens, which must end
54 55 with term if specified"""
55 56 pos = start
56 57 program = pycompat.bytestr(program)
57 58 while pos < end:
58 59 c = program[pos]
59 60 if c.isspace(): # skip inter-token whitespace
60 61 pass
61 62 elif c in "(=,)%|+-*/": # handle simple operators
62 63 yield (c, None, pos)
63 64 elif c in '"\'': # handle quoted templates
64 65 s = pos + 1
65 66 data, pos = _parsetemplate(program, s, end, c)
66 67 yield ('template', data, s)
67 68 pos -= 1
68 69 elif c == 'r' and program[pos:pos + 2] in ("r'", 'r"'):
69 70 # handle quoted strings
70 71 c = program[pos + 1]
71 72 s = pos = pos + 2
72 73 while pos < end: # find closing quote
73 74 d = program[pos]
74 75 if d == '\\': # skip over escaped characters
75 76 pos += 2
76 77 continue
77 78 if d == c:
78 79 yield ('string', program[s:pos], s)
79 80 break
80 81 pos += 1
81 82 else:
82 83 raise error.ParseError(_("unterminated string"), s)
83 84 elif c.isdigit():
84 85 s = pos
85 86 while pos < end:
86 87 d = program[pos]
87 88 if not d.isdigit():
88 89 break
89 90 pos += 1
90 91 yield ('integer', program[s:pos], s)
91 92 pos -= 1
92 93 elif (c == '\\' and program[pos:pos + 2] in (r"\'", r'\"')
93 94 or c == 'r' and program[pos:pos + 3] in (r"r\'", r'r\"')):
94 95 # handle escaped quoted strings for compatibility with 2.9.2-3.4,
95 96 # where some of nested templates were preprocessed as strings and
96 97 # then compiled. therefore, \"...\" was allowed. (issue4733)
97 98 #
98 99 # processing flow of _evalifliteral() at 5ab28a2e9962:
99 100 # outer template string -> stringify() -> compiletemplate()
100 101 # ------------------------ ------------ ------------------
101 102 # {f("\\\\ {g(\"\\\"\")}"} \\ {g("\"")} [r'\\', {g("\"")}]
102 103 # ~~~~~~~~
103 104 # escaped quoted string
104 105 if c == 'r':
105 106 pos += 1
106 107 token = 'string'
107 108 else:
108 109 token = 'template'
109 110 quote = program[pos:pos + 2]
110 111 s = pos = pos + 2
111 112 while pos < end: # find closing escaped quote
112 113 if program.startswith('\\\\\\', pos, end):
113 114 pos += 4 # skip over double escaped characters
114 115 continue
115 116 if program.startswith(quote, pos, end):
116 117 # interpret as if it were a part of an outer string
117 118 data = parser.unescapestr(program[s:pos])
118 119 if token == 'template':
119 120 data = _parsetemplate(data, 0, len(data))[0]
120 121 yield (token, data, s)
121 122 pos += 1
122 123 break
123 124 pos += 1
124 125 else:
125 126 raise error.ParseError(_("unterminated string"), s)
126 127 elif c.isalnum() or c in '_':
127 128 s = pos
128 129 pos += 1
129 130 while pos < end: # find end of symbol
130 131 d = program[pos]
131 132 if not (d.isalnum() or d == "_"):
132 133 break
133 134 pos += 1
134 135 sym = program[s:pos]
135 136 yield ('symbol', sym, s)
136 137 pos -= 1
137 138 elif c == term:
138 139 yield ('end', None, pos + 1)
139 140 return
140 141 else:
141 142 raise error.ParseError(_("syntax error"), pos)
142 143 pos += 1
143 144 if term:
144 145 raise error.ParseError(_("unterminated template expansion"), start)
145 146 yield ('end', None, pos)
146 147
147 148 def _parsetemplate(tmpl, start, stop, quote=''):
148 149 r"""
149 150 >>> _parsetemplate('foo{bar}"baz', 0, 12)
150 151 ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
151 152 >>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"')
152 153 ([('string', 'foo'), ('symbol', 'bar')], 9)
153 154 >>> _parsetemplate('foo"{bar}', 0, 9, quote='"')
154 155 ([('string', 'foo')], 4)
155 156 >>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"')
156 157 ([('string', 'foo"'), ('string', 'bar')], 9)
157 158 >>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"')
158 159 ([('string', 'foo\\')], 6)
159 160 """
160 161 parsed = []
161 162 sepchars = '{' + quote
162 163 pos = start
163 164 p = parser.parser(elements)
164 165 while pos < stop:
165 166 n = min((tmpl.find(c, pos, stop) for c in sepchars),
166 167 key=lambda n: (n < 0, n))
167 168 if n < 0:
168 169 parsed.append(('string', parser.unescapestr(tmpl[pos:stop])))
169 170 pos = stop
170 171 break
171 172 c = tmpl[n]
172 173 bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
173 174 if bs % 2 == 1:
174 175 # escaped (e.g. '\{', '\\\{', but not '\\{')
175 176 parsed.append(('string', parser.unescapestr(tmpl[pos:n - 1]) + c))
176 177 pos = n + 1
177 178 continue
178 179 if n > pos:
179 180 parsed.append(('string', parser.unescapestr(tmpl[pos:n])))
180 181 if c == quote:
181 182 return parsed, n + 1
182 183
183 184 parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}'))
184 185 parsed.append(parseres)
185 186
186 187 if quote:
187 188 raise error.ParseError(_("unterminated string"), start)
188 189 return parsed, pos
189 190
190 191 def _unnesttemplatelist(tree):
191 192 """Expand list of templates to node tuple
192 193
193 194 >>> def f(tree):
194 195 ... print prettyformat(_unnesttemplatelist(tree))
195 196 >>> f(('template', []))
196 197 ('string', '')
197 198 >>> f(('template', [('string', 'foo')]))
198 199 ('string', 'foo')
199 200 >>> f(('template', [('string', 'foo'), ('symbol', 'rev')]))
200 201 (template
201 202 ('string', 'foo')
202 203 ('symbol', 'rev'))
203 204 >>> f(('template', [('symbol', 'rev')])) # template(rev) -> str
204 205 (template
205 206 ('symbol', 'rev'))
206 207 >>> f(('template', [('template', [('string', 'foo')])]))
207 208 ('string', 'foo')
208 209 """
209 210 if not isinstance(tree, tuple):
210 211 return tree
211 212 op = tree[0]
212 213 if op != 'template':
213 214 return (op,) + tuple(_unnesttemplatelist(x) for x in tree[1:])
214 215
215 216 assert len(tree) == 2
216 217 xs = tuple(_unnesttemplatelist(x) for x in tree[1])
217 218 if not xs:
218 219 return ('string', '') # empty template ""
219 220 elif len(xs) == 1 and xs[0][0] == 'string':
220 221 return xs[0] # fast path for string with no template fragment "x"
221 222 else:
222 223 return (op,) + xs
223 224
224 225 def parse(tmpl):
225 226 """Parse template string into tree"""
226 227 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl))
227 228 assert pos == len(tmpl), 'unquoted template should be consumed'
228 229 return _unnesttemplatelist(('template', parsed))
229 230
230 231 def _parseexpr(expr):
231 232 """Parse a template expression into tree
232 233
233 234 >>> _parseexpr('"foo"')
234 235 ('string', 'foo')
235 236 >>> _parseexpr('foo(bar)')
236 237 ('func', ('symbol', 'foo'), ('symbol', 'bar'))
237 238 >>> _parseexpr('foo(')
238 239 Traceback (most recent call last):
239 240 ...
240 241 ParseError: ('not a prefix: end', 4)
241 242 >>> _parseexpr('"foo" "bar"')
242 243 Traceback (most recent call last):
243 244 ...
244 245 ParseError: ('invalid token', 7)
245 246 """
246 247 p = parser.parser(elements)
247 248 tree, pos = p.parse(tokenize(expr, 0, len(expr)))
248 249 if pos != len(expr):
249 250 raise error.ParseError(_('invalid token'), pos)
250 251 return _unnesttemplatelist(tree)
251 252
252 253 def prettyformat(tree):
253 254 return parser.prettyformat(tree, ('integer', 'string', 'symbol'))
254 255
255 256 def compileexp(exp, context, curmethods):
256 257 """Compile parsed template tree to (func, data) pair"""
257 258 t = exp[0]
258 259 if t in curmethods:
259 260 return curmethods[t](exp, context)
260 261 raise error.ParseError(_("unknown method '%s'") % t)
261 262
262 263 # template evaluation
263 264
264 265 def getsymbol(exp):
265 266 if exp[0] == 'symbol':
266 267 return exp[1]
267 268 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
268 269
269 270 def getlist(x):
270 271 if not x:
271 272 return []
272 273 if x[0] == 'list':
273 274 return getlist(x[1]) + [x[2]]
274 275 return [x]
275 276
276 277 def gettemplate(exp, context):
277 278 """Compile given template tree or load named template from map file;
278 279 returns (func, data) pair"""
279 280 if exp[0] in ('template', 'string'):
280 281 return compileexp(exp, context, methods)
281 282 if exp[0] == 'symbol':
282 283 # unlike runsymbol(), here 'symbol' is always taken as template name
283 284 # even if it exists in mapping. this allows us to override mapping
284 285 # by web templates, e.g. 'changelogtag' is redefined in map file.
285 286 return context._load(exp[1])
286 287 raise error.ParseError(_("expected template specifier"))
287 288
288 289 def findsymbolicname(arg):
289 290 """Find symbolic name for the given compiled expression; returns None
290 291 if nothing found reliably"""
291 292 while True:
292 293 func, data = arg
293 294 if func is runsymbol:
294 295 return data
295 296 elif func is runfilter:
296 297 arg = data[0]
297 298 else:
298 299 return None
299 300
300 301 def evalfuncarg(context, mapping, arg):
301 302 func, data = arg
302 303 # func() may return string, generator of strings or arbitrary object such
303 304 # as date tuple, but filter does not want generator.
304 305 thing = func(context, mapping, data)
305 306 if isinstance(thing, types.GeneratorType):
306 307 thing = stringify(thing)
307 308 return thing
308 309
309 310 def evalboolean(context, mapping, arg):
310 311 """Evaluate given argument as boolean, but also takes boolean literals"""
311 312 func, data = arg
312 313 if func is runsymbol:
313 314 thing = func(context, mapping, data, default=None)
314 315 if thing is None:
315 316 # not a template keyword, takes as a boolean literal
316 317 thing = util.parsebool(data)
317 318 else:
318 319 thing = func(context, mapping, data)
319 320 if isinstance(thing, bool):
320 321 return thing
321 322 # other objects are evaluated as strings, which means 0 is True, but
322 323 # empty dict/list should be False as they are expected to be ''
323 324 return bool(stringify(thing))
324 325
325 326 def evalinteger(context, mapping, arg, err):
326 327 v = evalfuncarg(context, mapping, arg)
327 328 try:
328 329 return int(v)
329 330 except (TypeError, ValueError):
330 331 raise error.ParseError(err)
331 332
332 333 def evalstring(context, mapping, arg):
333 334 func, data = arg
334 335 return stringify(func(context, mapping, data))
335 336
336 337 def evalstringliteral(context, mapping, arg):
337 338 """Evaluate given argument as string template, but returns symbol name
338 339 if it is unknown"""
339 340 func, data = arg
340 341 if func is runsymbol:
341 342 thing = func(context, mapping, data, default=data)
342 343 else:
343 344 thing = func(context, mapping, data)
344 345 return stringify(thing)
345 346
346 347 def runinteger(context, mapping, data):
347 348 return int(data)
348 349
349 350 def runstring(context, mapping, data):
350 351 return data
351 352
352 353 def _recursivesymbolblocker(key):
353 354 def showrecursion(**args):
354 355 raise error.Abort(_("recursive reference '%s' in template") % key)
355 356 return showrecursion
356 357
357 358 def _runrecursivesymbol(context, mapping, key):
358 359 raise error.Abort(_("recursive reference '%s' in template") % key)
359 360
360 361 def runsymbol(context, mapping, key, default=''):
361 362 v = mapping.get(key)
362 363 if v is None:
363 364 v = context._defaults.get(key)
364 365 if v is None:
365 366 # put poison to cut recursion. we can't move this to parsing phase
366 367 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
367 368 safemapping = mapping.copy()
368 369 safemapping[key] = _recursivesymbolblocker(key)
369 370 try:
370 371 v = context.process(key, safemapping)
371 372 except TemplateNotFound:
372 373 v = default
373 374 if callable(v):
374 375 return v(**pycompat.strkwargs(mapping))
375 376 return v
376 377
377 378 def buildtemplate(exp, context):
378 379 ctmpl = [compileexp(e, context, methods) for e in exp[1:]]
379 380 return (runtemplate, ctmpl)
380 381
381 382 def runtemplate(context, mapping, template):
382 383 for func, data in template:
383 384 yield func(context, mapping, data)
384 385
385 386 def buildfilter(exp, context):
386 387 n = getsymbol(exp[2])
387 388 if n in context._filters:
388 389 filt = context._filters[n]
389 390 arg = compileexp(exp[1], context, methods)
390 391 return (runfilter, (arg, filt))
391 392 if n in funcs:
392 393 f = funcs[n]
393 394 args = _buildfuncargs(exp[1], context, methods, n, f._argspec)
394 395 return (f, args)
395 396 raise error.ParseError(_("unknown function '%s'") % n)
396 397
397 398 def runfilter(context, mapping, data):
398 399 arg, filt = data
399 400 thing = evalfuncarg(context, mapping, arg)
400 401 try:
401 402 return filt(thing)
402 403 except (ValueError, AttributeError, TypeError):
403 404 sym = findsymbolicname(arg)
404 405 if sym:
405 406 msg = (_("template filter '%s' is not compatible with keyword '%s'")
406 407 % (filt.func_name, sym))
407 408 else:
408 409 msg = _("incompatible use of template filter '%s'") % filt.func_name
409 410 raise error.Abort(msg)
410 411
411 412 def buildmap(exp, context):
412 413 func, data = compileexp(exp[1], context, methods)
413 414 tfunc, tdata = gettemplate(exp[2], context)
414 415 return (runmap, (func, data, tfunc, tdata))
415 416
416 417 def runmap(context, mapping, data):
417 418 func, data, tfunc, tdata = data
418 419 d = func(context, mapping, data)
419 420 if util.safehasattr(d, 'itermaps'):
420 421 diter = d.itermaps()
421 422 else:
422 423 try:
423 424 diter = iter(d)
424 425 except TypeError:
425 426 if func is runsymbol:
426 427 raise error.ParseError(_("keyword '%s' is not iterable") % data)
427 428 else:
428 429 raise error.ParseError(_("%r is not iterable") % d)
429 430
430 431 for i, v in enumerate(diter):
431 432 lm = mapping.copy()
432 433 lm['index'] = i
433 434 if isinstance(v, dict):
434 435 lm.update(v)
435 436 lm['originalnode'] = mapping.get('node')
436 437 yield tfunc(context, lm, tdata)
437 438 else:
438 439 # v is not an iterable of dicts, this happen when 'key'
439 440 # has been fully expanded already and format is useless.
440 441 # If so, return the expanded value.
441 442 yield v
442 443
443 444 def buildnegate(exp, context):
444 445 arg = compileexp(exp[1], context, exprmethods)
445 446 return (runnegate, arg)
446 447
447 448 def runnegate(context, mapping, data):
448 449 data = evalinteger(context, mapping, data,
449 450 _('negation needs an integer argument'))
450 451 return -data
451 452
452 453 def buildarithmetic(exp, context, func):
453 454 left = compileexp(exp[1], context, exprmethods)
454 455 right = compileexp(exp[2], context, exprmethods)
455 456 return (runarithmetic, (func, left, right))
456 457
457 458 def runarithmetic(context, mapping, data):
458 459 func, left, right = data
459 460 left = evalinteger(context, mapping, left,
460 461 _('arithmetic only defined on integers'))
461 462 right = evalinteger(context, mapping, right,
462 463 _('arithmetic only defined on integers'))
463 464 try:
464 465 return func(left, right)
465 466 except ZeroDivisionError:
466 467 raise error.Abort(_('division by zero is not defined'))
467 468
468 469 def buildfunc(exp, context):
469 470 n = getsymbol(exp[1])
470 471 if n in funcs:
471 472 f = funcs[n]
472 473 args = _buildfuncargs(exp[2], context, exprmethods, n, f._argspec)
473 474 return (f, args)
474 475 if n in context._filters:
475 476 args = _buildfuncargs(exp[2], context, exprmethods, n, argspec=None)
476 477 if len(args) != 1:
477 478 raise error.ParseError(_("filter %s expects one argument") % n)
478 479 f = context._filters[n]
479 480 return (runfilter, (args[0], f))
480 481 raise error.ParseError(_("unknown function '%s'") % n)
481 482
482 483 def _buildfuncargs(exp, context, curmethods, funcname, argspec):
483 484 """Compile parsed tree of function arguments into list or dict of
484 485 (func, data) pairs
485 486
486 487 >>> context = engine(lambda t: (runsymbol, t))
487 488 >>> def fargs(expr, argspec):
488 489 ... x = _parseexpr(expr)
489 490 ... n = getsymbol(x[1])
490 491 ... return _buildfuncargs(x[2], context, exprmethods, n, argspec)
491 492 >>> fargs('a(l=1, k=2)', 'k l m').keys()
492 493 ['l', 'k']
493 494 >>> args = fargs('a(opts=1, k=2)', '**opts')
494 495 >>> args.keys(), args['opts'].keys()
495 496 (['opts'], ['opts', 'k'])
496 497 """
497 498 def compiledict(xs):
498 499 return util.sortdict((k, compileexp(x, context, curmethods))
499 500 for k, x in xs.iteritems())
500 501 def compilelist(xs):
501 502 return [compileexp(x, context, curmethods) for x in xs]
502 503
503 504 if not argspec:
504 505 # filter or function with no argspec: return list of positional args
505 506 return compilelist(getlist(exp))
506 507
507 508 # function with argspec: return dict of named args
508 509 _poskeys, varkey, _keys, optkey = argspec = parser.splitargspec(argspec)
509 510 treeargs = parser.buildargsdict(getlist(exp), funcname, argspec,
510 511 keyvaluenode='keyvalue', keynode='symbol')
511 512 compargs = util.sortdict()
512 513 if varkey:
513 514 compargs[varkey] = compilelist(treeargs.pop(varkey))
514 515 if optkey:
515 516 compargs[optkey] = compiledict(treeargs.pop(optkey))
516 517 compargs.update(compiledict(treeargs))
517 518 return compargs
518 519
519 520 def buildkeyvaluepair(exp, content):
520 521 raise error.ParseError(_("can't use a key-value pair in this context"))
521 522
522 523 # dict of template built-in functions
523 524 funcs = {}
524 525
525 526 templatefunc = registrar.templatefunc(funcs)
526 527
527 528 @templatefunc('date(date[, fmt])')
528 529 def date(context, mapping, args):
529 530 """Format a date. See :hg:`help dates` for formatting
530 531 strings. The default is a Unix date format, including the timezone:
531 532 "Mon Sep 04 15:13:13 2006 0700"."""
532 533 if not (1 <= len(args) <= 2):
533 534 # i18n: "date" is a keyword
534 535 raise error.ParseError(_("date expects one or two arguments"))
535 536
536 537 date = evalfuncarg(context, mapping, args[0])
537 538 fmt = None
538 539 if len(args) == 2:
539 540 fmt = evalstring(context, mapping, args[1])
540 541 try:
541 542 if fmt is None:
542 543 return util.datestr(date)
543 544 else:
544 545 return util.datestr(date, fmt)
545 546 except (TypeError, ValueError):
546 547 # i18n: "date" is a keyword
547 548 raise error.ParseError(_("date expects a date information"))
548 549
549 550 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
550 551 def dict_(context, mapping, args):
551 552 """Construct a dict from key-value pairs. A key may be omitted if
552 553 a value expression can provide an unambiguous name."""
553 554 data = util.sortdict()
554 555
555 556 for v in args['args']:
556 557 k = findsymbolicname(v)
557 558 if not k:
558 559 raise error.ParseError(_('dict key cannot be inferred'))
559 560 if k in data or k in args['kwargs']:
560 561 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
561 562 data[k] = evalfuncarg(context, mapping, v)
562 563
563 564 data.update((k, evalfuncarg(context, mapping, v))
564 565 for k, v in args['kwargs'].iteritems())
565 566 return templatekw.hybriddict(data)
566 567
567 568 @templatefunc('diff([includepattern [, excludepattern]])')
568 569 def diff(context, mapping, args):
569 570 """Show a diff, optionally
570 571 specifying files to include or exclude."""
571 572 if len(args) > 2:
572 573 # i18n: "diff" is a keyword
573 574 raise error.ParseError(_("diff expects zero, one, or two arguments"))
574 575
575 576 def getpatterns(i):
576 577 if i < len(args):
577 578 s = evalstring(context, mapping, args[i]).strip()
578 579 if s:
579 580 return [s]
580 581 return []
581 582
582 583 ctx = mapping['ctx']
583 584 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
584 585
585 586 return ''.join(chunks)
586 587
587 588 @templatefunc('files(pattern)')
588 589 def files(context, mapping, args):
589 590 """All files of the current changeset matching the pattern. See
590 591 :hg:`help patterns`."""
591 592 if not len(args) == 1:
592 593 # i18n: "files" is a keyword
593 594 raise error.ParseError(_("files expects one argument"))
594 595
595 596 raw = evalstring(context, mapping, args[0])
596 597 ctx = mapping['ctx']
597 598 m = ctx.match([raw])
598 599 files = list(ctx.matches(m))
599 600 return templatekw.showlist("file", files, mapping)
600 601
601 602 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
602 603 def fill(context, mapping, args):
603 604 """Fill many
604 605 paragraphs with optional indentation. See the "fill" filter."""
605 606 if not (1 <= len(args) <= 4):
606 607 # i18n: "fill" is a keyword
607 608 raise error.ParseError(_("fill expects one to four arguments"))
608 609
609 610 text = evalstring(context, mapping, args[0])
610 611 width = 76
611 612 initindent = ''
612 613 hangindent = ''
613 614 if 2 <= len(args) <= 4:
614 615 width = evalinteger(context, mapping, args[1],
615 616 # i18n: "fill" is a keyword
616 617 _("fill expects an integer width"))
617 618 try:
618 619 initindent = evalstring(context, mapping, args[2])
619 620 hangindent = evalstring(context, mapping, args[3])
620 621 except IndexError:
621 622 pass
622 623
623 624 return templatefilters.fill(text, width, initindent, hangindent)
624 625
625 626 @templatefunc('formatnode(node)')
626 627 def formatnode(context, mapping, args):
627 628 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
628 629 if len(args) != 1:
629 630 # i18n: "formatnode" is a keyword
630 631 raise error.ParseError(_("formatnode expects one argument"))
631 632
632 633 ui = mapping['ui']
633 634 node = evalstring(context, mapping, args[0])
634 635 if ui.debugflag:
635 636 return node
636 637 return templatefilters.short(node)
637 638
638 639 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
639 640 argspec='text width fillchar left')
640 641 def pad(context, mapping, args):
641 642 """Pad text with a
642 643 fill character."""
643 644 if 'text' not in args or 'width' not in args:
644 645 # i18n: "pad" is a keyword
645 646 raise error.ParseError(_("pad() expects two to four arguments"))
646 647
647 648 width = evalinteger(context, mapping, args['width'],
648 649 # i18n: "pad" is a keyword
649 650 _("pad() expects an integer width"))
650 651
651 652 text = evalstring(context, mapping, args['text'])
652 653
653 654 left = False
654 655 fillchar = ' '
655 656 if 'fillchar' in args:
656 657 fillchar = evalstring(context, mapping, args['fillchar'])
657 658 if len(color.stripeffects(fillchar)) != 1:
658 659 # i18n: "pad" is a keyword
659 660 raise error.ParseError(_("pad() expects a single fill character"))
660 661 if 'left' in args:
661 662 left = evalboolean(context, mapping, args['left'])
662 663
663 664 fillwidth = width - encoding.colwidth(color.stripeffects(text))
664 665 if fillwidth <= 0:
665 666 return text
666 667 if left:
667 668 return fillchar * fillwidth + text
668 669 else:
669 670 return text + fillchar * fillwidth
670 671
671 672 @templatefunc('indent(text, indentchars[, firstline])')
672 673 def indent(context, mapping, args):
673 674 """Indents all non-empty lines
674 675 with the characters given in the indentchars string. An optional
675 676 third parameter will override the indent for the first line only
676 677 if present."""
677 678 if not (2 <= len(args) <= 3):
678 679 # i18n: "indent" is a keyword
679 680 raise error.ParseError(_("indent() expects two or three arguments"))
680 681
681 682 text = evalstring(context, mapping, args[0])
682 683 indent = evalstring(context, mapping, args[1])
683 684
684 685 if len(args) == 3:
685 686 firstline = evalstring(context, mapping, args[2])
686 687 else:
687 688 firstline = indent
688 689
689 690 # the indent function doesn't indent the first line, so we do it here
690 691 return templatefilters.indent(firstline + text, indent)
691 692
692 693 @templatefunc('get(dict, key)')
693 694 def get(context, mapping, args):
694 695 """Get an attribute/key from an object. Some keywords
695 696 are complex types. This function allows you to obtain the value of an
696 697 attribute on these types."""
697 698 if len(args) != 2:
698 699 # i18n: "get" is a keyword
699 700 raise error.ParseError(_("get() expects two arguments"))
700 701
701 702 dictarg = evalfuncarg(context, mapping, args[0])
702 703 if not util.safehasattr(dictarg, 'get'):
703 704 # i18n: "get" is a keyword
704 705 raise error.ParseError(_("get() expects a dict as first argument"))
705 706
706 707 key = evalfuncarg(context, mapping, args[1])
707 708 return dictarg.get(key)
708 709
709 710 @templatefunc('if(expr, then[, else])')
710 711 def if_(context, mapping, args):
711 712 """Conditionally execute based on the result of
712 713 an expression."""
713 714 if not (2 <= len(args) <= 3):
714 715 # i18n: "if" is a keyword
715 716 raise error.ParseError(_("if expects two or three arguments"))
716 717
717 718 test = evalboolean(context, mapping, args[0])
718 719 if test:
719 720 yield args[1][0](context, mapping, args[1][1])
720 721 elif len(args) == 3:
721 722 yield args[2][0](context, mapping, args[2][1])
722 723
723 724 @templatefunc('ifcontains(needle, haystack, then[, else])')
724 725 def ifcontains(context, mapping, args):
725 726 """Conditionally execute based
726 727 on whether the item "needle" is in "haystack"."""
727 728 if not (3 <= len(args) <= 4):
728 729 # i18n: "ifcontains" is a keyword
729 730 raise error.ParseError(_("ifcontains expects three or four arguments"))
730 731
731 732 needle = evalstring(context, mapping, args[0])
732 733 haystack = evalfuncarg(context, mapping, args[1])
733 734
734 735 if needle in haystack:
735 736 yield args[2][0](context, mapping, args[2][1])
736 737 elif len(args) == 4:
737 738 yield args[3][0](context, mapping, args[3][1])
738 739
739 740 @templatefunc('ifeq(expr1, expr2, then[, else])')
740 741 def ifeq(context, mapping, args):
741 742 """Conditionally execute based on
742 743 whether 2 items are equivalent."""
743 744 if not (3 <= len(args) <= 4):
744 745 # i18n: "ifeq" is a keyword
745 746 raise error.ParseError(_("ifeq expects three or four arguments"))
746 747
747 748 test = evalstring(context, mapping, args[0])
748 749 match = evalstring(context, mapping, args[1])
749 750 if test == match:
750 751 yield args[2][0](context, mapping, args[2][1])
751 752 elif len(args) == 4:
752 753 yield args[3][0](context, mapping, args[3][1])
753 754
754 755 @templatefunc('join(list, sep)')
755 756 def join(context, mapping, args):
756 757 """Join items in a list with a delimiter."""
757 758 if not (1 <= len(args) <= 2):
758 759 # i18n: "join" is a keyword
759 760 raise error.ParseError(_("join expects one or two arguments"))
760 761
761 762 joinset = args[0][0](context, mapping, args[0][1])
762 763 if util.safehasattr(joinset, 'itermaps'):
763 764 jf = joinset.joinfmt
764 765 joinset = [jf(x) for x in joinset.itermaps()]
765 766
766 767 joiner = " "
767 768 if len(args) > 1:
768 769 joiner = evalstring(context, mapping, args[1])
769 770
770 771 first = True
771 772 for x in joinset:
772 773 if first:
773 774 first = False
774 775 else:
775 776 yield joiner
776 777 yield x
777 778
778 779 @templatefunc('label(label, expr)')
779 780 def label(context, mapping, args):
780 781 """Apply a label to generated content. Content with
781 782 a label applied can result in additional post-processing, such as
782 783 automatic colorization."""
783 784 if len(args) != 2:
784 785 # i18n: "label" is a keyword
785 786 raise error.ParseError(_("label expects two arguments"))
786 787
787 788 ui = mapping['ui']
788 789 thing = evalstring(context, mapping, args[1])
789 790 # preserve unknown symbol as literal so effects like 'red', 'bold',
790 791 # etc. don't need to be quoted
791 792 label = evalstringliteral(context, mapping, args[0])
792 793
793 794 return ui.label(thing, label)
794 795
795 796 @templatefunc('latesttag([pattern])')
796 797 def latesttag(context, mapping, args):
797 798 """The global tags matching the given pattern on the
798 799 most recent globally tagged ancestor of this changeset.
799 800 If no such tags exist, the "{tag}" template resolves to
800 801 the string "null"."""
801 802 if len(args) > 1:
802 803 # i18n: "latesttag" is a keyword
803 804 raise error.ParseError(_("latesttag expects at most one argument"))
804 805
805 806 pattern = None
806 807 if len(args) == 1:
807 808 pattern = evalstring(context, mapping, args[0])
808 809
809 810 return templatekw.showlatesttags(pattern, **mapping)
810 811
811 812 @templatefunc('localdate(date[, tz])')
812 813 def localdate(context, mapping, args):
813 814 """Converts a date to the specified timezone.
814 815 The default is local date."""
815 816 if not (1 <= len(args) <= 2):
816 817 # i18n: "localdate" is a keyword
817 818 raise error.ParseError(_("localdate expects one or two arguments"))
818 819
819 820 date = evalfuncarg(context, mapping, args[0])
820 821 try:
821 822 date = util.parsedate(date)
822 823 except AttributeError: # not str nor date tuple
823 824 # i18n: "localdate" is a keyword
824 825 raise error.ParseError(_("localdate expects a date information"))
825 826 if len(args) >= 2:
826 827 tzoffset = None
827 828 tz = evalfuncarg(context, mapping, args[1])
828 829 if isinstance(tz, str):
829 830 tzoffset, remainder = util.parsetimezone(tz)
830 831 if remainder:
831 832 tzoffset = None
832 833 if tzoffset is None:
833 834 try:
834 835 tzoffset = int(tz)
835 836 except (TypeError, ValueError):
836 837 # i18n: "localdate" is a keyword
837 838 raise error.ParseError(_("localdate expects a timezone"))
838 839 else:
839 840 tzoffset = util.makedate()[1]
840 841 return (date[0], tzoffset)
841 842
842 843 @templatefunc('mod(a, b)')
843 844 def mod(context, mapping, args):
844 845 """Calculate a mod b such that a / b + a mod b == a"""
845 846 if not len(args) == 2:
846 847 # i18n: "mod" is a keyword
847 848 raise error.ParseError(_("mod expects two arguments"))
848 849
849 850 func = lambda a, b: a % b
850 851 return runarithmetic(context, mapping, (func, args[0], args[1]))
851 852
853 @templatefunc('obsfateverb(successors)')
854 def obsfateverb(context, mapping, args):
855 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
856 if len(args) != 1:
857 # i18n: "obsfateverb" is a keyword
858 raise error.ParseError(_("obsfateverb expects one arguments"))
859
860 successors = evalfuncarg(context, mapping, args[0])
861
862 try:
863 return obsutil.successorsetverb(successors)
864 except TypeError:
865 # i18n: "obsfateverb" is a keyword
866 errmsg = _("obsfateverb first argument should be countable")
867 raise error.ParseError(errmsg)
868
852 869 @templatefunc('relpath(path)')
853 870 def relpath(context, mapping, args):
854 871 """Convert a repository-absolute path into a filesystem path relative to
855 872 the current working directory."""
856 873 if len(args) != 1:
857 874 # i18n: "relpath" is a keyword
858 875 raise error.ParseError(_("relpath expects one argument"))
859 876
860 877 repo = mapping['ctx'].repo()
861 878 path = evalstring(context, mapping, args[0])
862 879 return repo.pathto(path)
863 880
864 881 @templatefunc('revset(query[, formatargs...])')
865 882 def revset(context, mapping, args):
866 883 """Execute a revision set query. See
867 884 :hg:`help revset`."""
868 885 if not len(args) > 0:
869 886 # i18n: "revset" is a keyword
870 887 raise error.ParseError(_("revset expects one or more arguments"))
871 888
872 889 raw = evalstring(context, mapping, args[0])
873 890 ctx = mapping['ctx']
874 891 repo = ctx.repo()
875 892
876 893 def query(expr):
877 894 m = revsetmod.match(repo.ui, expr, repo=repo)
878 895 return m(repo)
879 896
880 897 if len(args) > 1:
881 898 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
882 899 revs = query(revsetlang.formatspec(raw, *formatargs))
883 900 revs = list(revs)
884 901 else:
885 902 revsetcache = mapping['cache'].setdefault("revsetcache", {})
886 903 if raw in revsetcache:
887 904 revs = revsetcache[raw]
888 905 else:
889 906 revs = query(raw)
890 907 revs = list(revs)
891 908 revsetcache[raw] = revs
892 909
893 910 return templatekw.showrevslist("revision", revs, **mapping)
894 911
895 912 @templatefunc('rstdoc(text, style)')
896 913 def rstdoc(context, mapping, args):
897 914 """Format reStructuredText."""
898 915 if len(args) != 2:
899 916 # i18n: "rstdoc" is a keyword
900 917 raise error.ParseError(_("rstdoc expects two arguments"))
901 918
902 919 text = evalstring(context, mapping, args[0])
903 920 style = evalstring(context, mapping, args[1])
904 921
905 922 return minirst.format(text, style=style, keep=['verbose'])
906 923
907 924 @templatefunc('separate(sep, args)', argspec='sep *args')
908 925 def separate(context, mapping, args):
909 926 """Add a separator between non-empty arguments."""
910 927 if 'sep' not in args:
911 928 # i18n: "separate" is a keyword
912 929 raise error.ParseError(_("separate expects at least one argument"))
913 930
914 931 sep = evalstring(context, mapping, args['sep'])
915 932 first = True
916 933 for arg in args['args']:
917 934 argstr = evalstring(context, mapping, arg)
918 935 if not argstr:
919 936 continue
920 937 if first:
921 938 first = False
922 939 else:
923 940 yield sep
924 941 yield argstr
925 942
926 943 @templatefunc('shortest(node, minlength=4)')
927 944 def shortest(context, mapping, args):
928 945 """Obtain the shortest representation of
929 946 a node."""
930 947 if not (1 <= len(args) <= 2):
931 948 # i18n: "shortest" is a keyword
932 949 raise error.ParseError(_("shortest() expects one or two arguments"))
933 950
934 951 node = evalstring(context, mapping, args[0])
935 952
936 953 minlength = 4
937 954 if len(args) > 1:
938 955 minlength = evalinteger(context, mapping, args[1],
939 956 # i18n: "shortest" is a keyword
940 957 _("shortest() expects an integer minlength"))
941 958
942 959 # _partialmatch() of filtered changelog could take O(len(repo)) time,
943 960 # which would be unacceptably slow. so we look for hash collision in
944 961 # unfiltered space, which means some hashes may be slightly longer.
945 962 cl = mapping['ctx']._repo.unfiltered().changelog
946 963 def isvalid(test):
947 964 try:
948 965 if cl._partialmatch(test) is None:
949 966 return False
950 967
951 968 try:
952 969 i = int(test)
953 970 # if we are a pure int, then starting with zero will not be
954 971 # confused as a rev; or, obviously, if the int is larger than
955 972 # the value of the tip rev
956 973 if test[0] == '0' or i > len(cl):
957 974 return True
958 975 return False
959 976 except ValueError:
960 977 return True
961 978 except error.RevlogError:
962 979 return False
963 980 except error.WdirUnsupported:
964 981 # single 'ff...' match
965 982 return True
966 983
967 984 shortest = node
968 985 startlength = max(6, minlength)
969 986 length = startlength
970 987 while True:
971 988 test = node[:length]
972 989 if isvalid(test):
973 990 shortest = test
974 991 if length == minlength or length > startlength:
975 992 return shortest
976 993 length -= 1
977 994 else:
978 995 length += 1
979 996 if len(shortest) <= length:
980 997 return shortest
981 998
982 999 @templatefunc('strip(text[, chars])')
983 1000 def strip(context, mapping, args):
984 1001 """Strip characters from a string. By default,
985 1002 strips all leading and trailing whitespace."""
986 1003 if not (1 <= len(args) <= 2):
987 1004 # i18n: "strip" is a keyword
988 1005 raise error.ParseError(_("strip expects one or two arguments"))
989 1006
990 1007 text = evalstring(context, mapping, args[0])
991 1008 if len(args) == 2:
992 1009 chars = evalstring(context, mapping, args[1])
993 1010 return text.strip(chars)
994 1011 return text.strip()
995 1012
996 1013 @templatefunc('sub(pattern, replacement, expression)')
997 1014 def sub(context, mapping, args):
998 1015 """Perform text substitution
999 1016 using regular expressions."""
1000 1017 if len(args) != 3:
1001 1018 # i18n: "sub" is a keyword
1002 1019 raise error.ParseError(_("sub expects three arguments"))
1003 1020
1004 1021 pat = evalstring(context, mapping, args[0])
1005 1022 rpl = evalstring(context, mapping, args[1])
1006 1023 src = evalstring(context, mapping, args[2])
1007 1024 try:
1008 1025 patre = re.compile(pat)
1009 1026 except re.error:
1010 1027 # i18n: "sub" is a keyword
1011 1028 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
1012 1029 try:
1013 1030 yield patre.sub(rpl, src)
1014 1031 except re.error:
1015 1032 # i18n: "sub" is a keyword
1016 1033 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
1017 1034
1018 1035 @templatefunc('startswith(pattern, text)')
1019 1036 def startswith(context, mapping, args):
1020 1037 """Returns the value from the "text" argument
1021 1038 if it begins with the content from the "pattern" argument."""
1022 1039 if len(args) != 2:
1023 1040 # i18n: "startswith" is a keyword
1024 1041 raise error.ParseError(_("startswith expects two arguments"))
1025 1042
1026 1043 patn = evalstring(context, mapping, args[0])
1027 1044 text = evalstring(context, mapping, args[1])
1028 1045 if text.startswith(patn):
1029 1046 return text
1030 1047 return ''
1031 1048
1032 1049 @templatefunc('word(number, text[, separator])')
1033 1050 def word(context, mapping, args):
1034 1051 """Return the nth word from a string."""
1035 1052 if not (2 <= len(args) <= 3):
1036 1053 # i18n: "word" is a keyword
1037 1054 raise error.ParseError(_("word expects two or three arguments, got %d")
1038 1055 % len(args))
1039 1056
1040 1057 num = evalinteger(context, mapping, args[0],
1041 1058 # i18n: "word" is a keyword
1042 1059 _("word expects an integer index"))
1043 1060 text = evalstring(context, mapping, args[1])
1044 1061 if len(args) == 3:
1045 1062 splitter = evalstring(context, mapping, args[2])
1046 1063 else:
1047 1064 splitter = None
1048 1065
1049 1066 tokens = text.split(splitter)
1050 1067 if num >= len(tokens) or num < -len(tokens):
1051 1068 return ''
1052 1069 else:
1053 1070 return tokens[num]
1054 1071
1055 1072 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
1056 1073 exprmethods = {
1057 1074 "integer": lambda e, c: (runinteger, e[1]),
1058 1075 "string": lambda e, c: (runstring, e[1]),
1059 1076 "symbol": lambda e, c: (runsymbol, e[1]),
1060 1077 "template": buildtemplate,
1061 1078 "group": lambda e, c: compileexp(e[1], c, exprmethods),
1062 1079 # ".": buildmember,
1063 1080 "|": buildfilter,
1064 1081 "%": buildmap,
1065 1082 "func": buildfunc,
1066 1083 "keyvalue": buildkeyvaluepair,
1067 1084 "+": lambda e, c: buildarithmetic(e, c, lambda a, b: a + b),
1068 1085 "-": lambda e, c: buildarithmetic(e, c, lambda a, b: a - b),
1069 1086 "negate": buildnegate,
1070 1087 "*": lambda e, c: buildarithmetic(e, c, lambda a, b: a * b),
1071 1088 "/": lambda e, c: buildarithmetic(e, c, lambda a, b: a // b),
1072 1089 }
1073 1090
1074 1091 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
1075 1092 methods = exprmethods.copy()
1076 1093 methods["integer"] = exprmethods["symbol"] # '{1}' as variable
1077 1094
1078 1095 class _aliasrules(parser.basealiasrules):
1079 1096 """Parsing and expansion rule set of template aliases"""
1080 1097 _section = _('template alias')
1081 1098 _parse = staticmethod(_parseexpr)
1082 1099
1083 1100 @staticmethod
1084 1101 def _trygetfunc(tree):
1085 1102 """Return (name, args) if tree is func(...) or ...|filter; otherwise
1086 1103 None"""
1087 1104 if tree[0] == 'func' and tree[1][0] == 'symbol':
1088 1105 return tree[1][1], getlist(tree[2])
1089 1106 if tree[0] == '|' and tree[2][0] == 'symbol':
1090 1107 return tree[2][1], [tree[1]]
1091 1108
1092 1109 def expandaliases(tree, aliases):
1093 1110 """Return new tree of aliases are expanded"""
1094 1111 aliasmap = _aliasrules.buildmap(aliases)
1095 1112 return _aliasrules.expand(aliasmap, tree)
1096 1113
1097 1114 # template engine
1098 1115
1099 1116 stringify = templatefilters.stringify
1100 1117
1101 1118 def _flatten(thing):
1102 1119 '''yield a single stream from a possibly nested set of iterators'''
1103 1120 thing = templatekw.unwraphybrid(thing)
1104 1121 if isinstance(thing, bytes):
1105 1122 yield thing
1106 1123 elif thing is None:
1107 1124 pass
1108 1125 elif not util.safehasattr(thing, '__iter__'):
1109 1126 yield pycompat.bytestr(thing)
1110 1127 else:
1111 1128 for i in thing:
1112 1129 i = templatekw.unwraphybrid(i)
1113 1130 if isinstance(i, bytes):
1114 1131 yield i
1115 1132 elif i is None:
1116 1133 pass
1117 1134 elif not util.safehasattr(i, '__iter__'):
1118 1135 yield pycompat.bytestr(i)
1119 1136 else:
1120 1137 for j in _flatten(i):
1121 1138 yield j
1122 1139
1123 1140 def unquotestring(s):
1124 1141 '''unwrap quotes if any; otherwise returns unmodified string'''
1125 1142 if len(s) < 2 or s[0] not in "'\"" or s[0] != s[-1]:
1126 1143 return s
1127 1144 return s[1:-1]
1128 1145
1129 1146 class engine(object):
1130 1147 '''template expansion engine.
1131 1148
1132 1149 template expansion works like this. a map file contains key=value
1133 1150 pairs. if value is quoted, it is treated as string. otherwise, it
1134 1151 is treated as name of template file.
1135 1152
1136 1153 templater is asked to expand a key in map. it looks up key, and
1137 1154 looks for strings like this: {foo}. it expands {foo} by looking up
1138 1155 foo in map, and substituting it. expansion is recursive: it stops
1139 1156 when there is no more {foo} to replace.
1140 1157
1141 1158 expansion also allows formatting and filtering.
1142 1159
1143 1160 format uses key to expand each item in list. syntax is
1144 1161 {key%format}.
1145 1162
1146 1163 filter uses function to transform value. syntax is
1147 1164 {key|filter1|filter2|...}.'''
1148 1165
1149 1166 def __init__(self, loader, filters=None, defaults=None, aliases=()):
1150 1167 self._loader = loader
1151 1168 if filters is None:
1152 1169 filters = {}
1153 1170 self._filters = filters
1154 1171 if defaults is None:
1155 1172 defaults = {}
1156 1173 self._defaults = defaults
1157 1174 self._aliasmap = _aliasrules.buildmap(aliases)
1158 1175 self._cache = {} # key: (func, data)
1159 1176
1160 1177 def _load(self, t):
1161 1178 '''load, parse, and cache a template'''
1162 1179 if t not in self._cache:
1163 1180 # put poison to cut recursion while compiling 't'
1164 1181 self._cache[t] = (_runrecursivesymbol, t)
1165 1182 try:
1166 1183 x = parse(self._loader(t))
1167 1184 if self._aliasmap:
1168 1185 x = _aliasrules.expand(self._aliasmap, x)
1169 1186 self._cache[t] = compileexp(x, self, methods)
1170 1187 except: # re-raises
1171 1188 del self._cache[t]
1172 1189 raise
1173 1190 return self._cache[t]
1174 1191
1175 1192 def process(self, t, mapping):
1176 1193 '''Perform expansion. t is name of map element to expand.
1177 1194 mapping contains added elements for use during expansion. Is a
1178 1195 generator.'''
1179 1196 func, data = self._load(t)
1180 1197 return _flatten(func(self, mapping, data))
1181 1198
1182 1199 engines = {'default': engine}
1183 1200
1184 1201 def stylelist():
1185 1202 paths = templatepaths()
1186 1203 if not paths:
1187 1204 return _('no templates found, try `hg debuginstall` for more info')
1188 1205 dirlist = os.listdir(paths[0])
1189 1206 stylelist = []
1190 1207 for file in dirlist:
1191 1208 split = file.split(".")
1192 1209 if split[-1] in ('orig', 'rej'):
1193 1210 continue
1194 1211 if split[0] == "map-cmdline":
1195 1212 stylelist.append(split[1])
1196 1213 return ", ".join(sorted(stylelist))
1197 1214
1198 1215 def _readmapfile(mapfile):
1199 1216 """Load template elements from the given map file"""
1200 1217 if not os.path.exists(mapfile):
1201 1218 raise error.Abort(_("style '%s' not found") % mapfile,
1202 1219 hint=_("available styles: %s") % stylelist())
1203 1220
1204 1221 base = os.path.dirname(mapfile)
1205 1222 conf = config.config(includepaths=templatepaths())
1206 1223 conf.read(mapfile)
1207 1224
1208 1225 cache = {}
1209 1226 tmap = {}
1210 1227 for key, val in conf[''].items():
1211 1228 if not val:
1212 1229 raise error.ParseError(_('missing value'), conf.source('', key))
1213 1230 if val[0] in "'\"":
1214 1231 if val[0] != val[-1]:
1215 1232 raise error.ParseError(_('unmatched quotes'),
1216 1233 conf.source('', key))
1217 1234 cache[key] = unquotestring(val)
1218 1235 elif key == "__base__":
1219 1236 # treat as a pointer to a base class for this style
1220 1237 path = util.normpath(os.path.join(base, val))
1221 1238
1222 1239 # fallback check in template paths
1223 1240 if not os.path.exists(path):
1224 1241 for p in templatepaths():
1225 1242 p2 = util.normpath(os.path.join(p, val))
1226 1243 if os.path.isfile(p2):
1227 1244 path = p2
1228 1245 break
1229 1246 p3 = util.normpath(os.path.join(p2, "map"))
1230 1247 if os.path.isfile(p3):
1231 1248 path = p3
1232 1249 break
1233 1250
1234 1251 bcache, btmap = _readmapfile(path)
1235 1252 for k in bcache:
1236 1253 if k not in cache:
1237 1254 cache[k] = bcache[k]
1238 1255 for k in btmap:
1239 1256 if k not in tmap:
1240 1257 tmap[k] = btmap[k]
1241 1258 else:
1242 1259 val = 'default', val
1243 1260 if ':' in val[1]:
1244 1261 val = val[1].split(':', 1)
1245 1262 tmap[key] = val[0], os.path.join(base, val[1])
1246 1263 return cache, tmap
1247 1264
1248 1265 class TemplateNotFound(error.Abort):
1249 1266 pass
1250 1267
1251 1268 class templater(object):
1252 1269
1253 1270 def __init__(self, filters=None, defaults=None, cache=None, aliases=(),
1254 1271 minchunk=1024, maxchunk=65536):
1255 1272 '''set up template engine.
1256 1273 filters is dict of functions. each transforms a value into another.
1257 1274 defaults is dict of default map definitions.
1258 1275 aliases is list of alias (name, replacement) pairs.
1259 1276 '''
1260 1277 if filters is None:
1261 1278 filters = {}
1262 1279 if defaults is None:
1263 1280 defaults = {}
1264 1281 if cache is None:
1265 1282 cache = {}
1266 1283 self.cache = cache.copy()
1267 1284 self.map = {}
1268 1285 self.filters = templatefilters.filters.copy()
1269 1286 self.filters.update(filters)
1270 1287 self.defaults = defaults
1271 1288 self._aliases = aliases
1272 1289 self.minchunk, self.maxchunk = minchunk, maxchunk
1273 1290 self.ecache = {}
1274 1291
1275 1292 @classmethod
1276 1293 def frommapfile(cls, mapfile, filters=None, defaults=None, cache=None,
1277 1294 minchunk=1024, maxchunk=65536):
1278 1295 """Create templater from the specified map file"""
1279 1296 t = cls(filters, defaults, cache, [], minchunk, maxchunk)
1280 1297 cache, tmap = _readmapfile(mapfile)
1281 1298 t.cache.update(cache)
1282 1299 t.map = tmap
1283 1300 return t
1284 1301
1285 1302 def __contains__(self, key):
1286 1303 return key in self.cache or key in self.map
1287 1304
1288 1305 def load(self, t):
1289 1306 '''Get the template for the given template name. Use a local cache.'''
1290 1307 if t not in self.cache:
1291 1308 try:
1292 1309 self.cache[t] = util.readfile(self.map[t][1])
1293 1310 except KeyError as inst:
1294 1311 raise TemplateNotFound(_('"%s" not in template map') %
1295 1312 inst.args[0])
1296 1313 except IOError as inst:
1297 1314 raise IOError(inst.args[0], _('template file %s: %s') %
1298 1315 (self.map[t][1], inst.args[1]))
1299 1316 return self.cache[t]
1300 1317
1301 1318 def render(self, mapping):
1302 1319 """Render the default unnamed template and return result as string"""
1303 1320 return stringify(self('', **mapping))
1304 1321
1305 1322 def __call__(self, t, **mapping):
1306 1323 mapping = pycompat.byteskwargs(mapping)
1307 1324 ttype = t in self.map and self.map[t][0] or 'default'
1308 1325 if ttype not in self.ecache:
1309 1326 try:
1310 1327 ecls = engines[ttype]
1311 1328 except KeyError:
1312 1329 raise error.Abort(_('invalid template engine: %s') % ttype)
1313 1330 self.ecache[ttype] = ecls(self.load, self.filters, self.defaults,
1314 1331 self._aliases)
1315 1332 proc = self.ecache[ttype]
1316 1333
1317 1334 stream = proc.process(t, mapping)
1318 1335 if self.minchunk:
1319 1336 stream = util.increasingchunks(stream, min=self.minchunk,
1320 1337 max=self.maxchunk)
1321 1338 return stream
1322 1339
1323 1340 def templatepaths():
1324 1341 '''return locations used for template files.'''
1325 1342 pathsrel = ['templates']
1326 1343 paths = [os.path.normpath(os.path.join(util.datapath, f))
1327 1344 for f in pathsrel]
1328 1345 return [p for p in paths if os.path.isdir(p)]
1329 1346
1330 1347 def templatepath(name):
1331 1348 '''return location of template file. returns None if not found.'''
1332 1349 for p in templatepaths():
1333 1350 f = os.path.join(p, name)
1334 1351 if os.path.exists(f):
1335 1352 return f
1336 1353 return None
1337 1354
1338 1355 def stylemap(styles, paths=None):
1339 1356 """Return path to mapfile for a given style.
1340 1357
1341 1358 Searches mapfile in the following locations:
1342 1359 1. templatepath/style/map
1343 1360 2. templatepath/map-style
1344 1361 3. templatepath/map
1345 1362 """
1346 1363
1347 1364 if paths is None:
1348 1365 paths = templatepaths()
1349 1366 elif isinstance(paths, str):
1350 1367 paths = [paths]
1351 1368
1352 1369 if isinstance(styles, str):
1353 1370 styles = [styles]
1354 1371
1355 1372 for style in styles:
1356 1373 # only plain name is allowed to honor template paths
1357 1374 if (not style
1358 1375 or style in (os.curdir, os.pardir)
1359 1376 or pycompat.ossep in style
1360 1377 or pycompat.osaltsep and pycompat.osaltsep in style):
1361 1378 continue
1362 1379 locations = [os.path.join(style, 'map'), 'map-' + style]
1363 1380 locations.append('map')
1364 1381
1365 1382 for path in paths:
1366 1383 for location in locations:
1367 1384 mapfile = os.path.join(path, location)
1368 1385 if os.path.isfile(mapfile):
1369 1386 return style, mapfile
1370 1387
1371 1388 raise RuntimeError("No hgweb templates found in %r" % paths)
1372 1389
1373 1390 def loadfunction(ui, extname, registrarobj):
1374 1391 """Load template function from specified registrarobj
1375 1392 """
1376 1393 for name, func in registrarobj._table.iteritems():
1377 1394 funcs[name] = func
1378 1395
1379 1396 # tell hggettext to extract docstrings from these functions:
1380 1397 i18nfunctions = funcs.values()
@@ -1,1643 +1,1644
1 1 This test file test the various templates related to obsmarkers.
2 2
3 3 Global setup
4 4 ============
5 5
6 6 $ . $TESTDIR/testlib/obsmarker-common.sh
7 7 $ cat >> $HGRCPATH <<EOF
8 8 > [ui]
9 9 > interactive = true
10 10 > [phases]
11 11 > publish=False
12 12 > [experimental]
13 13 > stabilization=all
14 14 > [templates]
15 15 > obsfatesuccessors = " as {join(successors, ", ")}"
16 > obsfate = "rewritten{obsfatesuccessors}; "
16 > obsfateverb = "{obsfateverb(successors)}"
17 > obsfate = "{obsfateverb}{obsfatesuccessors}; "
17 18 > [alias]
18 19 > tlog = log -G -T '{node|short}\
19 20 > {if(predecessors, "\n Predecessors: {predecessors}")}\
20 21 > {if(predecessors, "\n semi-colon: {join(predecessors, "; ")}")}\
21 22 > {if(predecessors, "\n json: {predecessors|json}")}\
22 23 > {if(predecessors, "\n map: {join(predecessors % "{rev}:{node}", " ")}")}\
23 24 > {if(successorssets, "\n Successors: {successorssets}")}\
24 25 > {if(successorssets, "\n multi-line: {join(successorssets, "\n multi-line: ")}")}\
25 26 > {if(successorssets, "\n json: {successorssets|json}")}\n'
26 27 > fatelog = log -G -T '{node|short}\n{if(succsandmarkers, " Obsfate: {succsandmarkers % "{obsfate}"} \n" )}'
27 28 > fatelogjson = log -G -T '{node|short}\n{if(succsandmarkers, " Obsfate: {succsandmarkers|json}\n")}'
28 29 > EOF
29 30
30 31 Test templates on amended commit
31 32 ================================
32 33
33 34 Test setup
34 35 ----------
35 36
36 37 $ hg init $TESTTMP/templates-local-amend
37 38 $ cd $TESTTMP/templates-local-amend
38 39 $ mkcommit ROOT
39 40 $ mkcommit A0
40 41 $ echo 42 >> A0
41 42 $ HGUSER=test1 hg commit --amend -m "A1" --config devel.default-date="1234567890 0"
42 43 $ HGUSER=test2 hg commit --amend -m "A2" --config devel.default-date="987654321 0"
43 44
44 45 $ hg log --hidden -G
45 46 @ changeset: 4:d004c8f274b9
46 47 | tag: tip
47 48 | parent: 0:ea207398892e
48 49 | user: test
49 50 | date: Thu Jan 01 00:00:00 1970 +0000
50 51 | summary: A2
51 52 |
52 53 | x changeset: 3:a468dc9b3633
53 54 |/ parent: 0:ea207398892e
54 55 | user: test
55 56 | date: Thu Jan 01 00:00:00 1970 +0000
56 57 | summary: A1
57 58 |
58 59 | x changeset: 2:f137d23bb3e1
59 60 | | user: test
60 61 | | date: Thu Jan 01 00:00:00 1970 +0000
61 62 | | summary: temporary amend commit for 471f378eab4c
62 63 | |
63 64 | x changeset: 1:471f378eab4c
64 65 |/ user: test
65 66 | date: Thu Jan 01 00:00:00 1970 +0000
66 67 | summary: A0
67 68 |
68 69 o changeset: 0:ea207398892e
69 70 user: test
70 71 date: Thu Jan 01 00:00:00 1970 +0000
71 72 summary: ROOT
72 73
73 74 Check templates
74 75 ---------------
75 76 $ hg up 'desc(A0)' --hidden
76 77 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
77 78
78 79 Predecessors template should show current revision as it is the working copy
79 80 $ hg tlog
80 81 o d004c8f274b9
81 82 | Predecessors: 1:471f378eab4c
82 83 | semi-colon: 1:471f378eab4c
83 84 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
84 85 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
85 86 | @ 471f378eab4c
86 87 |/ Successors: 4:d004c8f274b9
87 88 | multi-line: 4:d004c8f274b9
88 89 | json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
89 90 o ea207398892e
90 91
91 92 $ hg fatelog -q --traceback
92 93 o d004c8f274b9
93 94 |
94 95 | @ 471f378eab4c
95 96 |/ Obsfate: rewritten as 4:d004c8f274b9;
96 97 o ea207398892e
97 98
98 99 $ hg fatelog
99 100 o d004c8f274b9
100 101 |
101 102 | @ 471f378eab4c
102 103 |/ Obsfate: rewritten as 4:d004c8f274b9;
103 104 o ea207398892e
104 105
105 106 $ hg fatelog -v
106 107 o d004c8f274b9
107 108 |
108 109 | @ 471f378eab4c
109 110 |/ Obsfate: rewritten as 4:d004c8f274b9;
110 111 o ea207398892e
111 112
112 113 $ hg up 'desc(A1)' --hidden
113 114 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
114 115
115 116 Predecessors template should show current revision as it is the working copy
116 117 $ hg tlog
117 118 o d004c8f274b9
118 119 | Predecessors: 3:a468dc9b3633
119 120 | semi-colon: 3:a468dc9b3633
120 121 | json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
121 122 | map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
122 123 | @ a468dc9b3633
123 124 |/ Successors: 4:d004c8f274b9
124 125 | multi-line: 4:d004c8f274b9
125 126 | json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
126 127 o ea207398892e
127 128
128 129 $ hg fatelog -v
129 130 o d004c8f274b9
130 131 |
131 132 | @ a468dc9b3633
132 133 |/ Obsfate: rewritten as 4:d004c8f274b9;
133 134 o ea207398892e
134 135
135 136 Predecessors template should show all the predecessors as we force their display
136 137 with --hidden
137 138 $ hg tlog --hidden
138 139 o d004c8f274b9
139 140 | Predecessors: 3:a468dc9b3633
140 141 | semi-colon: 3:a468dc9b3633
141 142 | json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
142 143 | map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
143 144 | @ a468dc9b3633
144 145 |/ Predecessors: 1:471f378eab4c
145 146 | semi-colon: 1:471f378eab4c
146 147 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
147 148 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
148 149 | Successors: 4:d004c8f274b9
149 150 | multi-line: 4:d004c8f274b9
150 151 | json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
151 152 | x f137d23bb3e1
152 153 | |
153 154 | x 471f378eab4c
154 155 |/ Successors: 3:a468dc9b3633
155 156 | multi-line: 3:a468dc9b3633
156 157 | json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
157 158 o ea207398892e
158 159
159 160 $ hg fatelog --hidden -q
160 161 o d004c8f274b9
161 162 |
162 163 | @ a468dc9b3633
163 164 |/ Obsfate: rewritten as 4:d004c8f274b9;
164 165 | x f137d23bb3e1
165 166 | |
166 167 | x 471f378eab4c
167 168 |/ Obsfate: rewritten as 3:a468dc9b3633;
168 169 o ea207398892e
169 170
170 171
171 172 Predecessors template shouldn't show anything as all obsolete commit are not
172 173 visible.
173 174 $ hg up 'desc(A2)'
174 175 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
175 176 $ hg tlog
176 177 @ d004c8f274b9
177 178 |
178 179 o ea207398892e
179 180
180 181 $ hg tlog --hidden
181 182 @ d004c8f274b9
182 183 | Predecessors: 3:a468dc9b3633
183 184 | semi-colon: 3:a468dc9b3633
184 185 | json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
185 186 | map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
186 187 | x a468dc9b3633
187 188 |/ Predecessors: 1:471f378eab4c
188 189 | semi-colon: 1:471f378eab4c
189 190 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
190 191 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
191 192 | Successors: 4:d004c8f274b9
192 193 | multi-line: 4:d004c8f274b9
193 194 | json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
194 195 | x f137d23bb3e1
195 196 | |
196 197 | x 471f378eab4c
197 198 |/ Successors: 3:a468dc9b3633
198 199 | multi-line: 3:a468dc9b3633
199 200 | json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
200 201 o ea207398892e
201 202
202 203 $ hg fatelog -v
203 204 @ d004c8f274b9
204 205 |
205 206 o ea207398892e
206 207
207 208
208 209 $ hg fatelog -v --hidden
209 210 @ d004c8f274b9
210 211 |
211 212 | x a468dc9b3633
212 213 |/ Obsfate: rewritten as 4:d004c8f274b9;
213 214 | x f137d23bb3e1
214 215 | |
215 216 | x 471f378eab4c
216 217 |/ Obsfate: rewritten as 3:a468dc9b3633;
217 218 o ea207398892e
218 219
219 220
220 221 $ hg fatelogjson --hidden
221 222 @ d004c8f274b9
222 223 |
223 224 | x a468dc9b3633
224 225 |/ Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
225 226 | x f137d23bb3e1
226 227 | |
227 228 | x 471f378eab4c
228 229 |/ Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["user", "test1"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
229 230 o ea207398892e
230 231
231 232 Test templates with splitted commit
232 233 ===================================
233 234
234 235 $ hg init $TESTTMP/templates-local-split
235 236 $ cd $TESTTMP/templates-local-split
236 237 $ mkcommit ROOT
237 238 $ echo 42 >> a
238 239 $ echo 43 >> b
239 240 $ hg commit -A -m "A0"
240 241 adding a
241 242 adding b
242 243 $ hg log --hidden -G
243 244 @ changeset: 1:471597cad322
244 245 | tag: tip
245 246 | user: test
246 247 | date: Thu Jan 01 00:00:00 1970 +0000
247 248 | summary: A0
248 249 |
249 250 o changeset: 0:ea207398892e
250 251 user: test
251 252 date: Thu Jan 01 00:00:00 1970 +0000
252 253 summary: ROOT
253 254
254 255 # Simulate split
255 256 $ hg up -r "desc(ROOT)"
256 257 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
257 258 $ echo 42 >> a
258 259 $ hg commit -A -m "A0"
259 260 adding a
260 261 created new head
261 262 $ echo 43 >> b
262 263 $ hg commit -A -m "A0"
263 264 adding b
264 265 $ hg debugobsolete `getid "1"` `getid "2"` `getid "3"`
265 266 obsoleted 1 changesets
266 267
267 268 $ hg log --hidden -G
268 269 @ changeset: 3:f257fde29c7a
269 270 | tag: tip
270 271 | user: test
271 272 | date: Thu Jan 01 00:00:00 1970 +0000
272 273 | summary: A0
273 274 |
274 275 o changeset: 2:337fec4d2edc
275 276 | parent: 0:ea207398892e
276 277 | user: test
277 278 | date: Thu Jan 01 00:00:00 1970 +0000
278 279 | summary: A0
279 280 |
280 281 | x changeset: 1:471597cad322
281 282 |/ user: test
282 283 | date: Thu Jan 01 00:00:00 1970 +0000
283 284 | summary: A0
284 285 |
285 286 o changeset: 0:ea207398892e
286 287 user: test
287 288 date: Thu Jan 01 00:00:00 1970 +0000
288 289 summary: ROOT
289 290
290 291 Check templates
291 292 ---------------
292 293
293 294 $ hg up 'obsolete()' --hidden
294 295 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
295 296
296 297 Predecessors template should show current revision as it is the working copy
297 298 $ hg tlog
298 299 o f257fde29c7a
299 300 | Predecessors: 1:471597cad322
300 301 | semi-colon: 1:471597cad322
301 302 | json: ["471597cad322d1f659bb169751be9133dad92ef3"]
302 303 | map: 1:471597cad322d1f659bb169751be9133dad92ef3
303 304 o 337fec4d2edc
304 305 | Predecessors: 1:471597cad322
305 306 | semi-colon: 1:471597cad322
306 307 | json: ["471597cad322d1f659bb169751be9133dad92ef3"]
307 308 | map: 1:471597cad322d1f659bb169751be9133dad92ef3
308 309 | @ 471597cad322
309 310 |/ Successors: 2:337fec4d2edc 3:f257fde29c7a
310 311 | multi-line: 2:337fec4d2edc 3:f257fde29c7a
311 312 | json: [["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]]
312 313 o ea207398892e
313 314
314 315
315 316 $ hg fatelog
316 317 o f257fde29c7a
317 318 |
318 319 o 337fec4d2edc
319 320 |
320 321 | @ 471597cad322
321 |/ Obsfate: rewritten as 2:337fec4d2edc, 3:f257fde29c7a;
322 |/ Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a;
322 323 o ea207398892e
323 324
324 325 $ hg up f257fde29c7a
325 326 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
326 327
327 328 Predecessors template should not show a predecessor as it's not displayed in
328 329 the log
329 330 $ hg tlog
330 331 @ f257fde29c7a
331 332 |
332 333 o 337fec4d2edc
333 334 |
334 335 o ea207398892e
335 336
336 337 Predecessors template should show both predecessors as we force their display
337 338 with --hidden
338 339 $ hg tlog --hidden
339 340 @ f257fde29c7a
340 341 | Predecessors: 1:471597cad322
341 342 | semi-colon: 1:471597cad322
342 343 | json: ["471597cad322d1f659bb169751be9133dad92ef3"]
343 344 | map: 1:471597cad322d1f659bb169751be9133dad92ef3
344 345 o 337fec4d2edc
345 346 | Predecessors: 1:471597cad322
346 347 | semi-colon: 1:471597cad322
347 348 | json: ["471597cad322d1f659bb169751be9133dad92ef3"]
348 349 | map: 1:471597cad322d1f659bb169751be9133dad92ef3
349 350 | x 471597cad322
350 351 |/ Successors: 2:337fec4d2edc 3:f257fde29c7a
351 352 | multi-line: 2:337fec4d2edc 3:f257fde29c7a
352 353 | json: [["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]]
353 354 o ea207398892e
354 355
355 356
356 357 $ hg fatelog --hidden
357 358 @ f257fde29c7a
358 359 |
359 360 o 337fec4d2edc
360 361 |
361 362 | x 471597cad322
362 |/ Obsfate: rewritten as 2:337fec4d2edc, 3:f257fde29c7a;
363 |/ Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a;
363 364 o ea207398892e
364 365
365 366 $ hg fatelogjson --hidden
366 367 @ f257fde29c7a
367 368 |
368 369 o 337fec4d2edc
369 370 |
370 371 | x 471597cad322
371 372 |/ Obsfate: [{"markers": [["471597cad322d1f659bb169751be9133dad92ef3", ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]}]
372 373 o ea207398892e
373 374
374 375 Test templates with folded commit
375 376 =================================
376 377
377 378 Test setup
378 379 ----------
379 380
380 381 $ hg init $TESTTMP/templates-local-fold
381 382 $ cd $TESTTMP/templates-local-fold
382 383 $ mkcommit ROOT
383 384 $ mkcommit A0
384 385 $ mkcommit B0
385 386 $ hg log --hidden -G
386 387 @ changeset: 2:0dec01379d3b
387 388 | tag: tip
388 389 | user: test
389 390 | date: Thu Jan 01 00:00:00 1970 +0000
390 391 | summary: B0
391 392 |
392 393 o changeset: 1:471f378eab4c
393 394 | user: test
394 395 | date: Thu Jan 01 00:00:00 1970 +0000
395 396 | summary: A0
396 397 |
397 398 o changeset: 0:ea207398892e
398 399 user: test
399 400 date: Thu Jan 01 00:00:00 1970 +0000
400 401 summary: ROOT
401 402
402 403 Simulate a fold
403 404 $ hg up -r "desc(ROOT)"
404 405 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
405 406 $ echo "A0" > A0
406 407 $ echo "B0" > B0
407 408 $ hg commit -A -m "C0"
408 409 adding A0
409 410 adding B0
410 411 created new head
411 412 $ hg debugobsolete `getid "desc(A0)"` `getid "desc(C0)"`
412 413 obsoleted 1 changesets
413 414 $ hg debugobsolete `getid "desc(B0)"` `getid "desc(C0)"`
414 415 obsoleted 1 changesets
415 416
416 417 $ hg log --hidden -G
417 418 @ changeset: 3:eb5a0daa2192
418 419 | tag: tip
419 420 | parent: 0:ea207398892e
420 421 | user: test
421 422 | date: Thu Jan 01 00:00:00 1970 +0000
422 423 | summary: C0
423 424 |
424 425 | x changeset: 2:0dec01379d3b
425 426 | | user: test
426 427 | | date: Thu Jan 01 00:00:00 1970 +0000
427 428 | | summary: B0
428 429 | |
429 430 | x changeset: 1:471f378eab4c
430 431 |/ user: test
431 432 | date: Thu Jan 01 00:00:00 1970 +0000
432 433 | summary: A0
433 434 |
434 435 o changeset: 0:ea207398892e
435 436 user: test
436 437 date: Thu Jan 01 00:00:00 1970 +0000
437 438 summary: ROOT
438 439
439 440 Check templates
440 441 ---------------
441 442
442 443 $ hg up 'desc(A0)' --hidden
443 444 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
444 445
445 446 Predecessors template should show current revision as it is the working copy
446 447 $ hg tlog
447 448 o eb5a0daa2192
448 449 | Predecessors: 1:471f378eab4c
449 450 | semi-colon: 1:471f378eab4c
450 451 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
451 452 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
452 453 | @ 471f378eab4c
453 454 |/ Successors: 3:eb5a0daa2192
454 455 | multi-line: 3:eb5a0daa2192
455 456 | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
456 457 o ea207398892e
457 458
458 459
459 460 $ hg fatelog
460 461 o eb5a0daa2192
461 462 |
462 463 | @ 471f378eab4c
463 464 |/ Obsfate: rewritten as 3:eb5a0daa2192;
464 465 o ea207398892e
465 466
466 467 $ hg up 'desc(B0)' --hidden
467 468 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
468 469
469 470 Predecessors template should show both predecessors as they should be both
470 471 displayed
471 472 $ hg tlog
472 473 o eb5a0daa2192
473 474 | Predecessors: 2:0dec01379d3b 1:471f378eab4c
474 475 | semi-colon: 2:0dec01379d3b; 1:471f378eab4c
475 476 | json: ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", "471f378eab4c5e25f6c77f785b27c936efb22874"]
476 477 | map: 2:0dec01379d3be6318c470ead31b1fe7ae7cb53d5 1:471f378eab4c5e25f6c77f785b27c936efb22874
477 478 | @ 0dec01379d3b
478 479 | | Successors: 3:eb5a0daa2192
479 480 | | multi-line: 3:eb5a0daa2192
480 481 | | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
481 482 | x 471f378eab4c
482 483 |/ Successors: 3:eb5a0daa2192
483 484 | multi-line: 3:eb5a0daa2192
484 485 | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
485 486 o ea207398892e
486 487
487 488
488 489 $ hg fatelog
489 490 o eb5a0daa2192
490 491 |
491 492 | @ 0dec01379d3b
492 493 | | Obsfate: rewritten as 3:eb5a0daa2192;
493 494 | x 471f378eab4c
494 495 |/ Obsfate: rewritten as 3:eb5a0daa2192;
495 496 o ea207398892e
496 497
497 498 $ hg up 'desc(C0)'
498 499 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
499 500
500 501 Predecessors template should not show predecessors as they are not displayed in
501 502 the log
502 503 $ hg tlog
503 504 @ eb5a0daa2192
504 505 |
505 506 o ea207398892e
506 507
507 508 Predecessors template should show both predecessors as we force their display
508 509 with --hidden
509 510 $ hg tlog --hidden
510 511 @ eb5a0daa2192
511 512 | Predecessors: 2:0dec01379d3b 1:471f378eab4c
512 513 | semi-colon: 2:0dec01379d3b; 1:471f378eab4c
513 514 | json: ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", "471f378eab4c5e25f6c77f785b27c936efb22874"]
514 515 | map: 2:0dec01379d3be6318c470ead31b1fe7ae7cb53d5 1:471f378eab4c5e25f6c77f785b27c936efb22874
515 516 | x 0dec01379d3b
516 517 | | Successors: 3:eb5a0daa2192
517 518 | | multi-line: 3:eb5a0daa2192
518 519 | | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
519 520 | x 471f378eab4c
520 521 |/ Successors: 3:eb5a0daa2192
521 522 | multi-line: 3:eb5a0daa2192
522 523 | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
523 524 o ea207398892e
524 525
525 526
526 527 $ hg fatelog --hidden
527 528 @ eb5a0daa2192
528 529 |
529 530 | x 0dec01379d3b
530 531 | | Obsfate: rewritten as 3:eb5a0daa2192;
531 532 | x 471f378eab4c
532 533 |/ Obsfate: rewritten as 3:eb5a0daa2192;
533 534 o ea207398892e
534 535
535 536
536 537 $ hg fatelogjson --hidden
537 538 @ eb5a0daa2192
538 539 |
539 540 | x 0dec01379d3b
540 541 | | Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
541 542 | x 471f378eab4c
542 543 |/ Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
543 544 o ea207398892e
544 545
545 546
546 547 Test templates with divergence
547 548 ==============================
548 549
549 550 Test setup
550 551 ----------
551 552
552 553 $ hg init $TESTTMP/templates-local-divergence
553 554 $ cd $TESTTMP/templates-local-divergence
554 555 $ mkcommit ROOT
555 556 $ mkcommit A0
556 557 $ hg commit --amend -m "A1"
557 558 $ hg log --hidden -G
558 559 @ changeset: 2:fdf9bde5129a
559 560 | tag: tip
560 561 | parent: 0:ea207398892e
561 562 | user: test
562 563 | date: Thu Jan 01 00:00:00 1970 +0000
563 564 | summary: A1
564 565 |
565 566 | x changeset: 1:471f378eab4c
566 567 |/ user: test
567 568 | date: Thu Jan 01 00:00:00 1970 +0000
568 569 | summary: A0
569 570 |
570 571 o changeset: 0:ea207398892e
571 572 user: test
572 573 date: Thu Jan 01 00:00:00 1970 +0000
573 574 summary: ROOT
574 575
575 576 $ hg update --hidden 'desc(A0)'
576 577 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
577 578 $ hg commit --amend -m "A2"
578 579 $ hg log --hidden -G
579 580 @ changeset: 3:65b757b745b9
580 581 | tag: tip
581 582 | parent: 0:ea207398892e
582 583 | user: test
583 584 | date: Thu Jan 01 00:00:00 1970 +0000
584 585 | instability: content-divergent
585 586 | summary: A2
586 587 |
587 588 | o changeset: 2:fdf9bde5129a
588 589 |/ parent: 0:ea207398892e
589 590 | user: test
590 591 | date: Thu Jan 01 00:00:00 1970 +0000
591 592 | instability: content-divergent
592 593 | summary: A1
593 594 |
594 595 | x changeset: 1:471f378eab4c
595 596 |/ user: test
596 597 | date: Thu Jan 01 00:00:00 1970 +0000
597 598 | summary: A0
598 599 |
599 600 o changeset: 0:ea207398892e
600 601 user: test
601 602 date: Thu Jan 01 00:00:00 1970 +0000
602 603 summary: ROOT
603 604
604 605 $ hg commit --amend -m 'A3'
605 606 $ hg log --hidden -G
606 607 @ changeset: 4:019fadeab383
607 608 | tag: tip
608 609 | parent: 0:ea207398892e
609 610 | user: test
610 611 | date: Thu Jan 01 00:00:00 1970 +0000
611 612 | instability: content-divergent
612 613 | summary: A3
613 614 |
614 615 | x changeset: 3:65b757b745b9
615 616 |/ parent: 0:ea207398892e
616 617 | user: test
617 618 | date: Thu Jan 01 00:00:00 1970 +0000
618 619 | summary: A2
619 620 |
620 621 | o changeset: 2:fdf9bde5129a
621 622 |/ parent: 0:ea207398892e
622 623 | user: test
623 624 | date: Thu Jan 01 00:00:00 1970 +0000
624 625 | instability: content-divergent
625 626 | summary: A1
626 627 |
627 628 | x changeset: 1:471f378eab4c
628 629 |/ user: test
629 630 | date: Thu Jan 01 00:00:00 1970 +0000
630 631 | summary: A0
631 632 |
632 633 o changeset: 0:ea207398892e
633 634 user: test
634 635 date: Thu Jan 01 00:00:00 1970 +0000
635 636 summary: ROOT
636 637
637 638
638 639 Check templates
639 640 ---------------
640 641
641 642 $ hg up 'desc(A0)' --hidden
642 643 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
643 644
644 645 Predecessors template should show current revision as it is the working copy
645 646 $ hg tlog
646 647 o 019fadeab383
647 648 | Predecessors: 1:471f378eab4c
648 649 | semi-colon: 1:471f378eab4c
649 650 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
650 651 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
651 652 | o fdf9bde5129a
652 653 |/ Predecessors: 1:471f378eab4c
653 654 | semi-colon: 1:471f378eab4c
654 655 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
655 656 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
656 657 | @ 471f378eab4c
657 658 |/ Successors: 2:fdf9bde5129a; 4:019fadeab383
658 659 | multi-line: 2:fdf9bde5129a
659 660 | multi-line: 4:019fadeab383
660 661 | json: [["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]]
661 662 o ea207398892e
662 663
663 664 $ hg fatelog
664 665 o 019fadeab383
665 666 |
666 667 | o fdf9bde5129a
667 668 |/
668 669 | @ 471f378eab4c
669 670 |/ Obsfate: rewritten as 2:fdf9bde5129a; rewritten as 4:019fadeab383;
670 671 o ea207398892e
671 672
672 673 $ hg up 'desc(A1)'
673 674 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
674 675
675 676 Predecessors template should not show predecessors as they are not displayed in
676 677 the log
677 678 $ hg tlog
678 679 o 019fadeab383
679 680 |
680 681 | @ fdf9bde5129a
681 682 |/
682 683 o ea207398892e
683 684
684 685
685 686 $ hg fatelog
686 687 o 019fadeab383
687 688 |
688 689 | @ fdf9bde5129a
689 690 |/
690 691 o ea207398892e
691 692
692 693 Predecessors template should the predecessors as we force their display with
693 694 --hidden
694 695 $ hg tlog --hidden
695 696 o 019fadeab383
696 697 | Predecessors: 3:65b757b745b9
697 698 | semi-colon: 3:65b757b745b9
698 699 | json: ["65b757b745b935093c87a2bccd877521cccffcbd"]
699 700 | map: 3:65b757b745b935093c87a2bccd877521cccffcbd
700 701 | x 65b757b745b9
701 702 |/ Predecessors: 1:471f378eab4c
702 703 | semi-colon: 1:471f378eab4c
703 704 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
704 705 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
705 706 | Successors: 4:019fadeab383
706 707 | multi-line: 4:019fadeab383
707 708 | json: [["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]]
708 709 | @ fdf9bde5129a
709 710 |/ Predecessors: 1:471f378eab4c
710 711 | semi-colon: 1:471f378eab4c
711 712 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
712 713 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
713 714 | x 471f378eab4c
714 715 |/ Successors: 2:fdf9bde5129a; 3:65b757b745b9
715 716 | multi-line: 2:fdf9bde5129a
716 717 | multi-line: 3:65b757b745b9
717 718 | json: [["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], ["65b757b745b935093c87a2bccd877521cccffcbd"]]
718 719 o ea207398892e
719 720
720 721
721 722 $ hg fatelog --hidden
722 723 o 019fadeab383
723 724 |
724 725 | x 65b757b745b9
725 726 |/ Obsfate: rewritten as 4:019fadeab383;
726 727 | @ fdf9bde5129a
727 728 |/
728 729 | x 471f378eab4c
729 730 |/ Obsfate: rewritten as 2:fdf9bde5129a; rewritten as 3:65b757b745b9;
730 731 o ea207398892e
731 732
732 733
733 734 $ hg fatelogjson --hidden
734 735 o 019fadeab383
735 736 |
736 737 | x 65b757b745b9
737 738 |/ Obsfate: [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]}]
738 739 | @ fdf9bde5129a
739 740 |/
740 741 | x 471f378eab4c
741 742 |/ Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"]}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"]}]
742 743 o ea207398892e
743 744
744 745
745 746 Test templates with amended + folded commit
746 747 ===========================================
747 748
748 749 Test setup
749 750 ----------
750 751
751 752 $ hg init $TESTTMP/templates-local-amend-fold
752 753 $ cd $TESTTMP/templates-local-amend-fold
753 754 $ mkcommit ROOT
754 755 $ mkcommit A0
755 756 $ mkcommit B0
756 757 $ hg commit --amend -m "B1"
757 758 $ hg log --hidden -G
758 759 @ changeset: 3:b7ea6d14e664
759 760 | tag: tip
760 761 | parent: 1:471f378eab4c
761 762 | user: test
762 763 | date: Thu Jan 01 00:00:00 1970 +0000
763 764 | summary: B1
764 765 |
765 766 | x changeset: 2:0dec01379d3b
766 767 |/ user: test
767 768 | date: Thu Jan 01 00:00:00 1970 +0000
768 769 | summary: B0
769 770 |
770 771 o changeset: 1:471f378eab4c
771 772 | user: test
772 773 | date: Thu Jan 01 00:00:00 1970 +0000
773 774 | summary: A0
774 775 |
775 776 o changeset: 0:ea207398892e
776 777 user: test
777 778 date: Thu Jan 01 00:00:00 1970 +0000
778 779 summary: ROOT
779 780
780 781 # Simulate a fold
781 782 $ hg up -r "desc(ROOT)"
782 783 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
783 784 $ echo "A0" > A0
784 785 $ echo "B0" > B0
785 786 $ hg commit -A -m "C0"
786 787 adding A0
787 788 adding B0
788 789 created new head
789 790 $ hg debugobsolete `getid "desc(A0)"` `getid "desc(C0)"`
790 791 obsoleted 1 changesets
791 792 $ hg debugobsolete `getid "desc(B1)"` `getid "desc(C0)"`
792 793 obsoleted 1 changesets
793 794
794 795 $ hg log --hidden -G
795 796 @ changeset: 4:eb5a0daa2192
796 797 | tag: tip
797 798 | parent: 0:ea207398892e
798 799 | user: test
799 800 | date: Thu Jan 01 00:00:00 1970 +0000
800 801 | summary: C0
801 802 |
802 803 | x changeset: 3:b7ea6d14e664
803 804 | | parent: 1:471f378eab4c
804 805 | | user: test
805 806 | | date: Thu Jan 01 00:00:00 1970 +0000
806 807 | | summary: B1
807 808 | |
808 809 | | x changeset: 2:0dec01379d3b
809 810 | |/ user: test
810 811 | | date: Thu Jan 01 00:00:00 1970 +0000
811 812 | | summary: B0
812 813 | |
813 814 | x changeset: 1:471f378eab4c
814 815 |/ user: test
815 816 | date: Thu Jan 01 00:00:00 1970 +0000
816 817 | summary: A0
817 818 |
818 819 o changeset: 0:ea207398892e
819 820 user: test
820 821 date: Thu Jan 01 00:00:00 1970 +0000
821 822 summary: ROOT
822 823
823 824 Check templates
824 825 ---------------
825 826
826 827 $ hg up 'desc(A0)' --hidden
827 828 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
828 829
829 830 Predecessors template should show current revision as it is the working copy
830 831 $ hg tlog
831 832 o eb5a0daa2192
832 833 | Predecessors: 1:471f378eab4c
833 834 | semi-colon: 1:471f378eab4c
834 835 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
835 836 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
836 837 | @ 471f378eab4c
837 838 |/ Successors: 4:eb5a0daa2192
838 839 | multi-line: 4:eb5a0daa2192
839 840 | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
840 841 o ea207398892e
841 842
842 843
843 844 $ hg fatelog
844 845 o eb5a0daa2192
845 846 |
846 847 | @ 471f378eab4c
847 848 |/ Obsfate: rewritten as 4:eb5a0daa2192;
848 849 o ea207398892e
849 850
850 851 $ hg up 'desc(B0)' --hidden
851 852 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
852 853
853 854 Predecessors template should both predecessors as they are visible
854 855 $ hg tlog
855 856 o eb5a0daa2192
856 857 | Predecessors: 2:0dec01379d3b 1:471f378eab4c
857 858 | semi-colon: 2:0dec01379d3b; 1:471f378eab4c
858 859 | json: ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", "471f378eab4c5e25f6c77f785b27c936efb22874"]
859 860 | map: 2:0dec01379d3be6318c470ead31b1fe7ae7cb53d5 1:471f378eab4c5e25f6c77f785b27c936efb22874
860 861 | @ 0dec01379d3b
861 862 | | Successors: 4:eb5a0daa2192
862 863 | | multi-line: 4:eb5a0daa2192
863 864 | | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
864 865 | x 471f378eab4c
865 866 |/ Successors: 4:eb5a0daa2192
866 867 | multi-line: 4:eb5a0daa2192
867 868 | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
868 869 o ea207398892e
869 870
870 871
871 872 $ hg fatelog
872 873 o eb5a0daa2192
873 874 |
874 875 | @ 0dec01379d3b
875 876 | | Obsfate: rewritten as 4:eb5a0daa2192;
876 877 | x 471f378eab4c
877 878 |/ Obsfate: rewritten as 4:eb5a0daa2192;
878 879 o ea207398892e
879 880
880 881 $ hg up 'desc(B1)' --hidden
881 882 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
882 883
883 884 Predecessors template should both predecessors as they are visible
884 885 $ hg tlog
885 886 o eb5a0daa2192
886 887 | Predecessors: 1:471f378eab4c 3:b7ea6d14e664
887 888 | semi-colon: 1:471f378eab4c; 3:b7ea6d14e664
888 889 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874", "b7ea6d14e664bdc8922221f7992631b50da3fb07"]
889 890 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874 3:b7ea6d14e664bdc8922221f7992631b50da3fb07
890 891 | @ b7ea6d14e664
891 892 | | Successors: 4:eb5a0daa2192
892 893 | | multi-line: 4:eb5a0daa2192
893 894 | | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
894 895 | x 471f378eab4c
895 896 |/ Successors: 4:eb5a0daa2192
896 897 | multi-line: 4:eb5a0daa2192
897 898 | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
898 899 o ea207398892e
899 900
900 901
901 902 $ hg fatelog
902 903 o eb5a0daa2192
903 904 |
904 905 | @ b7ea6d14e664
905 906 | | Obsfate: rewritten as 4:eb5a0daa2192;
906 907 | x 471f378eab4c
907 908 |/ Obsfate: rewritten as 4:eb5a0daa2192;
908 909 o ea207398892e
909 910
910 911 $ hg up 'desc(C0)'
911 912 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
912 913
913 914 Predecessors template should show no predecessors as they are both non visible
914 915 $ hg tlog
915 916 @ eb5a0daa2192
916 917 |
917 918 o ea207398892e
918 919
919 920
920 921 $ hg fatelog
921 922 @ eb5a0daa2192
922 923 |
923 924 o ea207398892e
924 925
925 926 Predecessors template should show all predecessors as we force their display
926 927 with --hidden
927 928 $ hg tlog --hidden
928 929 @ eb5a0daa2192
929 930 | Predecessors: 1:471f378eab4c 3:b7ea6d14e664
930 931 | semi-colon: 1:471f378eab4c; 3:b7ea6d14e664
931 932 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874", "b7ea6d14e664bdc8922221f7992631b50da3fb07"]
932 933 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874 3:b7ea6d14e664bdc8922221f7992631b50da3fb07
933 934 | x b7ea6d14e664
934 935 | | Predecessors: 2:0dec01379d3b
935 936 | | semi-colon: 2:0dec01379d3b
936 937 | | json: ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]
937 938 | | map: 2:0dec01379d3be6318c470ead31b1fe7ae7cb53d5
938 939 | | Successors: 4:eb5a0daa2192
939 940 | | multi-line: 4:eb5a0daa2192
940 941 | | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
941 942 | | x 0dec01379d3b
942 943 | |/ Successors: 3:b7ea6d14e664
943 944 | | multi-line: 3:b7ea6d14e664
944 945 | | json: [["b7ea6d14e664bdc8922221f7992631b50da3fb07"]]
945 946 | x 471f378eab4c
946 947 |/ Successors: 4:eb5a0daa2192
947 948 | multi-line: 4:eb5a0daa2192
948 949 | json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
949 950 o ea207398892e
950 951
951 952
952 953 $ hg fatelog --hidden
953 954 @ eb5a0daa2192
954 955 |
955 956 | x b7ea6d14e664
956 957 | | Obsfate: rewritten as 4:eb5a0daa2192;
957 958 | | x 0dec01379d3b
958 959 | |/ Obsfate: rewritten as 3:b7ea6d14e664;
959 960 | x 471f378eab4c
960 961 |/ Obsfate: rewritten as 4:eb5a0daa2192;
961 962 o ea207398892e
962 963
963 964
964 965 $ hg fatelogjson --hidden
965 966 @ eb5a0daa2192
966 967 |
967 968 | x b7ea6d14e664
968 969 | | Obsfate: [{"markers": [["b7ea6d14e664bdc8922221f7992631b50da3fb07", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
969 970 | | x 0dec01379d3b
970 971 | |/ Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"]}]
971 972 | x 471f378eab4c
972 973 |/ Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
973 974 o ea207398892e
974 975
975 976
976 977 Test template with pushed and pulled obs markers
977 978 ================================================
978 979
979 980 Test setup
980 981 ----------
981 982
982 983 $ hg init $TESTTMP/templates-local-remote-markers-1
983 984 $ cd $TESTTMP/templates-local-remote-markers-1
984 985 $ mkcommit ROOT
985 986 $ mkcommit A0
986 987 $ hg clone $TESTTMP/templates-local-remote-markers-1 $TESTTMP/templates-local-remote-markers-2
987 988 updating to branch default
988 989 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
989 990 $ cd $TESTTMP/templates-local-remote-markers-2
990 991 $ hg log --hidden -G
991 992 @ changeset: 1:471f378eab4c
992 993 | tag: tip
993 994 | user: test
994 995 | date: Thu Jan 01 00:00:00 1970 +0000
995 996 | summary: A0
996 997 |
997 998 o changeset: 0:ea207398892e
998 999 user: test
999 1000 date: Thu Jan 01 00:00:00 1970 +0000
1000 1001 summary: ROOT
1001 1002
1002 1003 $ cd $TESTTMP/templates-local-remote-markers-1
1003 1004 $ hg commit --amend -m "A1"
1004 1005 $ hg commit --amend -m "A2"
1005 1006 $ hg log --hidden -G
1006 1007 @ changeset: 3:7a230b46bf61
1007 1008 | tag: tip
1008 1009 | parent: 0:ea207398892e
1009 1010 | user: test
1010 1011 | date: Thu Jan 01 00:00:00 1970 +0000
1011 1012 | summary: A2
1012 1013 |
1013 1014 | x changeset: 2:fdf9bde5129a
1014 1015 |/ parent: 0:ea207398892e
1015 1016 | user: test
1016 1017 | date: Thu Jan 01 00:00:00 1970 +0000
1017 1018 | summary: A1
1018 1019 |
1019 1020 | x changeset: 1:471f378eab4c
1020 1021 |/ user: test
1021 1022 | date: Thu Jan 01 00:00:00 1970 +0000
1022 1023 | summary: A0
1023 1024 |
1024 1025 o changeset: 0:ea207398892e
1025 1026 user: test
1026 1027 date: Thu Jan 01 00:00:00 1970 +0000
1027 1028 summary: ROOT
1028 1029
1029 1030 $ cd $TESTTMP/templates-local-remote-markers-2
1030 1031 $ hg pull
1031 1032 pulling from $TESTTMP/templates-local-remote-markers-1 (glob)
1032 1033 searching for changes
1033 1034 adding changesets
1034 1035 adding manifests
1035 1036 adding file changes
1036 1037 added 1 changesets with 0 changes to 1 files (+1 heads)
1037 1038 2 new obsolescence markers
1038 1039 obsoleted 1 changesets
1039 1040 (run 'hg heads' to see heads, 'hg merge' to merge)
1040 1041 $ hg log --hidden -G
1041 1042 o changeset: 2:7a230b46bf61
1042 1043 | tag: tip
1043 1044 | parent: 0:ea207398892e
1044 1045 | user: test
1045 1046 | date: Thu Jan 01 00:00:00 1970 +0000
1046 1047 | summary: A2
1047 1048 |
1048 1049 | @ changeset: 1:471f378eab4c
1049 1050 |/ user: test
1050 1051 | date: Thu Jan 01 00:00:00 1970 +0000
1051 1052 | summary: A0
1052 1053 |
1053 1054 o changeset: 0:ea207398892e
1054 1055 user: test
1055 1056 date: Thu Jan 01 00:00:00 1970 +0000
1056 1057 summary: ROOT
1057 1058
1058 1059
1059 1060 $ hg debugobsolete
1060 1061 471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1061 1062 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 7a230b46bf61e50b30308c6cfd7bd1269ef54702 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
1062 1063
1063 1064 Check templates
1064 1065 ---------------
1065 1066
1066 1067 Predecessors template should show current revision as it is the working copy
1067 1068 $ hg tlog
1068 1069 o 7a230b46bf61
1069 1070 | Predecessors: 1:471f378eab4c
1070 1071 | semi-colon: 1:471f378eab4c
1071 1072 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
1072 1073 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
1073 1074 | @ 471f378eab4c
1074 1075 |/ Successors: 2:7a230b46bf61
1075 1076 | multi-line: 2:7a230b46bf61
1076 1077 | json: [["7a230b46bf61e50b30308c6cfd7bd1269ef54702"]]
1077 1078 o ea207398892e
1078 1079
1079 1080
1080 1081 $ hg fatelog
1081 1082 o 7a230b46bf61
1082 1083 |
1083 1084 | @ 471f378eab4c
1084 1085 |/ Obsfate: rewritten as 2:7a230b46bf61;
1085 1086 o ea207398892e
1086 1087
1087 1088 $ hg up 'desc(A2)'
1088 1089 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1089 1090
1090 1091 Predecessors template should show no predecessors as they are non visible
1091 1092 $ hg tlog
1092 1093 @ 7a230b46bf61
1093 1094 |
1094 1095 o ea207398892e
1095 1096
1096 1097
1097 1098 $ hg fatelog
1098 1099 @ 7a230b46bf61
1099 1100 |
1100 1101 o ea207398892e
1101 1102
1102 1103 Predecessors template should show all predecessors as we force their display
1103 1104 with --hidden
1104 1105 $ hg tlog --hidden
1105 1106 @ 7a230b46bf61
1106 1107 | Predecessors: 1:471f378eab4c
1107 1108 | semi-colon: 1:471f378eab4c
1108 1109 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
1109 1110 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
1110 1111 | x 471f378eab4c
1111 1112 |/ Successors: 2:7a230b46bf61
1112 1113 | multi-line: 2:7a230b46bf61
1113 1114 | json: [["7a230b46bf61e50b30308c6cfd7bd1269ef54702"]]
1114 1115 o ea207398892e
1115 1116
1116 1117
1117 1118 $ hg fatelog --hidden
1118 1119 @ 7a230b46bf61
1119 1120 |
1120 1121 | x 471f378eab4c
1121 1122 |/ Obsfate: rewritten as 2:7a230b46bf61;
1122 1123 o ea207398892e
1123 1124
1124 1125
1125 1126 Test template with obsmarkers cycle
1126 1127 ===================================
1127 1128
1128 1129 Test setup
1129 1130 ----------
1130 1131
1131 1132 $ hg init $TESTTMP/templates-local-cycle
1132 1133 $ cd $TESTTMP/templates-local-cycle
1133 1134 $ mkcommit ROOT
1134 1135 $ mkcommit A0
1135 1136 $ mkcommit B0
1136 1137 $ hg up -r 0
1137 1138 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
1138 1139 $ mkcommit C0
1139 1140 created new head
1140 1141
1141 1142 Create the cycle
1142 1143
1143 1144 $ hg debugobsolete `getid "desc(A0)"` `getid "desc(B0)"`
1144 1145 obsoleted 1 changesets
1145 1146 $ hg debugobsolete `getid "desc(B0)"` `getid "desc(C0)"`
1146 1147 obsoleted 1 changesets
1147 1148 $ hg debugobsolete `getid "desc(B0)"` `getid "desc(A0)"`
1148 1149
1149 1150 Check templates
1150 1151 ---------------
1151 1152
1152 1153 $ hg tlog
1153 1154 @ f897c6137566
1154 1155 |
1155 1156 o ea207398892e
1156 1157
1157 1158
1158 1159 $ hg fatelog
1159 1160 @ f897c6137566
1160 1161 |
1161 1162 o ea207398892e
1162 1163
1163 1164
1164 1165 $ hg up -r "desc(B0)" --hidden
1165 1166 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1166 1167 $ hg tlog
1167 1168 o f897c6137566
1168 1169 | Predecessors: 2:0dec01379d3b
1169 1170 | semi-colon: 2:0dec01379d3b
1170 1171 | json: ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]
1171 1172 | map: 2:0dec01379d3be6318c470ead31b1fe7ae7cb53d5
1172 1173 | @ 0dec01379d3b
1173 1174 | | Predecessors: 1:471f378eab4c
1174 1175 | | semi-colon: 1:471f378eab4c
1175 1176 | | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
1176 1177 | | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
1177 1178 | | Successors: 3:f897c6137566; 1:471f378eab4c
1178 1179 | | multi-line: 3:f897c6137566
1179 1180 | | multi-line: 1:471f378eab4c
1180 1181 | | json: [["f897c6137566320b081514b4c7227ecc3d384b39"], ["471f378eab4c5e25f6c77f785b27c936efb22874"]]
1181 1182 | x 471f378eab4c
1182 1183 |/ Predecessors: 2:0dec01379d3b
1183 1184 | semi-colon: 2:0dec01379d3b
1184 1185 | json: ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]
1185 1186 | map: 2:0dec01379d3be6318c470ead31b1fe7ae7cb53d5
1186 1187 | Successors: 2:0dec01379d3b
1187 1188 | multi-line: 2:0dec01379d3b
1188 1189 | json: [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]]
1189 1190 o ea207398892e
1190 1191
1191 1192
1192 1193 $ hg fatelog
1193 1194 o f897c6137566
1194 1195 |
1195 1196 | @ 0dec01379d3b
1196 1197 | | Obsfate: rewritten as 3:f897c6137566; rewritten as 1:471f378eab4c;
1197 1198 | x 471f378eab4c
1198 1199 |/ Obsfate: rewritten as 2:0dec01379d3b;
1199 1200 o ea207398892e
1200 1201
1201 1202
1202 1203 $ hg up -r "desc(A0)" --hidden
1203 1204 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1204 1205 $ hg tlog
1205 1206 o f897c6137566
1206 1207 | Predecessors: 1:471f378eab4c
1207 1208 | semi-colon: 1:471f378eab4c
1208 1209 | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
1209 1210 | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
1210 1211 | @ 471f378eab4c
1211 1212 |/
1212 1213 o ea207398892e
1213 1214
1214 1215
1215 1216 $ hg fatelog
1216 1217 o f897c6137566
1217 1218 |
1218 1219 | @ 471f378eab4c
1219 1220 |/
1220 1221 o ea207398892e
1221 1222
1222 1223
1223 1224 $ hg up -r "desc(ROOT)" --hidden
1224 1225 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1225 1226 $ hg tlog
1226 1227 o f897c6137566
1227 1228 |
1228 1229 @ ea207398892e
1229 1230
1230 1231
1231 1232 $ hg fatelog
1232 1233 o f897c6137566
1233 1234 |
1234 1235 @ ea207398892e
1235 1236
1236 1237
1237 1238 $ hg tlog --hidden
1238 1239 o f897c6137566
1239 1240 | Predecessors: 2:0dec01379d3b
1240 1241 | semi-colon: 2:0dec01379d3b
1241 1242 | json: ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]
1242 1243 | map: 2:0dec01379d3be6318c470ead31b1fe7ae7cb53d5
1243 1244 | x 0dec01379d3b
1244 1245 | | Predecessors: 1:471f378eab4c
1245 1246 | | semi-colon: 1:471f378eab4c
1246 1247 | | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
1247 1248 | | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
1248 1249 | | Successors: 3:f897c6137566; 1:471f378eab4c
1249 1250 | | multi-line: 3:f897c6137566
1250 1251 | | multi-line: 1:471f378eab4c
1251 1252 | | json: [["f897c6137566320b081514b4c7227ecc3d384b39"], ["471f378eab4c5e25f6c77f785b27c936efb22874"]]
1252 1253 | x 471f378eab4c
1253 1254 |/ Predecessors: 2:0dec01379d3b
1254 1255 | semi-colon: 2:0dec01379d3b
1255 1256 | json: ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]
1256 1257 | map: 2:0dec01379d3be6318c470ead31b1fe7ae7cb53d5
1257 1258 | Successors: 2:0dec01379d3b
1258 1259 | multi-line: 2:0dec01379d3b
1259 1260 | json: [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]]
1260 1261 @ ea207398892e
1261 1262
1262 1263 Test template with split + divergence with cycles
1263 1264 =================================================
1264 1265
1265 1266 $ hg log -G
1266 1267 o changeset: 3:f897c6137566
1267 1268 | tag: tip
1268 1269 | parent: 0:ea207398892e
1269 1270 | user: test
1270 1271 | date: Thu Jan 01 00:00:00 1970 +0000
1271 1272 | summary: C0
1272 1273 |
1273 1274 @ changeset: 0:ea207398892e
1274 1275 user: test
1275 1276 date: Thu Jan 01 00:00:00 1970 +0000
1276 1277 summary: ROOT
1277 1278
1278 1279 $ hg up
1279 1280 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1280 1281
1281 1282 Create a commit with three files
1282 1283 $ touch A B C
1283 1284 $ hg commit -A -m "Add A,B,C" A B C
1284 1285
1285 1286 Split it
1286 1287 $ hg up 3
1287 1288 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
1288 1289 $ touch A
1289 1290 $ hg commit -A -m "Add A,B,C" A
1290 1291 created new head
1291 1292
1292 1293 $ touch B
1293 1294 $ hg commit -A -m "Add A,B,C" B
1294 1295
1295 1296 $ touch C
1296 1297 $ hg commit -A -m "Add A,B,C" C
1297 1298
1298 1299 $ hg log -G
1299 1300 @ changeset: 7:ba2ed02b0c9a
1300 1301 | tag: tip
1301 1302 | user: test
1302 1303 | date: Thu Jan 01 00:00:00 1970 +0000
1303 1304 | summary: Add A,B,C
1304 1305 |
1305 1306 o changeset: 6:4a004186e638
1306 1307 | user: test
1307 1308 | date: Thu Jan 01 00:00:00 1970 +0000
1308 1309 | summary: Add A,B,C
1309 1310 |
1310 1311 o changeset: 5:dd800401bd8c
1311 1312 | parent: 3:f897c6137566
1312 1313 | user: test
1313 1314 | date: Thu Jan 01 00:00:00 1970 +0000
1314 1315 | summary: Add A,B,C
1315 1316 |
1316 1317 | o changeset: 4:9bd10a0775e4
1317 1318 |/ user: test
1318 1319 | date: Thu Jan 01 00:00:00 1970 +0000
1319 1320 | summary: Add A,B,C
1320 1321 |
1321 1322 o changeset: 3:f897c6137566
1322 1323 | parent: 0:ea207398892e
1323 1324 | user: test
1324 1325 | date: Thu Jan 01 00:00:00 1970 +0000
1325 1326 | summary: C0
1326 1327 |
1327 1328 o changeset: 0:ea207398892e
1328 1329 user: test
1329 1330 date: Thu Jan 01 00:00:00 1970 +0000
1330 1331 summary: ROOT
1331 1332
1332 1333 $ hg debugobsolete `getid "4"` `getid "5"` `getid "6"` `getid "7"`
1333 1334 obsoleted 1 changesets
1334 1335 $ hg log -G
1335 1336 @ changeset: 7:ba2ed02b0c9a
1336 1337 | tag: tip
1337 1338 | user: test
1338 1339 | date: Thu Jan 01 00:00:00 1970 +0000
1339 1340 | summary: Add A,B,C
1340 1341 |
1341 1342 o changeset: 6:4a004186e638
1342 1343 | user: test
1343 1344 | date: Thu Jan 01 00:00:00 1970 +0000
1344 1345 | summary: Add A,B,C
1345 1346 |
1346 1347 o changeset: 5:dd800401bd8c
1347 1348 | parent: 3:f897c6137566
1348 1349 | user: test
1349 1350 | date: Thu Jan 01 00:00:00 1970 +0000
1350 1351 | summary: Add A,B,C
1351 1352 |
1352 1353 o changeset: 3:f897c6137566
1353 1354 | parent: 0:ea207398892e
1354 1355 | user: test
1355 1356 | date: Thu Jan 01 00:00:00 1970 +0000
1356 1357 | summary: C0
1357 1358 |
1358 1359 o changeset: 0:ea207398892e
1359 1360 user: test
1360 1361 date: Thu Jan 01 00:00:00 1970 +0000
1361 1362 summary: ROOT
1362 1363
1363 1364 Diverge one of the splitted commit
1364 1365
1365 1366 $ hg up 6
1366 1367 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1367 1368 $ hg commit --amend -m "Add only B"
1368 1369
1369 1370 $ hg up 6 --hidden
1370 1371 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1371 1372 $ hg commit --amend -m "Add B only"
1372 1373
1373 1374 $ hg log -G
1374 1375 @ changeset: 9:0b997eb7ceee
1375 1376 | tag: tip
1376 1377 | parent: 5:dd800401bd8c
1377 1378 | user: test
1378 1379 | date: Thu Jan 01 00:00:00 1970 +0000
1379 1380 | instability: content-divergent
1380 1381 | summary: Add B only
1381 1382 |
1382 1383 | o changeset: 8:b18bc8331526
1383 1384 |/ parent: 5:dd800401bd8c
1384 1385 | user: test
1385 1386 | date: Thu Jan 01 00:00:00 1970 +0000
1386 1387 | instability: content-divergent
1387 1388 | summary: Add only B
1388 1389 |
1389 1390 | o changeset: 7:ba2ed02b0c9a
1390 1391 | | user: test
1391 1392 | | date: Thu Jan 01 00:00:00 1970 +0000
1392 1393 | | instability: orphan, content-divergent
1393 1394 | | summary: Add A,B,C
1394 1395 | |
1395 1396 | x changeset: 6:4a004186e638
1396 1397 |/ user: test
1397 1398 | date: Thu Jan 01 00:00:00 1970 +0000
1398 1399 | summary: Add A,B,C
1399 1400 |
1400 1401 o changeset: 5:dd800401bd8c
1401 1402 | parent: 3:f897c6137566
1402 1403 | user: test
1403 1404 | date: Thu Jan 01 00:00:00 1970 +0000
1404 1405 | instability: content-divergent
1405 1406 | summary: Add A,B,C
1406 1407 |
1407 1408 o changeset: 3:f897c6137566
1408 1409 | parent: 0:ea207398892e
1409 1410 | user: test
1410 1411 | date: Thu Jan 01 00:00:00 1970 +0000
1411 1412 | summary: C0
1412 1413 |
1413 1414 o changeset: 0:ea207398892e
1414 1415 user: test
1415 1416 date: Thu Jan 01 00:00:00 1970 +0000
1416 1417 summary: ROOT
1417 1418
1418 1419
1419 1420 Check templates
1420 1421 ---------------
1421 1422
1422 1423 $ hg tlog
1423 1424 @ 0b997eb7ceee
1424 1425 | Predecessors: 6:4a004186e638
1425 1426 | semi-colon: 6:4a004186e638
1426 1427 | json: ["4a004186e63889f20cb16434fcbd72220bd1eace"]
1427 1428 | map: 6:4a004186e63889f20cb16434fcbd72220bd1eace
1428 1429 | o b18bc8331526
1429 1430 |/ Predecessors: 6:4a004186e638
1430 1431 | semi-colon: 6:4a004186e638
1431 1432 | json: ["4a004186e63889f20cb16434fcbd72220bd1eace"]
1432 1433 | map: 6:4a004186e63889f20cb16434fcbd72220bd1eace
1433 1434 | o ba2ed02b0c9a
1434 1435 | |
1435 1436 | x 4a004186e638
1436 1437 |/ Successors: 8:b18bc8331526; 9:0b997eb7ceee
1437 1438 | multi-line: 8:b18bc8331526
1438 1439 | multi-line: 9:0b997eb7ceee
1439 1440 | json: [["b18bc8331526a22cbb1801022bd1555bf291c48b"], ["0b997eb7ceeee06200a02f8aab185979092d514e"]]
1440 1441 o dd800401bd8c
1441 1442 |
1442 1443 o f897c6137566
1443 1444 |
1444 1445 o ea207398892e
1445 1446
1446 1447 $ hg fatelog
1447 1448 @ 0b997eb7ceee
1448 1449 |
1449 1450 | o b18bc8331526
1450 1451 |/
1451 1452 | o ba2ed02b0c9a
1452 1453 | |
1453 1454 | x 4a004186e638
1454 1455 |/ Obsfate: rewritten as 8:b18bc8331526; rewritten as 9:0b997eb7ceee;
1455 1456 o dd800401bd8c
1456 1457 |
1457 1458 o f897c6137566
1458 1459 |
1459 1460 o ea207398892e
1460 1461
1461 1462 $ hg tlog --hidden
1462 1463 @ 0b997eb7ceee
1463 1464 | Predecessors: 6:4a004186e638
1464 1465 | semi-colon: 6:4a004186e638
1465 1466 | json: ["4a004186e63889f20cb16434fcbd72220bd1eace"]
1466 1467 | map: 6:4a004186e63889f20cb16434fcbd72220bd1eace
1467 1468 | o b18bc8331526
1468 1469 |/ Predecessors: 6:4a004186e638
1469 1470 | semi-colon: 6:4a004186e638
1470 1471 | json: ["4a004186e63889f20cb16434fcbd72220bd1eace"]
1471 1472 | map: 6:4a004186e63889f20cb16434fcbd72220bd1eace
1472 1473 | o ba2ed02b0c9a
1473 1474 | | Predecessors: 4:9bd10a0775e4
1474 1475 | | semi-colon: 4:9bd10a0775e4
1475 1476 | | json: ["9bd10a0775e478708cada5f176ec6de654359ce7"]
1476 1477 | | map: 4:9bd10a0775e478708cada5f176ec6de654359ce7
1477 1478 | x 4a004186e638
1478 1479 |/ Predecessors: 4:9bd10a0775e4
1479 1480 | semi-colon: 4:9bd10a0775e4
1480 1481 | json: ["9bd10a0775e478708cada5f176ec6de654359ce7"]
1481 1482 | map: 4:9bd10a0775e478708cada5f176ec6de654359ce7
1482 1483 | Successors: 8:b18bc8331526; 9:0b997eb7ceee
1483 1484 | multi-line: 8:b18bc8331526
1484 1485 | multi-line: 9:0b997eb7ceee
1485 1486 | json: [["b18bc8331526a22cbb1801022bd1555bf291c48b"], ["0b997eb7ceeee06200a02f8aab185979092d514e"]]
1486 1487 o dd800401bd8c
1487 1488 | Predecessors: 4:9bd10a0775e4
1488 1489 | semi-colon: 4:9bd10a0775e4
1489 1490 | json: ["9bd10a0775e478708cada5f176ec6de654359ce7"]
1490 1491 | map: 4:9bd10a0775e478708cada5f176ec6de654359ce7
1491 1492 | x 9bd10a0775e4
1492 1493 |/ Successors: 5:dd800401bd8c 6:4a004186e638 7:ba2ed02b0c9a
1493 1494 | multi-line: 5:dd800401bd8c 6:4a004186e638 7:ba2ed02b0c9a
1494 1495 | json: [["dd800401bd8c79d815329277739e433e883f784e", "4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f"]]
1495 1496 o f897c6137566
1496 1497 | Predecessors: 2:0dec01379d3b
1497 1498 | semi-colon: 2:0dec01379d3b
1498 1499 | json: ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]
1499 1500 | map: 2:0dec01379d3be6318c470ead31b1fe7ae7cb53d5
1500 1501 | x 0dec01379d3b
1501 1502 | | Predecessors: 1:471f378eab4c
1502 1503 | | semi-colon: 1:471f378eab4c
1503 1504 | | json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
1504 1505 | | map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
1505 1506 | | Successors: 3:f897c6137566; 1:471f378eab4c
1506 1507 | | multi-line: 3:f897c6137566
1507 1508 | | multi-line: 1:471f378eab4c
1508 1509 | | json: [["f897c6137566320b081514b4c7227ecc3d384b39"], ["471f378eab4c5e25f6c77f785b27c936efb22874"]]
1509 1510 | x 471f378eab4c
1510 1511 |/ Predecessors: 2:0dec01379d3b
1511 1512 | semi-colon: 2:0dec01379d3b
1512 1513 | json: ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]
1513 1514 | map: 2:0dec01379d3be6318c470ead31b1fe7ae7cb53d5
1514 1515 | Successors: 2:0dec01379d3b
1515 1516 | multi-line: 2:0dec01379d3b
1516 1517 | json: [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]]
1517 1518 o ea207398892e
1518 1519
1519 1520 $ hg fatelog --hidden
1520 1521 @ 0b997eb7ceee
1521 1522 |
1522 1523 | o b18bc8331526
1523 1524 |/
1524 1525 | o ba2ed02b0c9a
1525 1526 | |
1526 1527 | x 4a004186e638
1527 1528 |/ Obsfate: rewritten as 8:b18bc8331526; rewritten as 9:0b997eb7ceee;
1528 1529 o dd800401bd8c
1529 1530 |
1530 1531 | x 9bd10a0775e4
1531 |/ Obsfate: rewritten as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a;
1532 |/ Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a;
1532 1533 o f897c6137566
1533 1534 |
1534 1535 | x 0dec01379d3b
1535 1536 | | Obsfate: rewritten as 3:f897c6137566; rewritten as 1:471f378eab4c;
1536 1537 | x 471f378eab4c
1537 1538 |/ Obsfate: rewritten as 2:0dec01379d3b;
1538 1539 o ea207398892e
1539 1540
1540 1541 $ hg fatelogjson --hidden
1541 1542 @ 0b997eb7ceee
1542 1543 |
1543 1544 | o b18bc8331526
1544 1545 |/
1545 1546 | o ba2ed02b0c9a
1546 1547 | |
1547 1548 | x 4a004186e638
1548 1549 |/ Obsfate: [{"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["b18bc8331526a22cbb1801022bd1555bf291c48b"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["b18bc8331526a22cbb1801022bd1555bf291c48b"]}, {"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["0b997eb7ceeee06200a02f8aab185979092d514e"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["0b997eb7ceeee06200a02f8aab185979092d514e"]}]
1549 1550 o dd800401bd8c
1550 1551 |
1551 1552 | x 9bd10a0775e4
1552 1553 |/ Obsfate: [{"markers": [["9bd10a0775e478708cada5f176ec6de654359ce7", ["dd800401bd8c79d815329277739e433e883f784e", "4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["dd800401bd8c79d815329277739e433e883f784e", "4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f"]}]
1553 1554 o f897c6137566
1554 1555 |
1555 1556 | x 0dec01379d3b
1556 1557 | | Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["f897c6137566320b081514b4c7227ecc3d384b39"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["f897c6137566320b081514b4c7227ecc3d384b39"]}, {"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["471f378eab4c5e25f6c77f785b27c936efb22874"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["471f378eab4c5e25f6c77f785b27c936efb22874"]}]
1557 1558 | x 471f378eab4c
1558 1559 |/ Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]}]
1559 1560 o ea207398892e
1560 1561
1561 1562 $ hg up --hidden 4
1562 1563 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1563 1564 $ hg rebase -r 7 -d 8 --config extensions.rebase=
1564 1565 rebasing 7:ba2ed02b0c9a "Add A,B,C"
1565 1566 $ hg tlog
1566 1567 o eceed8f98ffc
1567 1568 | Predecessors: 4:9bd10a0775e4
1568 1569 | semi-colon: 4:9bd10a0775e4
1569 1570 | json: ["9bd10a0775e478708cada5f176ec6de654359ce7"]
1570 1571 | map: 4:9bd10a0775e478708cada5f176ec6de654359ce7
1571 1572 | o 0b997eb7ceee
1572 1573 | | Predecessors: 4:9bd10a0775e4
1573 1574 | | semi-colon: 4:9bd10a0775e4
1574 1575 | | json: ["9bd10a0775e478708cada5f176ec6de654359ce7"]
1575 1576 | | map: 4:9bd10a0775e478708cada5f176ec6de654359ce7
1576 1577 o | b18bc8331526
1577 1578 |/ Predecessors: 4:9bd10a0775e4
1578 1579 | semi-colon: 4:9bd10a0775e4
1579 1580 | json: ["9bd10a0775e478708cada5f176ec6de654359ce7"]
1580 1581 | map: 4:9bd10a0775e478708cada5f176ec6de654359ce7
1581 1582 o dd800401bd8c
1582 1583 | Predecessors: 4:9bd10a0775e4
1583 1584 | semi-colon: 4:9bd10a0775e4
1584 1585 | json: ["9bd10a0775e478708cada5f176ec6de654359ce7"]
1585 1586 | map: 4:9bd10a0775e478708cada5f176ec6de654359ce7
1586 1587 | @ 9bd10a0775e4
1587 1588 |/ Successors: 5:dd800401bd8c 9:0b997eb7ceee 10:eceed8f98ffc; 5:dd800401bd8c 8:b18bc8331526 10:eceed8f98ffc
1588 1589 | multi-line: 5:dd800401bd8c 9:0b997eb7ceee 10:eceed8f98ffc
1589 1590 | multi-line: 5:dd800401bd8c 8:b18bc8331526 10:eceed8f98ffc
1590 1591 | json: [["dd800401bd8c79d815329277739e433e883f784e", "0b997eb7ceeee06200a02f8aab185979092d514e", "eceed8f98ffc4186032e29a6542ab98888ebf68d"], ["dd800401bd8c79d815329277739e433e883f784e", "b18bc8331526a22cbb1801022bd1555bf291c48b", "eceed8f98ffc4186032e29a6542ab98888ebf68d"]]
1591 1592 o f897c6137566
1592 1593 |
1593 1594 o ea207398892e
1594 1595
1595 1596
1596 1597 $ hg fatelog
1597 1598 o eceed8f98ffc
1598 1599 |
1599 1600 | o 0b997eb7ceee
1600 1601 | |
1601 1602 o | b18bc8331526
1602 1603 |/
1603 1604 o dd800401bd8c
1604 1605 |
1605 1606 | @ 9bd10a0775e4
1606 |/ Obsfate: rewritten as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc; rewritten as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc;
1607 |/ Obsfate: split as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc; split as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc;
1607 1608 o f897c6137566
1608 1609 |
1609 1610 o ea207398892e
1610 1611
1611 1612 Test templates with pruned commits
1612 1613 ==================================
1613 1614
1614 1615 Test setup
1615 1616 ----------
1616 1617
1617 1618 $ hg init $TESTTMP/templates-local-prune
1618 1619 $ cd $TESTTMP/templates-local-prune
1619 1620 $ mkcommit ROOT
1620 1621 $ mkcommit A0
1621 1622 $ hg debugobsolete --record-parent `getid "."`
1622 1623 obsoleted 1 changesets
1623 1624
1624 1625 Check output
1625 1626 ------------
1626 1627
1627 1628 $ hg up "desc(A0)" --hidden
1628 1629 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1629 1630 $ hg tlog
1630 1631 @ 471f378eab4c
1631 1632 |
1632 1633 o ea207398892e
1633 1634
1634 1635 $ hg fatelog
1635 1636 @ 471f378eab4c
1636 1637 |
1637 1638 o ea207398892e
1638 1639
1639 1640 $ hg fatelog -v
1640 1641 @ 471f378eab4c
1641 1642 |
1642 1643 o ea207398892e
1643 1644
General Comments 0
You need to be logged in to leave comments. Login now