##// END OF EJS Templates
templater: don't overwrite the keyword mapping in runsymbol() (issue4362)...
Matt Harbison -
r23167:a3c2d921 stable
parent child Browse files
Show More
@@ -1,763 +1,761 b''
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 i18n import _
9 9 import os, re
10 10 import util, config, templatefilters, templatekw, parser, error
11 11 import revset as revsetmod
12 12 import types
13 13 import minirst
14 14
15 15 # template parsing
16 16
17 17 elements = {
18 18 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
19 19 ",": (2, None, ("list", 2)),
20 20 "|": (5, None, ("|", 5)),
21 21 "%": (6, None, ("%", 6)),
22 22 ")": (0, None, None),
23 23 "symbol": (0, ("symbol",), None),
24 24 "string": (0, ("string",), None),
25 25 "rawstring": (0, ("rawstring",), None),
26 26 "end": (0, None, None),
27 27 }
28 28
29 29 def tokenizer(data):
30 30 program, start, end = data
31 31 pos = start
32 32 while pos < end:
33 33 c = program[pos]
34 34 if c.isspace(): # skip inter-token whitespace
35 35 pass
36 36 elif c in "(,)%|": # handle simple operators
37 37 yield (c, None, pos)
38 38 elif (c in '"\'' or c == 'r' and
39 39 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
40 40 if c == 'r':
41 41 pos += 1
42 42 c = program[pos]
43 43 decode = False
44 44 else:
45 45 decode = True
46 46 pos += 1
47 47 s = pos
48 48 while pos < end: # find closing quote
49 49 d = program[pos]
50 50 if decode and d == '\\': # skip over escaped characters
51 51 pos += 2
52 52 continue
53 53 if d == c:
54 54 if not decode:
55 55 yield ('rawstring', program[s:pos], s)
56 56 break
57 57 yield ('string', program[s:pos], s)
58 58 break
59 59 pos += 1
60 60 else:
61 61 raise error.ParseError(_("unterminated string"), s)
62 62 elif c.isalnum() or c in '_':
63 63 s = pos
64 64 pos += 1
65 65 while pos < end: # find end of symbol
66 66 d = program[pos]
67 67 if not (d.isalnum() or d == "_"):
68 68 break
69 69 pos += 1
70 70 sym = program[s:pos]
71 71 yield ('symbol', sym, s)
72 72 pos -= 1
73 73 elif c == '}':
74 74 pos += 1
75 75 break
76 76 else:
77 77 raise error.ParseError(_("syntax error"), pos)
78 78 pos += 1
79 79 yield ('end', None, pos)
80 80
81 81 def compiletemplate(tmpl, context, strtoken="string"):
82 82 parsed = []
83 83 pos, stop = 0, len(tmpl)
84 84 p = parser.parser(tokenizer, elements)
85 85 while pos < stop:
86 86 n = tmpl.find('{', pos)
87 87 if n < 0:
88 88 parsed.append((strtoken, tmpl[pos:]))
89 89 break
90 90 if n > 0 and tmpl[n - 1] == '\\':
91 91 # escaped
92 92 parsed.append((strtoken, (tmpl[pos:n - 1] + "{")))
93 93 pos = n + 1
94 94 continue
95 95 if n > pos:
96 96 parsed.append((strtoken, tmpl[pos:n]))
97 97
98 98 pd = [tmpl, n + 1, stop]
99 99 parseres, pos = p.parse(pd)
100 100 parsed.append(parseres)
101 101
102 102 return [compileexp(e, context) for e in parsed]
103 103
104 104 def compileexp(exp, context):
105 105 t = exp[0]
106 106 if t in methods:
107 107 return methods[t](exp, context)
108 108 raise error.ParseError(_("unknown method '%s'") % t)
109 109
110 110 # template evaluation
111 111
112 112 def getsymbol(exp):
113 113 if exp[0] == 'symbol':
114 114 return exp[1]
115 115 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
116 116
117 117 def getlist(x):
118 118 if not x:
119 119 return []
120 120 if x[0] == 'list':
121 121 return getlist(x[1]) + [x[2]]
122 122 return [x]
123 123
124 124 def getfilter(exp, context):
125 125 f = getsymbol(exp)
126 126 if f not in context._filters:
127 127 raise error.ParseError(_("unknown function '%s'") % f)
128 128 return context._filters[f]
129 129
130 130 def gettemplate(exp, context):
131 131 if exp[0] == 'string' or exp[0] == 'rawstring':
132 132 return compiletemplate(exp[1], context, strtoken=exp[0])
133 133 if exp[0] == 'symbol':
134 134 return context._load(exp[1])
135 135 raise error.ParseError(_("expected template specifier"))
136 136
137 137 def runstring(context, mapping, data):
138 138 return data.decode("string-escape")
139 139
140 140 def runrawstring(context, mapping, data):
141 141 return data
142 142
143 143 def runsymbol(context, mapping, key):
144 144 v = mapping.get(key)
145 145 if v is None:
146 146 v = context._defaults.get(key)
147 147 if v is None:
148 148 try:
149 149 v = context.process(key, mapping)
150 150 except TemplateNotFound:
151 151 v = ''
152 152 if callable(v):
153 153 return v(**mapping)
154 154 if isinstance(v, types.GeneratorType):
155 155 v = list(v)
156 mapping[key] = v
157 return v
158 156 return v
159 157
160 158 def buildfilter(exp, context):
161 159 func, data = compileexp(exp[1], context)
162 160 filt = getfilter(exp[2], context)
163 161 return (runfilter, (func, data, filt))
164 162
165 163 def runfilter(context, mapping, data):
166 164 func, data, filt = data
167 165 try:
168 166 return filt(func(context, mapping, data))
169 167 except (ValueError, AttributeError, TypeError):
170 168 if isinstance(data, tuple):
171 169 dt = data[1]
172 170 else:
173 171 dt = data
174 172 raise util.Abort(_("template filter '%s' is not compatible with "
175 173 "keyword '%s'") % (filt.func_name, dt))
176 174
177 175 def buildmap(exp, context):
178 176 func, data = compileexp(exp[1], context)
179 177 ctmpl = gettemplate(exp[2], context)
180 178 return (runmap, (func, data, ctmpl))
181 179
182 180 def runtemplate(context, mapping, template):
183 181 for func, data in template:
184 182 yield func(context, mapping, data)
185 183
186 184 def runmap(context, mapping, data):
187 185 func, data, ctmpl = data
188 186 d = func(context, mapping, data)
189 187 if callable(d):
190 188 d = d()
191 189
192 190 lm = mapping.copy()
193 191
194 192 for i in d:
195 193 if isinstance(i, dict):
196 194 lm.update(i)
197 195 lm['originalnode'] = mapping.get('node')
198 196 yield runtemplate(context, lm, ctmpl)
199 197 else:
200 198 # v is not an iterable of dicts, this happen when 'key'
201 199 # has been fully expanded already and format is useless.
202 200 # If so, return the expanded value.
203 201 yield i
204 202
205 203 def buildfunc(exp, context):
206 204 n = getsymbol(exp[1])
207 205 args = [compileexp(x, context) for x in getlist(exp[2])]
208 206 if n in funcs:
209 207 f = funcs[n]
210 208 return (f, args)
211 209 if n in context._filters:
212 210 if len(args) != 1:
213 211 raise error.ParseError(_("filter %s expects one argument") % n)
214 212 f = context._filters[n]
215 213 return (runfilter, (args[0][0], args[0][1], f))
216 214 raise error.ParseError(_("unknown function '%s'") % n)
217 215
218 216 def date(context, mapping, args):
219 217 if not (1 <= len(args) <= 2):
220 218 # i18n: "date" is a keyword
221 219 raise error.ParseError(_("date expects one or two arguments"))
222 220
223 221 date = args[0][0](context, mapping, args[0][1])
224 222 if len(args) == 2:
225 223 fmt = stringify(args[1][0](context, mapping, args[1][1]))
226 224 return util.datestr(date, fmt)
227 225 return util.datestr(date)
228 226
229 227 def diff(context, mapping, args):
230 228 if len(args) > 2:
231 229 # i18n: "diff" is a keyword
232 230 raise error.ParseError(_("diff expects one, two or no arguments"))
233 231
234 232 def getpatterns(i):
235 233 if i < len(args):
236 234 s = args[i][1].strip()
237 235 if s:
238 236 return [s]
239 237 return []
240 238
241 239 ctx = mapping['ctx']
242 240 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
243 241
244 242 return ''.join(chunks)
245 243
246 244 def fill(context, mapping, args):
247 245 if not (1 <= len(args) <= 4):
248 246 # i18n: "fill" is a keyword
249 247 raise error.ParseError(_("fill expects one to four arguments"))
250 248
251 249 text = stringify(args[0][0](context, mapping, args[0][1]))
252 250 width = 76
253 251 initindent = ''
254 252 hangindent = ''
255 253 if 2 <= len(args) <= 4:
256 254 try:
257 255 width = int(stringify(args[1][0](context, mapping, args[1][1])))
258 256 except ValueError:
259 257 # i18n: "fill" is a keyword
260 258 raise error.ParseError(_("fill expects an integer width"))
261 259 try:
262 260 initindent = stringify(_evalifliteral(args[2], context, mapping))
263 261 hangindent = stringify(_evalifliteral(args[3], context, mapping))
264 262 except IndexError:
265 263 pass
266 264
267 265 return templatefilters.fill(text, width, initindent, hangindent)
268 266
269 267 def pad(context, mapping, args):
270 268 """usage: pad(text, width, fillchar=' ', right=False)
271 269 """
272 270 if not (2 <= len(args) <= 4):
273 271 # i18n: "pad" is a keyword
274 272 raise error.ParseError(_("pad() expects two to four arguments"))
275 273
276 274 width = int(args[1][1])
277 275
278 276 text = stringify(args[0][0](context, mapping, args[0][1]))
279 277 if args[0][0] == runstring:
280 278 text = stringify(runtemplate(context, mapping,
281 279 compiletemplate(text, context)))
282 280
283 281 right = False
284 282 fillchar = ' '
285 283 if len(args) > 2:
286 284 fillchar = stringify(args[2][0](context, mapping, args[2][1]))
287 285 if len(args) > 3:
288 286 right = util.parsebool(args[3][1])
289 287
290 288 if right:
291 289 return text.rjust(width, fillchar)
292 290 else:
293 291 return text.ljust(width, fillchar)
294 292
295 293 def get(context, mapping, args):
296 294 if len(args) != 2:
297 295 # i18n: "get" is a keyword
298 296 raise error.ParseError(_("get() expects two arguments"))
299 297
300 298 dictarg = args[0][0](context, mapping, args[0][1])
301 299 if not util.safehasattr(dictarg, 'get'):
302 300 # i18n: "get" is a keyword
303 301 raise error.ParseError(_("get() expects a dict as first argument"))
304 302
305 303 key = args[1][0](context, mapping, args[1][1])
306 304 yield dictarg.get(key)
307 305
308 306 def _evalifliteral(arg, context, mapping):
309 307 t = stringify(arg[0](context, mapping, arg[1]))
310 308 if arg[0] == runstring or arg[0] == runrawstring:
311 309 yield runtemplate(context, mapping,
312 310 compiletemplate(t, context, strtoken='rawstring'))
313 311 else:
314 312 yield t
315 313
316 314 def if_(context, mapping, args):
317 315 if not (2 <= len(args) <= 3):
318 316 # i18n: "if" is a keyword
319 317 raise error.ParseError(_("if expects two or three arguments"))
320 318
321 319 test = stringify(args[0][0](context, mapping, args[0][1]))
322 320 if test:
323 321 yield _evalifliteral(args[1], context, mapping)
324 322 elif len(args) == 3:
325 323 yield _evalifliteral(args[2], context, mapping)
326 324
327 325 def ifcontains(context, mapping, args):
328 326 if not (3 <= len(args) <= 4):
329 327 # i18n: "ifcontains" is a keyword
330 328 raise error.ParseError(_("ifcontains expects three or four arguments"))
331 329
332 330 item = stringify(args[0][0](context, mapping, args[0][1]))
333 331 items = args[1][0](context, mapping, args[1][1])
334 332
335 333 # Iterating over items gives a formatted string, so we iterate
336 334 # directly over the raw values.
337 335 if ((callable(items) and item in [i.values()[0] for i in items()]) or
338 336 (isinstance(items, str) and item in items)):
339 337 yield _evalifliteral(args[2], context, mapping)
340 338 elif len(args) == 4:
341 339 yield _evalifliteral(args[3], context, mapping)
342 340
343 341 def ifeq(context, mapping, args):
344 342 if not (3 <= len(args) <= 4):
345 343 # i18n: "ifeq" is a keyword
346 344 raise error.ParseError(_("ifeq expects three or four arguments"))
347 345
348 346 test = stringify(args[0][0](context, mapping, args[0][1]))
349 347 match = stringify(args[1][0](context, mapping, args[1][1]))
350 348 if test == match:
351 349 yield _evalifliteral(args[2], context, mapping)
352 350 elif len(args) == 4:
353 351 yield _evalifliteral(args[3], context, mapping)
354 352
355 353 def join(context, mapping, args):
356 354 if not (1 <= len(args) <= 2):
357 355 # i18n: "join" is a keyword
358 356 raise error.ParseError(_("join expects one or two arguments"))
359 357
360 358 joinset = args[0][0](context, mapping, args[0][1])
361 359 if callable(joinset):
362 360 jf = joinset.joinfmt
363 361 joinset = [jf(x) for x in joinset()]
364 362
365 363 joiner = " "
366 364 if len(args) > 1:
367 365 joiner = stringify(args[1][0](context, mapping, args[1][1]))
368 366
369 367 first = True
370 368 for x in joinset:
371 369 if first:
372 370 first = False
373 371 else:
374 372 yield joiner
375 373 yield x
376 374
377 375 def label(context, mapping, args):
378 376 if len(args) != 2:
379 377 # i18n: "label" is a keyword
380 378 raise error.ParseError(_("label expects two arguments"))
381 379
382 380 # ignore args[0] (the label string) since this is supposed to be a a no-op
383 381 yield _evalifliteral(args[1], context, mapping)
384 382
385 383 def revset(context, mapping, args):
386 384 """usage: revset(query[, formatargs...])
387 385 """
388 386 if not len(args) > 0:
389 387 # i18n: "revset" is a keyword
390 388 raise error.ParseError(_("revset expects one or more arguments"))
391 389
392 390 raw = args[0][1]
393 391 ctx = mapping['ctx']
394 392 repo = ctx._repo
395 393
396 394 def query(expr):
397 395 m = revsetmod.match(repo.ui, expr)
398 396 return m(repo, revsetmod.spanset(repo))
399 397
400 398 if len(args) > 1:
401 399 formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
402 400 revs = query(revsetmod.formatspec(raw, *formatargs))
403 401 revs = list([str(r) for r in revs])
404 402 else:
405 403 revsetcache = mapping['cache'].setdefault("revsetcache", {})
406 404 if raw in revsetcache:
407 405 revs = revsetcache[raw]
408 406 else:
409 407 revs = query(raw)
410 408 revs = list([str(r) for r in revs])
411 409 revsetcache[raw] = revs
412 410
413 411 return templatekw.showlist("revision", revs, **mapping)
414 412
415 413 def rstdoc(context, mapping, args):
416 414 if len(args) != 2:
417 415 # i18n: "rstdoc" is a keyword
418 416 raise error.ParseError(_("rstdoc expects two arguments"))
419 417
420 418 text = stringify(args[0][0](context, mapping, args[0][1]))
421 419 style = stringify(args[1][0](context, mapping, args[1][1]))
422 420
423 421 return minirst.format(text, style=style, keep=['verbose'])
424 422
425 423 def shortest(context, mapping, args):
426 424 """usage: shortest(node, minlength=4)
427 425 """
428 426 if not (1 <= len(args) <= 2):
429 427 # i18n: "shortest" is a keyword
430 428 raise error.ParseError(_("shortest() expects one or two arguments"))
431 429
432 430 node = stringify(args[0][0](context, mapping, args[0][1]))
433 431
434 432 minlength = 4
435 433 if len(args) > 1:
436 434 minlength = int(args[1][1])
437 435
438 436 cl = mapping['ctx']._repo.changelog
439 437 def isvalid(test):
440 438 try:
441 439 try:
442 440 cl.index.partialmatch(test)
443 441 except AttributeError:
444 442 # Pure mercurial doesn't support partialmatch on the index.
445 443 # Fallback to the slow way.
446 444 if cl._partialmatch(test) is None:
447 445 return False
448 446
449 447 try:
450 448 i = int(test)
451 449 # if we are a pure int, then starting with zero will not be
452 450 # confused as a rev; or, obviously, if the int is larger than
453 451 # the value of the tip rev
454 452 if test[0] == '0' or i > len(cl):
455 453 return True
456 454 return False
457 455 except ValueError:
458 456 return True
459 457 except error.RevlogError:
460 458 return False
461 459
462 460 shortest = node
463 461 startlength = max(6, minlength)
464 462 length = startlength
465 463 while True:
466 464 test = node[:length]
467 465 if isvalid(test):
468 466 shortest = test
469 467 if length == minlength or length > startlength:
470 468 return shortest
471 469 length -= 1
472 470 else:
473 471 length += 1
474 472 if len(shortest) <= length:
475 473 return shortest
476 474
477 475 def strip(context, mapping, args):
478 476 if not (1 <= len(args) <= 2):
479 477 # i18n: "strip" is a keyword
480 478 raise error.ParseError(_("strip expects one or two arguments"))
481 479
482 480 text = stringify(args[0][0](context, mapping, args[0][1]))
483 481 if len(args) == 2:
484 482 chars = stringify(args[1][0](context, mapping, args[1][1]))
485 483 return text.strip(chars)
486 484 return text.strip()
487 485
488 486 def sub(context, mapping, args):
489 487 if len(args) != 3:
490 488 # i18n: "sub" is a keyword
491 489 raise error.ParseError(_("sub expects three arguments"))
492 490
493 491 pat = stringify(args[0][0](context, mapping, args[0][1]))
494 492 rpl = stringify(args[1][0](context, mapping, args[1][1]))
495 493 src = stringify(_evalifliteral(args[2], context, mapping))
496 494 yield re.sub(pat, rpl, src)
497 495
498 496 def startswith(context, mapping, args):
499 497 if len(args) != 2:
500 498 # i18n: "startswith" is a keyword
501 499 raise error.ParseError(_("startswith expects two arguments"))
502 500
503 501 patn = stringify(args[0][0](context, mapping, args[0][1]))
504 502 text = stringify(args[1][0](context, mapping, args[1][1]))
505 503 if text.startswith(patn):
506 504 return text
507 505 return ''
508 506
509 507
510 508 def word(context, mapping, args):
511 509 """return nth word from a string"""
512 510 if not (2 <= len(args) <= 3):
513 511 # i18n: "word" is a keyword
514 512 raise error.ParseError(_("word expects two or three arguments, got %d")
515 513 % len(args))
516 514
517 515 num = int(stringify(args[0][0](context, mapping, args[0][1])))
518 516 text = stringify(args[1][0](context, mapping, args[1][1]))
519 517 if len(args) == 3:
520 518 splitter = stringify(args[2][0](context, mapping, args[2][1]))
521 519 else:
522 520 splitter = None
523 521
524 522 tokens = text.split(splitter)
525 523 if num >= len(tokens):
526 524 return ''
527 525 else:
528 526 return tokens[num]
529 527
530 528 methods = {
531 529 "string": lambda e, c: (runstring, e[1]),
532 530 "rawstring": lambda e, c: (runrawstring, e[1]),
533 531 "symbol": lambda e, c: (runsymbol, e[1]),
534 532 "group": lambda e, c: compileexp(e[1], c),
535 533 # ".": buildmember,
536 534 "|": buildfilter,
537 535 "%": buildmap,
538 536 "func": buildfunc,
539 537 }
540 538
541 539 funcs = {
542 540 "date": date,
543 541 "diff": diff,
544 542 "fill": fill,
545 543 "get": get,
546 544 "if": if_,
547 545 "ifcontains": ifcontains,
548 546 "ifeq": ifeq,
549 547 "join": join,
550 548 "label": label,
551 549 "pad": pad,
552 550 "revset": revset,
553 551 "rstdoc": rstdoc,
554 552 "shortest": shortest,
555 553 "startswith": startswith,
556 554 "strip": strip,
557 555 "sub": sub,
558 556 "word": word,
559 557 }
560 558
561 559 # template engine
562 560
563 561 stringify = templatefilters.stringify
564 562
565 563 def _flatten(thing):
566 564 '''yield a single stream from a possibly nested set of iterators'''
567 565 if isinstance(thing, str):
568 566 yield thing
569 567 elif not util.safehasattr(thing, '__iter__'):
570 568 if thing is not None:
571 569 yield str(thing)
572 570 else:
573 571 for i in thing:
574 572 if isinstance(i, str):
575 573 yield i
576 574 elif not util.safehasattr(i, '__iter__'):
577 575 if i is not None:
578 576 yield str(i)
579 577 elif i is not None:
580 578 for j in _flatten(i):
581 579 yield j
582 580
583 581 def parsestring(s, quoted=True):
584 582 '''parse a string using simple c-like syntax.
585 583 string must be in quotes if quoted is True.'''
586 584 if quoted:
587 585 if len(s) < 2 or s[0] != s[-1]:
588 586 raise SyntaxError(_('unmatched quotes'))
589 587 return s[1:-1].decode('string_escape')
590 588
591 589 return s.decode('string_escape')
592 590
593 591 class engine(object):
594 592 '''template expansion engine.
595 593
596 594 template expansion works like this. a map file contains key=value
597 595 pairs. if value is quoted, it is treated as string. otherwise, it
598 596 is treated as name of template file.
599 597
600 598 templater is asked to expand a key in map. it looks up key, and
601 599 looks for strings like this: {foo}. it expands {foo} by looking up
602 600 foo in map, and substituting it. expansion is recursive: it stops
603 601 when there is no more {foo} to replace.
604 602
605 603 expansion also allows formatting and filtering.
606 604
607 605 format uses key to expand each item in list. syntax is
608 606 {key%format}.
609 607
610 608 filter uses function to transform value. syntax is
611 609 {key|filter1|filter2|...}.'''
612 610
613 611 def __init__(self, loader, filters={}, defaults={}):
614 612 self._loader = loader
615 613 self._filters = filters
616 614 self._defaults = defaults
617 615 self._cache = {}
618 616
619 617 def _load(self, t):
620 618 '''load, parse, and cache a template'''
621 619 if t not in self._cache:
622 620 self._cache[t] = compiletemplate(self._loader(t), self)
623 621 return self._cache[t]
624 622
625 623 def process(self, t, mapping):
626 624 '''Perform expansion. t is name of map element to expand.
627 625 mapping contains added elements for use during expansion. Is a
628 626 generator.'''
629 627 return _flatten(runtemplate(self, mapping, self._load(t)))
630 628
631 629 engines = {'default': engine}
632 630
633 631 def stylelist():
634 632 paths = templatepaths()
635 633 if not paths:
636 634 return _('no templates found, try `hg debuginstall` for more info')
637 635 dirlist = os.listdir(paths[0])
638 636 stylelist = []
639 637 for file in dirlist:
640 638 split = file.split(".")
641 639 if split[0] == "map-cmdline":
642 640 stylelist.append(split[1])
643 641 return ", ".join(sorted(stylelist))
644 642
645 643 class TemplateNotFound(util.Abort):
646 644 pass
647 645
648 646 class templater(object):
649 647
650 648 def __init__(self, mapfile, filters={}, defaults={}, cache={},
651 649 minchunk=1024, maxchunk=65536):
652 650 '''set up template engine.
653 651 mapfile is name of file to read map definitions from.
654 652 filters is dict of functions. each transforms a value into another.
655 653 defaults is dict of default map definitions.'''
656 654 self.mapfile = mapfile or 'template'
657 655 self.cache = cache.copy()
658 656 self.map = {}
659 657 self.base = (mapfile and os.path.dirname(mapfile)) or ''
660 658 self.filters = templatefilters.filters.copy()
661 659 self.filters.update(filters)
662 660 self.defaults = defaults
663 661 self.minchunk, self.maxchunk = minchunk, maxchunk
664 662 self.ecache = {}
665 663
666 664 if not mapfile:
667 665 return
668 666 if not os.path.exists(mapfile):
669 667 raise util.Abort(_("style '%s' not found") % mapfile,
670 668 hint=_("available styles: %s") % stylelist())
671 669
672 670 conf = config.config()
673 671 conf.read(mapfile)
674 672
675 673 for key, val in conf[''].items():
676 674 if not val:
677 675 raise SyntaxError(_('%s: missing value') % conf.source('', key))
678 676 if val[0] in "'\"":
679 677 try:
680 678 self.cache[key] = parsestring(val)
681 679 except SyntaxError, inst:
682 680 raise SyntaxError('%s: %s' %
683 681 (conf.source('', key), inst.args[0]))
684 682 else:
685 683 val = 'default', val
686 684 if ':' in val[1]:
687 685 val = val[1].split(':', 1)
688 686 self.map[key] = val[0], os.path.join(self.base, val[1])
689 687
690 688 def __contains__(self, key):
691 689 return key in self.cache or key in self.map
692 690
693 691 def load(self, t):
694 692 '''Get the template for the given template name. Use a local cache.'''
695 693 if t not in self.cache:
696 694 try:
697 695 self.cache[t] = util.readfile(self.map[t][1])
698 696 except KeyError, inst:
699 697 raise TemplateNotFound(_('"%s" not in template map') %
700 698 inst.args[0])
701 699 except IOError, inst:
702 700 raise IOError(inst.args[0], _('template file %s: %s') %
703 701 (self.map[t][1], inst.args[1]))
704 702 return self.cache[t]
705 703
706 704 def __call__(self, t, **mapping):
707 705 ttype = t in self.map and self.map[t][0] or 'default'
708 706 if ttype not in self.ecache:
709 707 self.ecache[ttype] = engines[ttype](self.load,
710 708 self.filters, self.defaults)
711 709 proc = self.ecache[ttype]
712 710
713 711 stream = proc.process(t, mapping)
714 712 if self.minchunk:
715 713 stream = util.increasingchunks(stream, min=self.minchunk,
716 714 max=self.maxchunk)
717 715 return stream
718 716
719 717 def templatepaths():
720 718 '''return locations used for template files.'''
721 719 pathsrel = ['templates']
722 720 paths = [os.path.normpath(os.path.join(util.datapath, f))
723 721 for f in pathsrel]
724 722 return [p for p in paths if os.path.isdir(p)]
725 723
726 724 def templatepath(name):
727 725 '''return location of template file. returns None if not found.'''
728 726 for p in templatepaths():
729 727 f = os.path.join(p, name)
730 728 if os.path.exists(f):
731 729 return f
732 730 return None
733 731
734 732 def stylemap(styles, paths=None):
735 733 """Return path to mapfile for a given style.
736 734
737 735 Searches mapfile in the following locations:
738 736 1. templatepath/style/map
739 737 2. templatepath/map-style
740 738 3. templatepath/map
741 739 """
742 740
743 741 if paths is None:
744 742 paths = templatepaths()
745 743 elif isinstance(paths, str):
746 744 paths = [paths]
747 745
748 746 if isinstance(styles, str):
749 747 styles = [styles]
750 748
751 749 for style in styles:
752 750 if not style:
753 751 continue
754 752 locations = [os.path.join(style, 'map'), 'map-' + style]
755 753 locations.append('map')
756 754
757 755 for path in paths:
758 756 for location in locations:
759 757 mapfile = os.path.join(path, location)
760 758 if os.path.isfile(mapfile):
761 759 return style, mapfile
762 760
763 761 raise RuntimeError("No hgweb templates found in %r" % paths)
@@ -1,150 +1,156 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > convert=
4 4 > [convert]
5 5 > hg.saverev=False
6 6 > EOF
7 7 $ hg init orig
8 8 $ cd orig
9 9 $ echo foo > foo
10 10 $ echo bar > bar
11 11 $ hg ci -qAm 'add foo bar' -d '0 0'
12 12 $ echo >> foo
13 13 $ hg ci -m 'change foo' -d '1 0'
14 14 $ hg up -qC 0
15 15 $ hg copy --after --force foo bar
16 16 $ hg copy foo baz
17 17 $ hg ci -m 'make bar and baz copies of foo' -d '2 0'
18 18 created new head
19
20 Test that template can print all file copies (issue4362)
21 $ hg log -r . --template "{file_copies % ' File: {file_copy}\n'}"
22 File: bar (foo)
23 File: baz (foo)
24
19 25 $ hg bookmark premerge1
20 26 $ hg merge -r 1
21 27 merging baz and foo to baz
22 28 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
23 29 (branch merge, don't forget to commit)
24 30 $ hg ci -m 'merge local copy' -d '3 0'
25 31 $ hg up -C 1
26 32 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
27 33 (leaving bookmark premerge1)
28 34 $ hg bookmark premerge2
29 35 $ hg merge 2
30 36 merging foo and baz to baz
31 37 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
32 38 (branch merge, don't forget to commit)
33 39 $ hg ci -m 'merge remote copy' -d '4 0'
34 40 created new head
35 41 #if execbit
36 42 $ chmod +x baz
37 43 #else
38 44 $ echo some other change to make sure we get a rev 5 > baz
39 45 #endif
40 46 $ hg ci -m 'mark baz executable' -d '5 0'
41 47 $ cd ..
42 48 $ hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
43 49 initializing destination new repository
44 50 scanning source...
45 51 sorting...
46 52 converting...
47 53 5 add foo bar
48 54 4 change foo
49 55 3 make bar and baz copies of foo
50 56 2 merge local copy
51 57 1 merge remote copy
52 58 0 mark baz executable
53 59 updating bookmarks
54 60 $ cd new
55 61 $ hg out ../orig
56 62 comparing with ../orig
57 63 searching for changes
58 64 no changes found
59 65 [1]
60 66 #if execbit
61 67 $ hg bookmarks
62 68 premerge1 3:973ef48a98a4
63 69 premerge2 5:13d9b87cf8f8
64 70 #else
65 71 Different hash because no x bit
66 72 $ hg bookmarks
67 73 premerge1 3:973ef48a98a4
68 74 premerge2 5:df0779bcf33c
69 75 #endif
70 76 $ cd ..
71 77
72 78 check shamap LF and CRLF handling
73 79
74 80 $ cat > rewrite.py <<EOF
75 81 > import sys
76 82 > # Interlace LF and CRLF
77 83 > lines = [(l.rstrip() + ((i % 2) and '\n' or '\r\n'))
78 84 > for i, l in enumerate(file(sys.argv[1]))]
79 85 > file(sys.argv[1], 'wb').write(''.join(lines))
80 86 > EOF
81 87 $ python rewrite.py new/.hg/shamap
82 88 $ cd orig
83 89 $ hg up -qC 1
84 90 $ echo foo >> foo
85 91 $ hg ci -qm 'change foo again'
86 92 $ hg up -qC 2
87 93 $ echo foo >> foo
88 94 $ hg ci -qm 'change foo again again'
89 95 $ cd ..
90 96 $ hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
91 97 scanning source...
92 98 sorting...
93 99 converting...
94 100 1 change foo again again
95 101 0 change foo again
96 102 updating bookmarks
97 103
98 104 init broken repository
99 105
100 106 $ hg init broken
101 107 $ cd broken
102 108 $ echo a >> a
103 109 $ echo b >> b
104 110 $ hg ci -qAm init
105 111 $ echo a >> a
106 112 $ echo b >> b
107 113 $ hg copy b c
108 114 $ hg ci -qAm changeall
109 115 $ hg up -qC 0
110 116 $ echo bc >> b
111 117 $ hg ci -m changebagain
112 118 created new head
113 119 $ HGMERGE=internal:local hg -q merge
114 120 $ hg ci -m merge
115 121 $ hg mv b d
116 122 $ hg ci -m moveb
117 123
118 124 break it
119 125
120 126 $ rm .hg/store/data/b.*
121 127 $ cd ..
122 128 $ hg --config convert.hg.ignoreerrors=True convert broken fixed
123 129 initializing destination fixed repository
124 130 scanning source...
125 131 sorting...
126 132 converting...
127 133 4 init
128 134 ignoring: data/b.i@1e88685f5dde: no match found
129 135 3 changeall
130 136 2 changebagain
131 137 1 merge
132 138 0 moveb
133 139 $ hg -R fixed verify
134 140 checking changesets
135 141 checking manifests
136 142 crosschecking files in changesets and manifests
137 143 checking files
138 144 3 files, 5 changesets, 5 total revisions
139 145
140 146 manifest -r 0
141 147
142 148 $ hg -R fixed manifest -r 0
143 149 a
144 150
145 151 manifest -r tip
146 152
147 153 $ hg -R fixed manifest -r tip
148 154 a
149 155 c
150 156 d
@@ -1,691 +1,697 b''
1 1 Create a repo with some stuff in it:
2 2
3 3 $ hg init a
4 4 $ cd a
5 5 $ echo a > a
6 6 $ echo a > d
7 7 $ echo a > e
8 8 $ hg ci -qAm0
9 9 $ echo b > a
10 10 $ hg ci -m1 -u bar
11 11 $ hg mv a b
12 12 $ hg ci -m2
13 13 $ hg cp b c
14 14 $ hg ci -m3 -u baz
15 15 $ echo b > d
16 16 $ echo f > e
17 17 $ hg ci -m4
18 18 $ hg up -q 3
19 19 $ echo b > e
20 20 $ hg branch -q stable
21 21 $ hg ci -m5
22 22 $ hg merge -q default --tool internal:local
23 23 $ hg branch -q default
24 24 $ hg ci -m6
25 25 $ hg phase --public 3
26 26 $ hg phase --force --secret 6
27 27
28 28 $ hg log -G --template '{author}@{rev}.{phase}: {desc}\n'
29 29 @ test@6.secret: 6
30 30 |\
31 31 | o test@5.draft: 5
32 32 | |
33 33 o | test@4.draft: 4
34 34 |/
35 35 o baz@3.public: 3
36 36 |
37 37 o test@2.public: 2
38 38 |
39 39 o bar@1.public: 1
40 40 |
41 41 o test@0.public: 0
42 42
43 43
44 44 Need to specify a rev:
45 45
46 46 $ hg graft
47 47 abort: no revisions specified
48 48 [255]
49 49
50 50 Can't graft ancestor:
51 51
52 52 $ hg graft 1 2
53 53 skipping ancestor revision 1
54 54 skipping ancestor revision 2
55 55 [255]
56 56
57 57 Specify revisions with -r:
58 58
59 59 $ hg graft -r 1 -r 2
60 60 skipping ancestor revision 1
61 61 skipping ancestor revision 2
62 62 [255]
63 63
64 64 $ hg graft -r 1 2
65 65 skipping ancestor revision 2
66 66 skipping ancestor revision 1
67 67 [255]
68 68
69 69 Can't graft with dirty wd:
70 70
71 71 $ hg up -q 0
72 72 $ echo foo > a
73 73 $ hg graft 1
74 74 abort: uncommitted changes
75 75 [255]
76 76 $ hg revert a
77 77
78 78 Graft a rename:
79 79 (this also tests that editor is invoked if '--edit' is specified)
80 80
81 81 $ hg status --rev "2^1" --rev 2
82 82 A b
83 83 R a
84 84 $ HGEDITOR=cat hg graft 2 -u foo --edit
85 85 grafting revision 2
86 86 merging a and b to b
87 87 2
88 88
89 89
90 90 HG: Enter commit message. Lines beginning with 'HG:' are removed.
91 91 HG: Leave message empty to abort commit.
92 92 HG: --
93 93 HG: user: foo
94 94 HG: branch 'default'
95 95 HG: added b
96 96 HG: removed a
97 97 $ hg export tip --git
98 98 # HG changeset patch
99 99 # User foo
100 100 # Date 0 0
101 101 # Thu Jan 01 00:00:00 1970 +0000
102 102 # Node ID ef0ef43d49e79e81ddafdc7997401ba0041efc82
103 103 # Parent 68795b066622ca79a25816a662041d8f78f3cd9e
104 104 2
105 105
106 106 diff --git a/a b/b
107 107 rename from a
108 108 rename to b
109 109
110 110 Look for extra:source
111 111
112 112 $ hg log --debug -r tip
113 113 changeset: 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
114 114 tag: tip
115 115 phase: draft
116 116 parent: 0:68795b066622ca79a25816a662041d8f78f3cd9e
117 117 parent: -1:0000000000000000000000000000000000000000
118 118 manifest: 7:e59b6b228f9cbf9903d5e9abf996e083a1f533eb
119 119 user: foo
120 120 date: Thu Jan 01 00:00:00 1970 +0000
121 121 files+: b
122 122 files-: a
123 123 extra: branch=default
124 124 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
125 125 description:
126 126 2
127 127
128 128
129 129
130 130 Graft out of order, skipping a merge and a duplicate
131 131 (this also tests that editor is not invoked if '--edit' is not specified)
132 132
133 133 $ hg graft 1 5 4 3 'merge()' 2 -n
134 134 skipping ungraftable merge revision 6
135 135 skipping revision 2 (already grafted to 7)
136 136 grafting revision 1
137 137 grafting revision 5
138 138 grafting revision 4
139 139 grafting revision 3
140 140
141 141 $ HGEDITOR=cat hg graft 1 5 4 3 'merge()' 2 --debug
142 142 skipping ungraftable merge revision 6
143 143 scanning for duplicate grafts
144 144 skipping revision 2 (already grafted to 7)
145 145 grafting revision 1
146 146 searching for copies back to rev 1
147 147 unmatched files in local:
148 148 b
149 149 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
150 150 src: 'a' -> dst: 'b' *
151 151 checking for directory renames
152 152 resolving manifests
153 153 branchmerge: True, force: True, partial: False
154 154 ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
155 155 preserving b for resolve of b
156 156 b: local copied/moved from a -> m
157 157 updating: b 1/1 files (100.00%)
158 158 picked tool 'internal:merge' for b (binary False symlink False)
159 159 merging b and a to b
160 160 my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622
161 161 premerge successful
162 162 b
163 163 grafting revision 5
164 164 searching for copies back to rev 1
165 165 resolving manifests
166 166 branchmerge: True, force: True, partial: False
167 167 ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
168 168 e: remote is newer -> g
169 169 getting e
170 170 updating: e 1/1 files (100.00%)
171 171 b: keep -> k
172 172 e
173 173 grafting revision 4
174 174 searching for copies back to rev 1
175 175 resolving manifests
176 176 branchmerge: True, force: True, partial: False
177 177 ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d
178 178 preserving e for resolve of e
179 179 d: remote is newer -> g
180 180 getting d
181 181 updating: d 1/2 files (50.00%)
182 182 b: keep -> k
183 183 e: versions differ -> m
184 184 updating: e 2/2 files (100.00%)
185 185 picked tool 'internal:merge' for e (binary False symlink False)
186 186 merging e
187 187 my e@1905859650ec+ other e@9c233e8e184d ancestor e@68795b066622
188 188 warning: conflicts during merge.
189 189 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
190 190 abort: unresolved conflicts, can't continue
191 191 (use hg resolve and hg graft --continue)
192 192 [255]
193 193
194 194 Commit while interrupted should fail:
195 195
196 196 $ hg ci -m 'commit interrupted graft'
197 197 abort: graft in progress
198 198 (use 'hg graft --continue' or 'hg update' to abort)
199 199 [255]
200 200
201 201 Abort the graft and try committing:
202 202
203 203 $ hg up -C .
204 204 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
205 205 $ echo c >> e
206 206 $ hg ci -mtest
207 207
208 208 $ hg strip . --config extensions.mq=
209 209 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
210 210 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
211 211
212 212 Graft again:
213 213
214 214 $ hg graft 1 5 4 3 'merge()' 2
215 215 skipping ungraftable merge revision 6
216 216 skipping revision 2 (already grafted to 7)
217 217 skipping revision 1 (already grafted to 8)
218 218 skipping revision 5 (already grafted to 9)
219 219 grafting revision 4
220 220 merging e
221 221 warning: conflicts during merge.
222 222 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
223 223 abort: unresolved conflicts, can't continue
224 224 (use hg resolve and hg graft --continue)
225 225 [255]
226 226
227 227 Continue without resolve should fail:
228 228
229 229 $ hg graft -c
230 230 grafting revision 4
231 231 abort: unresolved merge conflicts (see hg help resolve)
232 232 [255]
233 233
234 234 Fix up:
235 235
236 236 $ echo b > e
237 237 $ hg resolve -m e
238 238 (no more unresolved files)
239 239
240 240 Continue with a revision should fail:
241 241
242 242 $ hg graft -c 6
243 243 abort: can't specify --continue and revisions
244 244 [255]
245 245
246 246 $ hg graft -c -r 6
247 247 abort: can't specify --continue and revisions
248 248 [255]
249 249
250 250 Continue for real, clobber usernames
251 251
252 252 $ hg graft -c -U
253 253 grafting revision 4
254 254 grafting revision 3
255 255
256 256 Compare with original:
257 257
258 258 $ hg diff -r 6
259 259 $ hg status --rev 0:. -C
260 260 M d
261 261 M e
262 262 A b
263 263 a
264 264 A c
265 265 a
266 266 R a
267 267
268 268 View graph:
269 269
270 270 $ hg log -G --template '{author}@{rev}.{phase}: {desc}\n'
271 271 @ test@11.draft: 3
272 272 |
273 273 o test@10.draft: 4
274 274 |
275 275 o test@9.draft: 5
276 276 |
277 277 o bar@8.draft: 1
278 278 |
279 279 o foo@7.draft: 2
280 280 |
281 281 | o test@6.secret: 6
282 282 | |\
283 283 | | o test@5.draft: 5
284 284 | | |
285 285 | o | test@4.draft: 4
286 286 | |/
287 287 | o baz@3.public: 3
288 288 | |
289 289 | o test@2.public: 2
290 290 | |
291 291 | o bar@1.public: 1
292 292 |/
293 293 o test@0.public: 0
294 294
295 295 Graft again onto another branch should preserve the original source
296 296 $ hg up -q 0
297 297 $ echo 'g'>g
298 298 $ hg add g
299 299 $ hg ci -m 7
300 300 created new head
301 301 $ hg graft 7
302 302 grafting revision 7
303 303
304 304 $ hg log -r 7 --template '{rev}:{node}\n'
305 305 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
306 306 $ hg log -r 2 --template '{rev}:{node}\n'
307 307 2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
308 308
309 309 $ hg log --debug -r tip
310 310 changeset: 13:9db0f28fd3747e92c57d015f53b5593aeec53c2d
311 311 tag: tip
312 312 phase: draft
313 313 parent: 12:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
314 314 parent: -1:0000000000000000000000000000000000000000
315 315 manifest: 13:dc313617b8c32457c0d589e0dbbedfe71f3cd637
316 316 user: foo
317 317 date: Thu Jan 01 00:00:00 1970 +0000
318 318 files+: b
319 319 files-: a
320 320 extra: branch=default
321 321 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
322 322 description:
323 323 2
324 324
325 325
326 326 Disallow grafting an already grafted cset onto its original branch
327 327 $ hg up -q 6
328 328 $ hg graft 7
329 329 skipping already grafted revision 7 (was grafted from 2)
330 330 [255]
331 331
332 332 Disallow grafting already grafted csets with the same origin onto each other
333 333 $ hg up -q 13
334 334 $ hg graft 2
335 335 skipping revision 2 (already grafted to 13)
336 336 [255]
337 337 $ hg graft 7
338 338 skipping already grafted revision 7 (13 also has origin 2)
339 339 [255]
340 340
341 341 $ hg up -q 7
342 342 $ hg graft 2
343 343 skipping revision 2 (already grafted to 7)
344 344 [255]
345 345 $ hg graft tip
346 346 skipping already grafted revision 13 (7 also has origin 2)
347 347 [255]
348 348
349 349 Graft with --log
350 350
351 351 $ hg up -Cq 1
352 352 $ hg graft 3 --log -u foo
353 353 grafting revision 3
354 354 warning: can't find ancestor for 'c' copied from 'b'!
355 355 $ hg log --template '{rev} {parents} {desc}\n' -r tip
356 356 14 1:5d205f8b35b6 3
357 357 (grafted from 4c60f11aa304a54ae1c199feb94e7fc771e51ed8)
358 358
359 359 Resolve conflicted graft
360 360 $ hg up -q 0
361 361 $ echo b > a
362 362 $ hg ci -m 8
363 363 created new head
364 364 $ echo a > a
365 365 $ hg ci -m 9
366 366 $ hg graft 1 --tool internal:fail
367 367 grafting revision 1
368 368 abort: unresolved conflicts, can't continue
369 369 (use hg resolve and hg graft --continue)
370 370 [255]
371 371 $ hg resolve --all
372 372 merging a
373 373 (no more unresolved files)
374 374 $ hg graft -c
375 375 grafting revision 1
376 376 $ hg export tip --git
377 377 # HG changeset patch
378 378 # User bar
379 379 # Date 0 0
380 380 # Thu Jan 01 00:00:00 1970 +0000
381 381 # Node ID 64ecd9071ce83c6e62f538d8ce7709d53f32ebf7
382 382 # Parent 4bdb9a9d0b84ffee1d30f0dfc7744cade17aa19c
383 383 1
384 384
385 385 diff --git a/a b/a
386 386 --- a/a
387 387 +++ b/a
388 388 @@ -1,1 +1,1 @@
389 389 -a
390 390 +b
391 391
392 392 Resolve conflicted graft with rename
393 393 $ echo c > a
394 394 $ hg ci -m 10
395 395 $ hg graft 2 --tool internal:fail
396 396 grafting revision 2
397 397 abort: unresolved conflicts, can't continue
398 398 (use hg resolve and hg graft --continue)
399 399 [255]
400 400 $ hg resolve --all
401 401 merging a and b to b
402 402 (no more unresolved files)
403 403 $ hg graft -c
404 404 grafting revision 2
405 405 $ hg export tip --git
406 406 # HG changeset patch
407 407 # User test
408 408 # Date 0 0
409 409 # Thu Jan 01 00:00:00 1970 +0000
410 410 # Node ID 2e80e1351d6ed50302fe1e05f8bd1d4d412b6e11
411 411 # Parent e5a51ae854a8bbaaf25cc5c6a57ff46042dadbb4
412 412 2
413 413
414 414 diff --git a/a b/b
415 415 rename from a
416 416 rename to b
417 417
418 418 Test simple origin(), with and without args
419 419 $ hg log -r 'origin()'
420 420 changeset: 1:5d205f8b35b6
421 421 user: bar
422 422 date: Thu Jan 01 00:00:00 1970 +0000
423 423 summary: 1
424 424
425 425 changeset: 2:5c095ad7e90f
426 426 user: test
427 427 date: Thu Jan 01 00:00:00 1970 +0000
428 428 summary: 2
429 429
430 430 changeset: 3:4c60f11aa304
431 431 user: baz
432 432 date: Thu Jan 01 00:00:00 1970 +0000
433 433 summary: 3
434 434
435 435 changeset: 4:9c233e8e184d
436 436 user: test
437 437 date: Thu Jan 01 00:00:00 1970 +0000
438 438 summary: 4
439 439
440 440 changeset: 5:97f8bfe72746
441 441 branch: stable
442 442 parent: 3:4c60f11aa304
443 443 user: test
444 444 date: Thu Jan 01 00:00:00 1970 +0000
445 445 summary: 5
446 446
447 447 $ hg log -r 'origin(7)'
448 448 changeset: 2:5c095ad7e90f
449 449 user: test
450 450 date: Thu Jan 01 00:00:00 1970 +0000
451 451 summary: 2
452 452
453 453 Now transplant a graft to test following through copies
454 454 $ hg up -q 0
455 455 $ hg branch -q dev
456 456 $ hg ci -qm "dev branch"
457 457 $ hg --config extensions.transplant= transplant -q 7
458 458 $ hg log -r 'origin(.)'
459 459 changeset: 2:5c095ad7e90f
460 460 user: test
461 461 date: Thu Jan 01 00:00:00 1970 +0000
462 462 summary: 2
463 463
464 464 Test that the graft and transplant markers in extra are converted, allowing
465 465 origin() to still work. Note that these recheck the immediately preceeding two
466 466 tests.
467 467 $ hg --quiet --config extensions.convert= --config convert.hg.saverev=True convert . ../converted
468 468
469 469 The graft case
470 470 $ hg -R ../converted log -r 7 --template "{rev}: {node}\n{join(extras, '\n')}\n"
471 471 7: 7ae846e9111fc8f57745634250c7b9ac0a60689b
472 472 branch=default
473 473 convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82
474 474 source=e0213322b2c1a5d5d236c74e79666441bee67a7d
475 475 $ hg -R ../converted log -r 'origin(7)'
476 476 changeset: 2:e0213322b2c1
477 477 user: test
478 478 date: Thu Jan 01 00:00:00 1970 +0000
479 479 summary: 2
480 480
481 Test that template correctly expands more than one 'extra' (issue4362)
482 $ hg -R ../converted log -r 7 --template "{extras % ' Extra: {extra}\n'}"
483 Extra: branch=default
484 Extra: convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82
485 Extra: source=e0213322b2c1a5d5d236c74e79666441bee67a7d
486
481 487 The transplant case
482 488 $ hg -R ../converted log -r tip --template "{rev}: {node}\n{join(extras, '\n')}\n"
483 489 21: fbb6c5cc81002f2b4b49c9d731404688bcae5ade
484 490 branch=dev
485 491 convert_revision=7e61b508e709a11d28194a5359bc3532d910af21
486 492 transplant_source=z\xe8F\xe9\x11\x1f\xc8\xf5wEcBP\xc7\xb9\xac (esc)
487 493 `h\x9b (esc)
488 494 $ hg -R ../converted log -r 'origin(tip)'
489 495 changeset: 2:e0213322b2c1
490 496 user: test
491 497 date: Thu Jan 01 00:00:00 1970 +0000
492 498 summary: 2
493 499
494 500
495 501 Test simple destination
496 502 $ hg log -r 'destination()'
497 503 changeset: 7:ef0ef43d49e7
498 504 parent: 0:68795b066622
499 505 user: foo
500 506 date: Thu Jan 01 00:00:00 1970 +0000
501 507 summary: 2
502 508
503 509 changeset: 8:6b9e5368ca4e
504 510 user: bar
505 511 date: Thu Jan 01 00:00:00 1970 +0000
506 512 summary: 1
507 513
508 514 changeset: 9:1905859650ec
509 515 user: test
510 516 date: Thu Jan 01 00:00:00 1970 +0000
511 517 summary: 5
512 518
513 519 changeset: 10:52dc0b4c6907
514 520 user: test
515 521 date: Thu Jan 01 00:00:00 1970 +0000
516 522 summary: 4
517 523
518 524 changeset: 11:882b35362a6b
519 525 user: test
520 526 date: Thu Jan 01 00:00:00 1970 +0000
521 527 summary: 3
522 528
523 529 changeset: 13:9db0f28fd374
524 530 user: foo
525 531 date: Thu Jan 01 00:00:00 1970 +0000
526 532 summary: 2
527 533
528 534 changeset: 14:f64defefacee
529 535 parent: 1:5d205f8b35b6
530 536 user: foo
531 537 date: Thu Jan 01 00:00:00 1970 +0000
532 538 summary: 3
533 539
534 540 changeset: 17:64ecd9071ce8
535 541 user: bar
536 542 date: Thu Jan 01 00:00:00 1970 +0000
537 543 summary: 1
538 544
539 545 changeset: 19:2e80e1351d6e
540 546 user: test
541 547 date: Thu Jan 01 00:00:00 1970 +0000
542 548 summary: 2
543 549
544 550 changeset: 21:7e61b508e709
545 551 branch: dev
546 552 tag: tip
547 553 user: foo
548 554 date: Thu Jan 01 00:00:00 1970 +0000
549 555 summary: 2
550 556
551 557 $ hg log -r 'destination(2)'
552 558 changeset: 7:ef0ef43d49e7
553 559 parent: 0:68795b066622
554 560 user: foo
555 561 date: Thu Jan 01 00:00:00 1970 +0000
556 562 summary: 2
557 563
558 564 changeset: 13:9db0f28fd374
559 565 user: foo
560 566 date: Thu Jan 01 00:00:00 1970 +0000
561 567 summary: 2
562 568
563 569 changeset: 19:2e80e1351d6e
564 570 user: test
565 571 date: Thu Jan 01 00:00:00 1970 +0000
566 572 summary: 2
567 573
568 574 changeset: 21:7e61b508e709
569 575 branch: dev
570 576 tag: tip
571 577 user: foo
572 578 date: Thu Jan 01 00:00:00 1970 +0000
573 579 summary: 2
574 580
575 581 Transplants of grafts can find a destination...
576 582 $ hg log -r 'destination(7)'
577 583 changeset: 21:7e61b508e709
578 584 branch: dev
579 585 tag: tip
580 586 user: foo
581 587 date: Thu Jan 01 00:00:00 1970 +0000
582 588 summary: 2
583 589
584 590 ... grafts of grafts unfortunately can't
585 591 $ hg graft -q 13
586 592 $ hg log -r 'destination(13)'
587 593 All copies of a cset
588 594 $ hg log -r 'origin(13) or destination(origin(13))'
589 595 changeset: 2:5c095ad7e90f
590 596 user: test
591 597 date: Thu Jan 01 00:00:00 1970 +0000
592 598 summary: 2
593 599
594 600 changeset: 7:ef0ef43d49e7
595 601 parent: 0:68795b066622
596 602 user: foo
597 603 date: Thu Jan 01 00:00:00 1970 +0000
598 604 summary: 2
599 605
600 606 changeset: 13:9db0f28fd374
601 607 user: foo
602 608 date: Thu Jan 01 00:00:00 1970 +0000
603 609 summary: 2
604 610
605 611 changeset: 19:2e80e1351d6e
606 612 user: test
607 613 date: Thu Jan 01 00:00:00 1970 +0000
608 614 summary: 2
609 615
610 616 changeset: 21:7e61b508e709
611 617 branch: dev
612 618 user: foo
613 619 date: Thu Jan 01 00:00:00 1970 +0000
614 620 summary: 2
615 621
616 622 changeset: 22:1313d0a825e2
617 623 branch: dev
618 624 tag: tip
619 625 user: foo
620 626 date: Thu Jan 01 00:00:00 1970 +0000
621 627 summary: 2
622 628
623 629
624 630 graft works on complex revset
625 631
626 632 $ hg graft 'origin(13) or destination(origin(13))'
627 633 skipping ancestor revision 21
628 634 skipping ancestor revision 22
629 635 skipping revision 2 (already grafted to 22)
630 636 grafting revision 7
631 637 grafting revision 13
632 638 grafting revision 19
633 639 merging b
634 640
635 641 graft with --force (still doesn't graft merges)
636 642
637 643 $ hg graft 19 0 6
638 644 skipping ungraftable merge revision 6
639 645 skipping ancestor revision 0
640 646 skipping already grafted revision 19 (22 also has origin 2)
641 647 [255]
642 648 $ hg graft 19 0 6 --force
643 649 skipping ungraftable merge revision 6
644 650 grafting revision 19
645 651 merging b
646 652 grafting revision 0
647 653
648 654 graft --force after backout
649 655
650 656 $ echo abc > a
651 657 $ hg ci -m 28
652 658 $ hg backout 28
653 659 reverting a
654 660 changeset 29:484c03b8dfa4 backs out changeset 28:6c56f0f7f033
655 661 $ hg graft 28
656 662 skipping ancestor revision 28
657 663 [255]
658 664 $ hg graft 28 --force
659 665 grafting revision 28
660 666 merging a
661 667 $ cat a
662 668 abc
663 669
664 670 graft --continue after --force
665 671
666 672 $ hg backout 30
667 673 reverting a
668 674 changeset 31:3b96c18b7a1b backs out changeset 30:8f539994be33
669 675 $ hg graft 28 --force --tool internal:fail
670 676 grafting revision 28
671 677 abort: unresolved conflicts, can't continue
672 678 (use hg resolve and hg graft --continue)
673 679 [255]
674 680 $ hg resolve --all
675 681 merging a
676 682 (no more unresolved files)
677 683 $ hg graft -c
678 684 grafting revision 28
679 685 $ cat a
680 686 abc
681 687
682 688 Continue testing same origin policy, using revision numbers from test above
683 689 but do some destructive editing of the repo:
684 690
685 691 $ hg up -qC 7
686 692 $ hg tag -l -r 13 tmp
687 693 $ hg --config extensions.mq= strip 2
688 694 saved backup bundle to $TESTTMP/a/.hg/strip-backup/5c095ad7e90f-backup.hg (glob)
689 695 $ hg graft tmp
690 696 skipping already grafted revision 8 (2 also has unknown origin 5c095ad7e90f871700f02dd1fa5012cb4498a2d4)
691 697 [255]
General Comments 0
You need to be logged in to leave comments. Login now