##// END OF EJS Templates
i18n: add/relocate "i18n keyword" comments for i18n messages in revset.py
FUJIWARA Katsunori -
r17259:e96ad092 stable
parent child Browse files
Show More
@@ -1,1834 +1,1854 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 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 import re
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node
11 11 import bookmarks as bookmarksmod
12 12 import match as matchmod
13 13 from i18n import _
14 14 import encoding
15 15
16 16 def _revancestors(repo, revs, followfirst):
17 17 """Like revlog.ancestors(), but supports followfirst."""
18 18 cut = followfirst and 1 or None
19 19 cl = repo.changelog
20 20 visit = util.deque(revs)
21 21 seen = set([node.nullrev])
22 22 while visit:
23 23 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 24 if parent not in seen:
25 25 visit.append(parent)
26 26 seen.add(parent)
27 27 yield parent
28 28
29 29 def _revdescendants(repo, revs, followfirst):
30 30 """Like revlog.descendants() but supports followfirst."""
31 31 cut = followfirst and 1 or None
32 32 cl = repo.changelog
33 33 first = min(revs)
34 34 nullrev = node.nullrev
35 35 if first == nullrev:
36 36 # Are there nodes with a null first parent and a non-null
37 37 # second one? Maybe. Do we care? Probably not.
38 38 for i in cl:
39 39 yield i
40 40 return
41 41
42 42 seen = set(revs)
43 43 for i in xrange(first + 1, len(cl)):
44 44 for x in cl.parentrevs(i)[:cut]:
45 45 if x != nullrev and x in seen:
46 46 seen.add(i)
47 47 yield i
48 48 break
49 49
50 50 def _revsbetween(repo, roots, heads):
51 51 """Return all paths between roots and heads, inclusive of both endpoint
52 52 sets."""
53 53 if not roots:
54 54 return []
55 55 parentrevs = repo.changelog.parentrevs
56 56 visit = heads[:]
57 57 reachable = set()
58 58 seen = {}
59 59 minroot = min(roots)
60 60 roots = set(roots)
61 61 # open-code the post-order traversal due to the tiny size of
62 62 # sys.getrecursionlimit()
63 63 while visit:
64 64 rev = visit.pop()
65 65 if rev in roots:
66 66 reachable.add(rev)
67 67 parents = parentrevs(rev)
68 68 seen[rev] = parents
69 69 for parent in parents:
70 70 if parent >= minroot and parent not in seen:
71 71 visit.append(parent)
72 72 if not reachable:
73 73 return []
74 74 for rev in sorted(seen):
75 75 for parent in seen[rev]:
76 76 if parent in reachable:
77 77 reachable.add(rev)
78 78 return sorted(reachable)
79 79
80 80 elements = {
81 81 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
82 82 "~": (18, None, ("ancestor", 18)),
83 83 "^": (18, None, ("parent", 18), ("parentpost", 18)),
84 84 "-": (5, ("negate", 19), ("minus", 5)),
85 85 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
86 86 ("dagrangepost", 17)),
87 87 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
88 88 ("dagrangepost", 17)),
89 89 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
90 90 "not": (10, ("not", 10)),
91 91 "!": (10, ("not", 10)),
92 92 "and": (5, None, ("and", 5)),
93 93 "&": (5, None, ("and", 5)),
94 94 "or": (4, None, ("or", 4)),
95 95 "|": (4, None, ("or", 4)),
96 96 "+": (4, None, ("or", 4)),
97 97 ",": (2, None, ("list", 2)),
98 98 ")": (0, None, None),
99 99 "symbol": (0, ("symbol",), None),
100 100 "string": (0, ("string",), None),
101 101 "end": (0, None, None),
102 102 }
103 103
104 104 keywords = set(['and', 'or', 'not'])
105 105
106 106 def tokenize(program):
107 107 pos, l = 0, len(program)
108 108 while pos < l:
109 109 c = program[pos]
110 110 if c.isspace(): # skip inter-token whitespace
111 111 pass
112 112 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
113 113 yield ('::', None, pos)
114 114 pos += 1 # skip ahead
115 115 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
116 116 yield ('..', None, pos)
117 117 pos += 1 # skip ahead
118 118 elif c in "():,-|&+!~^": # handle simple operators
119 119 yield (c, None, pos)
120 120 elif (c in '"\'' or c == 'r' and
121 121 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
122 122 if c == 'r':
123 123 pos += 1
124 124 c = program[pos]
125 125 decode = lambda x: x
126 126 else:
127 127 decode = lambda x: x.decode('string-escape')
128 128 pos += 1
129 129 s = pos
130 130 while pos < l: # find closing quote
131 131 d = program[pos]
132 132 if d == '\\': # skip over escaped characters
133 133 pos += 2
134 134 continue
135 135 if d == c:
136 136 yield ('string', decode(program[s:pos]), s)
137 137 break
138 138 pos += 1
139 139 else:
140 140 raise error.ParseError(_("unterminated string"), s)
141 141 # gather up a symbol/keyword
142 142 elif c.isalnum() or c in '._' or ord(c) > 127:
143 143 s = pos
144 144 pos += 1
145 145 while pos < l: # find end of symbol
146 146 d = program[pos]
147 147 if not (d.isalnum() or d in "._/" or ord(d) > 127):
148 148 break
149 149 if d == '.' and program[pos - 1] == '.': # special case for ..
150 150 pos -= 1
151 151 break
152 152 pos += 1
153 153 sym = program[s:pos]
154 154 if sym in keywords: # operator keywords
155 155 yield (sym, None, s)
156 156 else:
157 157 yield ('symbol', sym, s)
158 158 pos -= 1
159 159 else:
160 160 raise error.ParseError(_("syntax error"), pos)
161 161 pos += 1
162 162 yield ('end', None, pos)
163 163
164 164 # helpers
165 165
166 166 def getstring(x, err):
167 167 if x and (x[0] == 'string' or x[0] == 'symbol'):
168 168 return x[1]
169 169 raise error.ParseError(err)
170 170
171 171 def getlist(x):
172 172 if not x:
173 173 return []
174 174 if x[0] == 'list':
175 175 return getlist(x[1]) + [x[2]]
176 176 return [x]
177 177
178 178 def getargs(x, min, max, err):
179 179 l = getlist(x)
180 180 if len(l) < min or (max >= 0 and len(l) > max):
181 181 raise error.ParseError(err)
182 182 return l
183 183
184 184 def getset(repo, subset, x):
185 185 if not x:
186 186 raise error.ParseError(_("missing argument"))
187 187 return methods[x[0]](repo, subset, *x[1:])
188 188
189 189 def _getrevsource(repo, r):
190 190 extra = repo[r].extra()
191 191 for label in ('source', 'transplant_source', 'rebase_source'):
192 192 if label in extra:
193 193 try:
194 194 return repo[extra[label]].rev()
195 195 except error.RepoLookupError:
196 196 pass
197 197 return None
198 198
199 199 # operator methods
200 200
201 201 def stringset(repo, subset, x):
202 202 x = repo[x].rev()
203 203 if x == -1 and len(subset) == len(repo):
204 204 return [-1]
205 205 if len(subset) == len(repo) or x in subset:
206 206 return [x]
207 207 return []
208 208
209 209 def symbolset(repo, subset, x):
210 210 if x in symbols:
211 211 raise error.ParseError(_("can't use %s here") % x)
212 212 return stringset(repo, subset, x)
213 213
214 214 def rangeset(repo, subset, x, y):
215 215 m = getset(repo, subset, x)
216 216 if not m:
217 217 m = getset(repo, range(len(repo)), x)
218 218
219 219 n = getset(repo, subset, y)
220 220 if not n:
221 221 n = getset(repo, range(len(repo)), y)
222 222
223 223 if not m or not n:
224 224 return []
225 225 m, n = m[0], n[-1]
226 226
227 227 if m < n:
228 228 r = range(m, n + 1)
229 229 else:
230 230 r = range(m, n - 1, -1)
231 231 s = set(subset)
232 232 return [x for x in r if x in s]
233 233
234 234 def dagrange(repo, subset, x, y):
235 235 if subset:
236 236 r = range(len(repo))
237 237 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
238 238 s = set(subset)
239 239 return [r for r in xs if r in s]
240 240 return []
241 241
242 242 def andset(repo, subset, x, y):
243 243 return getset(repo, getset(repo, subset, x), y)
244 244
245 245 def orset(repo, subset, x, y):
246 246 xl = getset(repo, subset, x)
247 247 s = set(xl)
248 248 yl = getset(repo, [r for r in subset if r not in s], y)
249 249 return xl + yl
250 250
251 251 def notset(repo, subset, x):
252 252 s = set(getset(repo, subset, x))
253 253 return [r for r in subset if r not in s]
254 254
255 255 def listset(repo, subset, a, b):
256 256 raise error.ParseError(_("can't use a list in this context"))
257 257
258 258 def func(repo, subset, a, b):
259 259 if a[0] == 'symbol' and a[1] in symbols:
260 260 return symbols[a[1]](repo, subset, b)
261 261 raise error.ParseError(_("not a function: %s") % a[1])
262 262
263 263 # functions
264 264
265 265 def adds(repo, subset, x):
266 266 """``adds(pattern)``
267 267 Changesets that add a file matching pattern.
268 268 """
269 269 # i18n: "adds" is a keyword
270 270 pat = getstring(x, _("adds requires a pattern"))
271 271 return checkstatus(repo, subset, pat, 1)
272 272
273 273 def ancestor(repo, subset, x):
274 274 """``ancestor(single, single)``
275 275 Greatest common ancestor of the two changesets.
276 276 """
277 277 # i18n: "ancestor" is a keyword
278 278 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
279 279 r = range(len(repo))
280 280 a = getset(repo, r, l[0])
281 281 b = getset(repo, r, l[1])
282 282 if len(a) != 1 or len(b) != 1:
283 283 # i18n: "ancestor" is a keyword
284 284 raise error.ParseError(_("ancestor arguments must be single revisions"))
285 285 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
286 286
287 287 return [r for r in an if r in subset]
288 288
289 289 def _ancestors(repo, subset, x, followfirst=False):
290 290 args = getset(repo, range(len(repo)), x)
291 291 if not args:
292 292 return []
293 293 s = set(_revancestors(repo, args, followfirst)) | set(args)
294 294 return [r for r in subset if r in s]
295 295
296 296 def ancestors(repo, subset, x):
297 297 """``ancestors(set)``
298 298 Changesets that are ancestors of a changeset in set.
299 299 """
300 300 return _ancestors(repo, subset, x)
301 301
302 302 def _firstancestors(repo, subset, x):
303 303 # ``_firstancestors(set)``
304 304 # Like ``ancestors(set)`` but follows only the first parents.
305 305 return _ancestors(repo, subset, x, followfirst=True)
306 306
307 307 def ancestorspec(repo, subset, x, n):
308 308 """``set~n``
309 309 Changesets that are the Nth ancestor (first parents only) of a changeset
310 310 in set.
311 311 """
312 312 try:
313 313 n = int(n[1])
314 314 except (TypeError, ValueError):
315 315 raise error.ParseError(_("~ expects a number"))
316 316 ps = set()
317 317 cl = repo.changelog
318 318 for r in getset(repo, subset, x):
319 319 for i in range(n):
320 320 r = cl.parentrevs(r)[0]
321 321 ps.add(r)
322 322 return [r for r in subset if r in ps]
323 323
324 324 def author(repo, subset, x):
325 325 """``author(string)``
326 326 Alias for ``user(string)``.
327 327 """
328 328 # i18n: "author" is a keyword
329 329 n = encoding.lower(getstring(x, _("author requires a string")))
330 330 kind, pattern, matcher = _substringmatcher(n)
331 331 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
332 332
333 333 def bisect(repo, subset, x):
334 334 """``bisect(string)``
335 335 Changesets marked in the specified bisect status:
336 336
337 337 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
338 338 - ``goods``, ``bads`` : csets topologicaly good/bad
339 339 - ``range`` : csets taking part in the bisection
340 340 - ``pruned`` : csets that are goods, bads or skipped
341 341 - ``untested`` : csets whose fate is yet unknown
342 342 - ``ignored`` : csets ignored due to DAG topology
343 343 - ``current`` : the cset currently being bisected
344 344 """
345 # i18n: "bisect" is a keyword
345 346 status = getstring(x, _("bisect requires a string")).lower()
346 347 state = set(hbisect.get(repo, status))
347 348 return [r for r in subset if r in state]
348 349
349 350 # Backward-compatibility
350 351 # - no help entry so that we do not advertise it any more
351 352 def bisected(repo, subset, x):
352 353 return bisect(repo, subset, x)
353 354
354 355 def bookmark(repo, subset, x):
355 356 """``bookmark([name])``
356 357 The named bookmark or all bookmarks.
357 358
358 359 If `name` starts with `re:`, the remainder of the name is treated as
359 360 a regular expression. To match a bookmark that actually starts with `re:`,
360 361 use the prefix `literal:`.
361 362 """
362 363 # i18n: "bookmark" is a keyword
363 364 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
364 365 if args:
365 366 bm = getstring(args[0],
366 367 # i18n: "bookmark" is a keyword
367 368 _('the argument to bookmark must be a string'))
368 369 kind, pattern, matcher = _stringmatcher(bm)
369 370 if kind == 'literal':
370 371 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
371 372 if not bmrev:
372 373 raise util.Abort(_("bookmark '%s' does not exist") % bm)
373 374 bmrev = repo[bmrev].rev()
374 375 return [r for r in subset if r == bmrev]
375 376 else:
376 377 matchrevs = set()
377 378 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
378 379 if matcher(name):
379 380 matchrevs.add(bmrev)
380 381 if not matchrevs:
381 382 raise util.Abort(_("no bookmarks exist that match '%s'")
382 383 % pattern)
383 384 bmrevs = set()
384 385 for bmrev in matchrevs:
385 386 bmrevs.add(repo[bmrev].rev())
386 387 return [r for r in subset if r in bmrevs]
387 388
388 389 bms = set([repo[r].rev()
389 390 for r in bookmarksmod.listbookmarks(repo).values()])
390 391 return [r for r in subset if r in bms]
391 392
392 393 def branch(repo, subset, x):
393 394 """``branch(string or set)``
394 395 All changesets belonging to the given branch or the branches of the given
395 396 changesets.
396 397
397 398 If `string` starts with `re:`, the remainder of the name is treated as
398 399 a regular expression. To match a branch that actually starts with `re:`,
399 400 use the prefix `literal:`.
400 401 """
401 402 try:
402 403 b = getstring(x, '')
403 404 except error.ParseError:
404 405 # not a string, but another revspec, e.g. tip()
405 406 pass
406 407 else:
407 408 kind, pattern, matcher = _stringmatcher(b)
408 409 if kind == 'literal':
409 410 # note: falls through to the revspec case if no branch with
410 411 # this name exists
411 412 if pattern in repo.branchmap():
412 413 return [r for r in subset if matcher(repo[r].branch())]
413 414 else:
414 415 return [r for r in subset if matcher(repo[r].branch())]
415 416
416 417 s = getset(repo, range(len(repo)), x)
417 418 b = set()
418 419 for r in s:
419 420 b.add(repo[r].branch())
420 421 s = set(s)
421 422 return [r for r in subset if r in s or repo[r].branch() in b]
422 423
423 424 def checkstatus(repo, subset, pat, field):
424 425 m = None
425 426 s = []
426 427 hasset = matchmod.patkind(pat) == 'set'
427 428 fname = None
428 429 for r in subset:
429 430 c = repo[r]
430 431 if not m or hasset:
431 432 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
432 433 if not m.anypats() and len(m.files()) == 1:
433 434 fname = m.files()[0]
434 435 if fname is not None:
435 436 if fname not in c.files():
436 437 continue
437 438 else:
438 439 for f in c.files():
439 440 if m(f):
440 441 break
441 442 else:
442 443 continue
443 444 files = repo.status(c.p1().node(), c.node())[field]
444 445 if fname is not None:
445 446 if fname in files:
446 447 s.append(r)
447 448 else:
448 449 for f in files:
449 450 if m(f):
450 451 s.append(r)
451 452 break
452 453 return s
453 454
454 455 def _children(repo, narrow, parentset):
455 456 cs = set()
456 457 pr = repo.changelog.parentrevs
457 458 for r in narrow:
458 459 for p in pr(r):
459 460 if p in parentset:
460 461 cs.add(r)
461 462 return cs
462 463
463 464 def children(repo, subset, x):
464 465 """``children(set)``
465 466 Child changesets of changesets in set.
466 467 """
467 468 s = set(getset(repo, range(len(repo)), x))
468 469 cs = _children(repo, subset, s)
469 470 return [r for r in subset if r in cs]
470 471
471 472 def closed(repo, subset, x):
472 473 """``closed()``
473 474 Changeset is closed.
474 475 """
475 476 # i18n: "closed" is a keyword
476 477 getargs(x, 0, 0, _("closed takes no arguments"))
477 478 return [r for r in subset if repo[r].closesbranch()]
478 479
479 480 def contains(repo, subset, x):
480 481 """``contains(pattern)``
481 482 Revision contains a file matching pattern. See :hg:`help patterns`
482 483 for information about file patterns.
483 484 """
484 485 # i18n: "contains" is a keyword
485 486 pat = getstring(x, _("contains requires a pattern"))
486 487 m = None
487 488 s = []
488 489 if not matchmod.patkind(pat):
489 490 for r in subset:
490 491 if pat in repo[r]:
491 492 s.append(r)
492 493 else:
493 494 for r in subset:
494 495 c = repo[r]
495 496 if not m or matchmod.patkind(pat) == 'set':
496 497 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
497 498 for f in c.manifest():
498 499 if m(f):
499 500 s.append(r)
500 501 break
501 502 return s
502 503
503 504 def converted(repo, subset, x):
504 505 """``converted([id])``
505 506 Changesets converted from the given identifier in the old repository if
506 507 present, or all converted changesets if no identifier is specified.
507 508 """
508 509
509 510 # There is exactly no chance of resolving the revision, so do a simple
510 511 # string compare and hope for the best
511 512
513 rev = None
512 514 # i18n: "converted" is a keyword
513 rev = None
514 515 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
515 516 if l:
517 # i18n: "converted" is a keyword
516 518 rev = getstring(l[0], _('converted requires a revision'))
517 519
518 520 def _matchvalue(r):
519 521 source = repo[r].extra().get('convert_revision', None)
520 522 return source is not None and (rev is None or source.startswith(rev))
521 523
522 524 return [r for r in subset if _matchvalue(r)]
523 525
524 526 def date(repo, subset, x):
525 527 """``date(interval)``
526 528 Changesets within the interval, see :hg:`help dates`.
527 529 """
528 530 # i18n: "date" is a keyword
529 531 ds = getstring(x, _("date requires a string"))
530 532 dm = util.matchdate(ds)
531 533 return [r for r in subset if dm(repo[r].date()[0])]
532 534
533 535 def desc(repo, subset, x):
534 536 """``desc(string)``
535 537 Search commit message for string. The match is case-insensitive.
536 538 """
537 539 # i18n: "desc" is a keyword
538 540 ds = encoding.lower(getstring(x, _("desc requires a string")))
539 541 l = []
540 542 for r in subset:
541 543 c = repo[r]
542 544 if ds in encoding.lower(c.description()):
543 545 l.append(r)
544 546 return l
545 547
546 548 def _descendants(repo, subset, x, followfirst=False):
547 549 args = getset(repo, range(len(repo)), x)
548 550 if not args:
549 551 return []
550 552 s = set(_revdescendants(repo, args, followfirst)) | set(args)
551 553 return [r for r in subset if r in s]
552 554
553 555 def descendants(repo, subset, x):
554 556 """``descendants(set)``
555 557 Changesets which are descendants of changesets in set.
556 558 """
557 559 return _descendants(repo, subset, x)
558 560
559 561 def _firstdescendants(repo, subset, x):
560 562 # ``_firstdescendants(set)``
561 563 # Like ``descendants(set)`` but follows only the first parents.
562 564 return _descendants(repo, subset, x, followfirst=True)
563 565
564 566 def destination(repo, subset, x):
565 567 """``destination([set])``
566 568 Changesets that were created by a graft, transplant or rebase operation,
567 569 with the given revisions specified as the source. Omitting the optional set
568 570 is the same as passing all().
569 571 """
570 572 if x is not None:
571 573 args = set(getset(repo, range(len(repo)), x))
572 574 else:
573 575 args = set(getall(repo, range(len(repo)), x))
574 576
575 577 dests = set()
576 578
577 579 # subset contains all of the possible destinations that can be returned, so
578 580 # iterate over them and see if their source(s) were provided in the args.
579 581 # Even if the immediate src of r is not in the args, src's source (or
580 582 # further back) may be. Scanning back further than the immediate src allows
581 583 # transitive transplants and rebases to yield the same results as transitive
582 584 # grafts.
583 585 for r in subset:
584 586 src = _getrevsource(repo, r)
585 587 lineage = None
586 588
587 589 while src is not None:
588 590 if lineage is None:
589 591 lineage = list()
590 592
591 593 lineage.append(r)
592 594
593 595 # The visited lineage is a match if the current source is in the arg
594 596 # set. Since every candidate dest is visited by way of iterating
595 597 # subset, any dests futher back in the lineage will be tested by a
596 598 # different iteration over subset. Likewise, if the src was already
597 599 # selected, the current lineage can be selected without going back
598 600 # further.
599 601 if src in args or src in dests:
600 602 dests.update(lineage)
601 603 break
602 604
603 605 r = src
604 606 src = _getrevsource(repo, r)
605 607
606 608 return [r for r in subset if r in dests]
607 609
608 610 def draft(repo, subset, x):
609 611 """``draft()``
610 612 Changeset in draft phase."""
613 # i18n: "draft" is a keyword
611 614 getargs(x, 0, 0, _("draft takes no arguments"))
612 615 pc = repo._phasecache
613 616 return [r for r in subset if pc.phase(repo, r) == phases.draft]
614 617
615 618 def extinct(repo, subset, x):
616 619 """``extinct()``
617 620 obsolete changeset with obsolete descendant only."""
621 # i18n: "extinct" is a keyword
618 622 getargs(x, 0, 0, _("extinct takes no arguments"))
619 623 extinctset = set(repo.revs('(obsolete()::) - (::(not obsolete()))'))
620 624 return [r for r in subset if r in extinctset]
621 625
622 626 def extra(repo, subset, x):
623 627 """``extra(label, [value])``
624 628 Changesets with the given label in the extra metadata, with the given
625 629 optional value.
626 630
627 631 If `value` starts with `re:`, the remainder of the value is treated as
628 632 a regular expression. To match a value that actually starts with `re:`,
629 633 use the prefix `literal:`.
630 634 """
631 635
636 # i18n: "extra" is a keyword
632 637 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
638 # i18n: "extra" is a keyword
633 639 label = getstring(l[0], _('first argument to extra must be a string'))
634 640 value = None
635 641
636 642 if len(l) > 1:
643 # i18n: "extra" is a keyword
637 644 value = getstring(l[1], _('second argument to extra must be a string'))
638 645 kind, value, matcher = _stringmatcher(value)
639 646
640 647 def _matchvalue(r):
641 648 extra = repo[r].extra()
642 649 return label in extra and (value is None or matcher(extra[label]))
643 650
644 651 return [r for r in subset if _matchvalue(r)]
645 652
646 653 def filelog(repo, subset, x):
647 654 """``filelog(pattern)``
648 655 Changesets connected to the specified filelog.
649 656
650 657 If you want to get all changesets affecting matched files exactly,
651 658 use ``file()`` predicate, because ``filelog()`` may omit some changesets
652 659 for performance reasons: see :hg:`help log` for detail.
653 660 """
654 661
662 # i18n: "filelog" is a keyword
655 663 pat = getstring(x, _("filelog requires a pattern"))
656 664 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
657 665 ctx=repo[None])
658 666 s = set()
659 667
660 668 if not matchmod.patkind(pat):
661 669 for f in m.files():
662 670 fl = repo.file(f)
663 671 for fr in fl:
664 672 s.add(fl.linkrev(fr))
665 673 else:
666 674 for f in repo[None]:
667 675 if m(f):
668 676 fl = repo.file(f)
669 677 for fr in fl:
670 678 s.add(fl.linkrev(fr))
671 679
672 680 return [r for r in subset if r in s]
673 681
674 682 def first(repo, subset, x):
675 683 """``first(set, [n])``
676 684 An alias for limit().
677 685 """
678 686 return limit(repo, subset, x)
679 687
680 688 def _follow(repo, subset, x, name, followfirst=False):
681 689 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
682 690 c = repo['.']
683 691 if l:
684 692 x = getstring(l[0], _("%s expected a filename") % name)
685 693 if x in c:
686 694 cx = c[x]
687 695 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
688 696 # include the revision responsible for the most recent version
689 697 s.add(cx.linkrev())
690 698 else:
691 699 return []
692 700 else:
693 701 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
694 702
695 703 return [r for r in subset if r in s]
696 704
697 705 def follow(repo, subset, x):
698 706 """``follow([file])``
699 707 An alias for ``::.`` (ancestors of the working copy's first parent).
700 708 If a filename is specified, the history of the given file is followed,
701 709 including copies.
702 710 """
703 711 return _follow(repo, subset, x, 'follow')
704 712
705 713 def _followfirst(repo, subset, x):
706 714 # ``followfirst([file])``
707 715 # Like ``follow([file])`` but follows only the first parent of
708 716 # every revision or file revision.
709 717 return _follow(repo, subset, x, '_followfirst', followfirst=True)
710 718
711 719 def getall(repo, subset, x):
712 720 """``all()``
713 721 All changesets, the same as ``0:tip``.
714 722 """
715 723 # i18n: "all" is a keyword
716 724 getargs(x, 0, 0, _("all takes no arguments"))
717 725 return subset
718 726
719 727 def grep(repo, subset, x):
720 728 """``grep(regex)``
721 729 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
722 730 to ensure special escape characters are handled correctly. Unlike
723 731 ``keyword(string)``, the match is case-sensitive.
724 732 """
725 733 try:
726 734 # i18n: "grep" is a keyword
727 735 gr = re.compile(getstring(x, _("grep requires a string")))
728 736 except re.error, e:
729 737 raise error.ParseError(_('invalid match pattern: %s') % e)
730 738 l = []
731 739 for r in subset:
732 740 c = repo[r]
733 741 for e in c.files() + [c.user(), c.description()]:
734 742 if gr.search(e):
735 743 l.append(r)
736 744 break
737 745 return l
738 746
739 747 def _matchfiles(repo, subset, x):
740 748 # _matchfiles takes a revset list of prefixed arguments:
741 749 #
742 750 # [p:foo, i:bar, x:baz]
743 751 #
744 752 # builds a match object from them and filters subset. Allowed
745 753 # prefixes are 'p:' for regular patterns, 'i:' for include
746 754 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
747 755 # a revision identifier, or the empty string to reference the
748 756 # working directory, from which the match object is
749 757 # initialized. Use 'd:' to set the default matching mode, default
750 758 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
751 759
752 760 # i18n: "_matchfiles" is a keyword
753 761 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
754 762 pats, inc, exc = [], [], []
755 763 hasset = False
756 764 rev, default = None, None
757 765 for arg in l:
766 # i18n: "_matchfiles" is a keyword
758 767 s = getstring(arg, _("_matchfiles requires string arguments"))
759 768 prefix, value = s[:2], s[2:]
760 769 if prefix == 'p:':
761 770 pats.append(value)
762 771 elif prefix == 'i:':
763 772 inc.append(value)
764 773 elif prefix == 'x:':
765 774 exc.append(value)
766 775 elif prefix == 'r:':
767 776 if rev is not None:
777 # i18n: "_matchfiles" is a keyword
768 778 raise error.ParseError(_('_matchfiles expected at most one '
769 779 'revision'))
770 780 rev = value
771 781 elif prefix == 'd:':
772 782 if default is not None:
783 # i18n: "_matchfiles" is a keyword
773 784 raise error.ParseError(_('_matchfiles expected at most one '
774 785 'default mode'))
775 786 default = value
776 787 else:
788 # i18n: "_matchfiles" is a keyword
777 789 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
778 790 if not hasset and matchmod.patkind(value) == 'set':
779 791 hasset = True
780 792 if not default:
781 793 default = 'glob'
782 794 m = None
783 795 s = []
784 796 for r in subset:
785 797 c = repo[r]
786 798 if not m or (hasset and rev is None):
787 799 ctx = c
788 800 if rev is not None:
789 801 ctx = repo[rev or None]
790 802 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
791 803 exclude=exc, ctx=ctx, default=default)
792 804 for f in c.files():
793 805 if m(f):
794 806 s.append(r)
795 807 break
796 808 return s
797 809
798 810 def hasfile(repo, subset, x):
799 811 """``file(pattern)``
800 812 Changesets affecting files matched by pattern.
801 813
802 814 If you want to pick changesets up fast, consider to
803 815 use ``filelog()`` predicate, too.
804 816 """
805 817 # i18n: "file" is a keyword
806 818 pat = getstring(x, _("file requires a pattern"))
807 819 return _matchfiles(repo, subset, ('string', 'p:' + pat))
808 820
809 821 def head(repo, subset, x):
810 822 """``head()``
811 823 Changeset is a named branch head.
812 824 """
813 825 # i18n: "head" is a keyword
814 826 getargs(x, 0, 0, _("head takes no arguments"))
815 827 hs = set()
816 828 for b, ls in repo.branchmap().iteritems():
817 829 hs.update(repo[h].rev() for h in ls)
818 830 return [r for r in subset if r in hs]
819 831
820 832 def heads(repo, subset, x):
821 833 """``heads(set)``
822 834 Members of set with no children in set.
823 835 """
824 836 s = getset(repo, subset, x)
825 837 ps = set(parents(repo, subset, x))
826 838 return [r for r in s if r not in ps]
827 839
828 840 def keyword(repo, subset, x):
829 841 """``keyword(string)``
830 842 Search commit message, user name, and names of changed files for
831 843 string. The match is case-insensitive.
832 844 """
833 845 # i18n: "keyword" is a keyword
834 846 kw = encoding.lower(getstring(x, _("keyword requires a string")))
835 847 l = []
836 848 for r in subset:
837 849 c = repo[r]
838 850 t = " ".join(c.files() + [c.user(), c.description()])
839 851 if kw in encoding.lower(t):
840 852 l.append(r)
841 853 return l
842 854
843 855 def limit(repo, subset, x):
844 856 """``limit(set, [n])``
845 857 First n members of set, defaulting to 1.
846 858 """
847 859 # i18n: "limit" is a keyword
848 860 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
849 861 try:
850 862 lim = 1
851 863 if len(l) == 2:
852 864 # i18n: "limit" is a keyword
853 865 lim = int(getstring(l[1], _("limit requires a number")))
854 866 except (TypeError, ValueError):
855 867 # i18n: "limit" is a keyword
856 868 raise error.ParseError(_("limit expects a number"))
857 869 ss = set(subset)
858 870 os = getset(repo, range(len(repo)), l[0])[:lim]
859 871 return [r for r in os if r in ss]
860 872
861 873 def last(repo, subset, x):
862 874 """``last(set, [n])``
863 875 Last n members of set, defaulting to 1.
864 876 """
865 877 # i18n: "last" is a keyword
866 878 l = getargs(x, 1, 2, _("last requires one or two arguments"))
867 879 try:
868 880 lim = 1
869 881 if len(l) == 2:
870 882 # i18n: "last" is a keyword
871 883 lim = int(getstring(l[1], _("last requires a number")))
872 884 except (TypeError, ValueError):
873 885 # i18n: "last" is a keyword
874 886 raise error.ParseError(_("last expects a number"))
875 887 ss = set(subset)
876 888 os = getset(repo, range(len(repo)), l[0])[-lim:]
877 889 return [r for r in os if r in ss]
878 890
879 891 def maxrev(repo, subset, x):
880 892 """``max(set)``
881 893 Changeset with highest revision number in set.
882 894 """
883 895 os = getset(repo, range(len(repo)), x)
884 896 if os:
885 897 m = max(os)
886 898 if m in subset:
887 899 return [m]
888 900 return []
889 901
890 902 def merge(repo, subset, x):
891 903 """``merge()``
892 904 Changeset is a merge changeset.
893 905 """
894 906 # i18n: "merge" is a keyword
895 907 getargs(x, 0, 0, _("merge takes no arguments"))
896 908 cl = repo.changelog
897 909 return [r for r in subset if cl.parentrevs(r)[1] != -1]
898 910
899 911 def minrev(repo, subset, x):
900 912 """``min(set)``
901 913 Changeset with lowest revision number in set.
902 914 """
903 915 os = getset(repo, range(len(repo)), x)
904 916 if os:
905 917 m = min(os)
906 918 if m in subset:
907 919 return [m]
908 920 return []
909 921
910 922 def modifies(repo, subset, x):
911 923 """``modifies(pattern)``
912 924 Changesets modifying files matched by pattern.
913 925 """
914 926 # i18n: "modifies" is a keyword
915 927 pat = getstring(x, _("modifies requires a pattern"))
916 928 return checkstatus(repo, subset, pat, 0)
917 929
918 930 def node_(repo, subset, x):
919 931 """``id(string)``
920 932 Revision non-ambiguously specified by the given hex string prefix.
921 933 """
922 934 # i18n: "id" is a keyword
923 935 l = getargs(x, 1, 1, _("id requires one argument"))
924 936 # i18n: "id" is a keyword
925 937 n = getstring(l[0], _("id requires a string"))
926 938 if len(n) == 40:
927 939 rn = repo[n].rev()
928 940 else:
929 941 rn = None
930 942 pm = repo.changelog._partialmatch(n)
931 943 if pm is not None:
932 944 rn = repo.changelog.rev(pm)
933 945
934 946 return [r for r in subset if r == rn]
935 947
936 948 def obsolete(repo, subset, x):
937 949 """``obsolete()``
938 950 Mutable changeset with a newer version."""
951 # i18n: "obsolete" is a keyword
939 952 getargs(x, 0, 0, _("obsolete takes no arguments"))
940 953 return [r for r in subset if repo[r].obsolete()]
941 954
942 955 def origin(repo, subset, x):
943 956 """``origin([set])``
944 957 Changesets that were specified as a source for the grafts, transplants or
945 958 rebases that created the given revisions. Omitting the optional set is the
946 959 same as passing all(). If a changeset created by these operations is itself
947 960 specified as a source for one of these operations, only the source changeset
948 961 for the first operation is selected.
949 962 """
950 963 if x is not None:
951 964 args = set(getset(repo, range(len(repo)), x))
952 965 else:
953 966 args = set(getall(repo, range(len(repo)), x))
954 967
955 968 def _firstsrc(rev):
956 969 src = _getrevsource(repo, rev)
957 970 if src is None:
958 971 return None
959 972
960 973 while True:
961 974 prev = _getrevsource(repo, src)
962 975
963 976 if prev is None:
964 977 return src
965 978 src = prev
966 979
967 980 o = set([_firstsrc(r) for r in args])
968 981 return [r for r in subset if r in o]
969 982
970 983 def outgoing(repo, subset, x):
971 984 """``outgoing([path])``
972 985 Changesets not found in the specified destination repository, or the
973 986 default push location.
974 987 """
975 988 import hg # avoid start-up nasties
976 989 # i18n: "outgoing" is a keyword
977 990 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
978 991 # i18n: "outgoing" is a keyword
979 992 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
980 993 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
981 994 dest, branches = hg.parseurl(dest)
982 995 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
983 996 if revs:
984 997 revs = [repo.lookup(rev) for rev in revs]
985 998 other = hg.peer(repo, {}, dest)
986 999 repo.ui.pushbuffer()
987 1000 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
988 1001 repo.ui.popbuffer()
989 1002 cl = repo.changelog
990 1003 o = set([cl.rev(r) for r in outgoing.missing])
991 1004 return [r for r in subset if r in o]
992 1005
993 1006 def p1(repo, subset, x):
994 1007 """``p1([set])``
995 1008 First parent of changesets in set, or the working directory.
996 1009 """
997 1010 if x is None:
998 1011 p = repo[x].p1().rev()
999 1012 return [r for r in subset if r == p]
1000 1013
1001 1014 ps = set()
1002 1015 cl = repo.changelog
1003 1016 for r in getset(repo, range(len(repo)), x):
1004 1017 ps.add(cl.parentrevs(r)[0])
1005 1018 return [r for r in subset if r in ps]
1006 1019
1007 1020 def p2(repo, subset, x):
1008 1021 """``p2([set])``
1009 1022 Second parent of changesets in set, or the working directory.
1010 1023 """
1011 1024 if x is None:
1012 1025 ps = repo[x].parents()
1013 1026 try:
1014 1027 p = ps[1].rev()
1015 1028 return [r for r in subset if r == p]
1016 1029 except IndexError:
1017 1030 return []
1018 1031
1019 1032 ps = set()
1020 1033 cl = repo.changelog
1021 1034 for r in getset(repo, range(len(repo)), x):
1022 1035 ps.add(cl.parentrevs(r)[1])
1023 1036 return [r for r in subset if r in ps]
1024 1037
1025 1038 def parents(repo, subset, x):
1026 1039 """``parents([set])``
1027 1040 The set of all parents for all changesets in set, or the working directory.
1028 1041 """
1029 1042 if x is None:
1030 1043 ps = tuple(p.rev() for p in repo[x].parents())
1031 1044 return [r for r in subset if r in ps]
1032 1045
1033 1046 ps = set()
1034 1047 cl = repo.changelog
1035 1048 for r in getset(repo, range(len(repo)), x):
1036 1049 ps.update(cl.parentrevs(r))
1037 1050 return [r for r in subset if r in ps]
1038 1051
1039 1052 def parentspec(repo, subset, x, n):
1040 1053 """``set^0``
1041 1054 The set.
1042 1055 ``set^1`` (or ``set^``), ``set^2``
1043 1056 First or second parent, respectively, of all changesets in set.
1044 1057 """
1045 1058 try:
1046 1059 n = int(n[1])
1047 1060 if n not in (0, 1, 2):
1048 1061 raise ValueError
1049 1062 except (TypeError, ValueError):
1050 1063 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1051 1064 ps = set()
1052 1065 cl = repo.changelog
1053 1066 for r in getset(repo, subset, x):
1054 1067 if n == 0:
1055 1068 ps.add(r)
1056 1069 elif n == 1:
1057 1070 ps.add(cl.parentrevs(r)[0])
1058 1071 elif n == 2:
1059 1072 parents = cl.parentrevs(r)
1060 1073 if len(parents) > 1:
1061 1074 ps.add(parents[1])
1062 1075 return [r for r in subset if r in ps]
1063 1076
1064 1077 def present(repo, subset, x):
1065 1078 """``present(set)``
1066 1079 An empty set, if any revision in set isn't found; otherwise,
1067 1080 all revisions in set.
1068 1081
1069 1082 If any of specified revisions is not present in the local repository,
1070 1083 the query is normally aborted. But this predicate allows the query
1071 1084 to continue even in such cases.
1072 1085 """
1073 1086 try:
1074 1087 return getset(repo, subset, x)
1075 1088 except error.RepoLookupError:
1076 1089 return []
1077 1090
1078 1091 def public(repo, subset, x):
1079 1092 """``public()``
1080 1093 Changeset in public phase."""
1094 # i18n: "public" is a keyword
1081 1095 getargs(x, 0, 0, _("public takes no arguments"))
1082 1096 pc = repo._phasecache
1083 1097 return [r for r in subset if pc.phase(repo, r) == phases.public]
1084 1098
1085 1099 def remote(repo, subset, x):
1086 1100 """``remote([id [,path]])``
1087 1101 Local revision that corresponds to the given identifier in a
1088 1102 remote repository, if present. Here, the '.' identifier is a
1089 1103 synonym for the current local branch.
1090 1104 """
1091 1105
1092 1106 import hg # avoid start-up nasties
1093 1107 # i18n: "remote" is a keyword
1094 1108 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1095 1109
1096 1110 q = '.'
1097 1111 if len(l) > 0:
1098 1112 # i18n: "remote" is a keyword
1099 1113 q = getstring(l[0], _("remote requires a string id"))
1100 1114 if q == '.':
1101 1115 q = repo['.'].branch()
1102 1116
1103 1117 dest = ''
1104 1118 if len(l) > 1:
1105 1119 # i18n: "remote" is a keyword
1106 1120 dest = getstring(l[1], _("remote requires a repository path"))
1107 1121 dest = repo.ui.expandpath(dest or 'default')
1108 1122 dest, branches = hg.parseurl(dest)
1109 1123 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1110 1124 if revs:
1111 1125 revs = [repo.lookup(rev) for rev in revs]
1112 1126 other = hg.peer(repo, {}, dest)
1113 1127 n = other.lookup(q)
1114 1128 if n in repo:
1115 1129 r = repo[n].rev()
1116 1130 if r in subset:
1117 1131 return [r]
1118 1132 return []
1119 1133
1120 1134 def removes(repo, subset, x):
1121 1135 """``removes(pattern)``
1122 1136 Changesets which remove files matching pattern.
1123 1137 """
1124 1138 # i18n: "removes" is a keyword
1125 1139 pat = getstring(x, _("removes requires a pattern"))
1126 1140 return checkstatus(repo, subset, pat, 2)
1127 1141
1128 1142 def rev(repo, subset, x):
1129 1143 """``rev(number)``
1130 1144 Revision with the given numeric identifier.
1131 1145 """
1132 1146 # i18n: "rev" is a keyword
1133 1147 l = getargs(x, 1, 1, _("rev requires one argument"))
1134 1148 try:
1135 1149 # i18n: "rev" is a keyword
1136 1150 l = int(getstring(l[0], _("rev requires a number")))
1137 1151 except (TypeError, ValueError):
1138 1152 # i18n: "rev" is a keyword
1139 1153 raise error.ParseError(_("rev expects a number"))
1140 1154 return [r for r in subset if r == l]
1141 1155
1142 1156 def matching(repo, subset, x):
1143 1157 """``matching(revision [, field])``
1144 1158 Changesets in which a given set of fields match the set of fields in the
1145 1159 selected revision or set.
1146 1160
1147 1161 To match more than one field pass the list of fields to match separated
1148 1162 by spaces (e.g. ``author description``).
1149 1163
1150 1164 Valid fields are most regular revision fields and some special fields.
1151 1165
1152 1166 Regular revision fields are ``description``, ``author``, ``branch``,
1153 1167 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1154 1168 and ``diff``.
1155 1169 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1156 1170 contents of the revision. Two revisions matching their ``diff`` will
1157 1171 also match their ``files``.
1158 1172
1159 1173 Special fields are ``summary`` and ``metadata``:
1160 1174 ``summary`` matches the first line of the description.
1161 1175 ``metadata`` is equivalent to matching ``description user date``
1162 1176 (i.e. it matches the main metadata fields).
1163 1177
1164 1178 ``metadata`` is the default field which is used when no fields are
1165 1179 specified. You can match more than one field at a time.
1166 1180 """
1181 # i18n: "matching" is a keyword
1167 1182 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1168 1183
1169 1184 revs = getset(repo, xrange(len(repo)), l[0])
1170 1185
1171 1186 fieldlist = ['metadata']
1172 1187 if len(l) > 1:
1173 1188 fieldlist = getstring(l[1],
1189 # i18n: "matching" is a keyword
1174 1190 _("matching requires a string "
1175 1191 "as its second argument")).split()
1176 1192
1177 1193 # Make sure that there are no repeated fields,
1178 1194 # expand the 'special' 'metadata' field type
1179 1195 # and check the 'files' whenever we check the 'diff'
1180 1196 fields = []
1181 1197 for field in fieldlist:
1182 1198 if field == 'metadata':
1183 1199 fields += ['user', 'description', 'date']
1184 1200 elif field == 'diff':
1185 1201 # a revision matching the diff must also match the files
1186 1202 # since matching the diff is very costly, make sure to
1187 1203 # also match the files first
1188 1204 fields += ['files', 'diff']
1189 1205 else:
1190 1206 if field == 'author':
1191 1207 field = 'user'
1192 1208 fields.append(field)
1193 1209 fields = set(fields)
1194 1210 if 'summary' in fields and 'description' in fields:
1195 1211 # If a revision matches its description it also matches its summary
1196 1212 fields.discard('summary')
1197 1213
1198 1214 # We may want to match more than one field
1199 1215 # Not all fields take the same amount of time to be matched
1200 1216 # Sort the selected fields in order of increasing matching cost
1201 1217 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1202 1218 'files', 'description', 'substate', 'diff']
1203 1219 def fieldkeyfunc(f):
1204 1220 try:
1205 1221 return fieldorder.index(f)
1206 1222 except ValueError:
1207 1223 # assume an unknown field is very costly
1208 1224 return len(fieldorder)
1209 1225 fields = list(fields)
1210 1226 fields.sort(key=fieldkeyfunc)
1211 1227
1212 1228 # Each field will be matched with its own "getfield" function
1213 1229 # which will be added to the getfieldfuncs array of functions
1214 1230 getfieldfuncs = []
1215 1231 _funcs = {
1216 1232 'user': lambda r: repo[r].user(),
1217 1233 'branch': lambda r: repo[r].branch(),
1218 1234 'date': lambda r: repo[r].date(),
1219 1235 'description': lambda r: repo[r].description(),
1220 1236 'files': lambda r: repo[r].files(),
1221 1237 'parents': lambda r: repo[r].parents(),
1222 1238 'phase': lambda r: repo[r].phase(),
1223 1239 'substate': lambda r: repo[r].substate,
1224 1240 'summary': lambda r: repo[r].description().splitlines()[0],
1225 1241 'diff': lambda r: list(repo[r].diff(git=True),)
1226 1242 }
1227 1243 for info in fields:
1228 1244 getfield = _funcs.get(info, None)
1229 1245 if getfield is None:
1230 1246 raise error.ParseError(
1247 # i18n: "matching" is a keyword
1231 1248 _("unexpected field name passed to matching: %s") % info)
1232 1249 getfieldfuncs.append(getfield)
1233 1250 # convert the getfield array of functions into a "getinfo" function
1234 1251 # which returns an array of field values (or a single value if there
1235 1252 # is only one field to match)
1236 1253 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1237 1254
1238 1255 matches = set()
1239 1256 for rev in revs:
1240 1257 target = getinfo(rev)
1241 1258 for r in subset:
1242 1259 match = True
1243 1260 for n, f in enumerate(getfieldfuncs):
1244 1261 if target[n] != f(r):
1245 1262 match = False
1246 1263 break
1247 1264 if match:
1248 1265 matches.add(r)
1249 1266 return [r for r in subset if r in matches]
1250 1267
1251 1268 def reverse(repo, subset, x):
1252 1269 """``reverse(set)``
1253 1270 Reverse order of set.
1254 1271 """
1255 1272 l = getset(repo, subset, x)
1256 1273 if not isinstance(l, list):
1257 1274 l = list(l)
1258 1275 l.reverse()
1259 1276 return l
1260 1277
1261 1278 def roots(repo, subset, x):
1262 1279 """``roots(set)``
1263 1280 Changesets in set with no parent changeset in set.
1264 1281 """
1265 1282 s = set(getset(repo, xrange(len(repo)), x))
1266 1283 subset = [r for r in subset if r in s]
1267 1284 cs = _children(repo, subset, s)
1268 1285 return [r for r in subset if r not in cs]
1269 1286
1270 1287 def secret(repo, subset, x):
1271 1288 """``secret()``
1272 1289 Changeset in secret phase."""
1290 # i18n: "secret" is a keyword
1273 1291 getargs(x, 0, 0, _("secret takes no arguments"))
1274 1292 pc = repo._phasecache
1275 1293 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1276 1294
1277 1295 def sort(repo, subset, x):
1278 1296 """``sort(set[, [-]key...])``
1279 1297 Sort set by keys. The default sort order is ascending, specify a key
1280 1298 as ``-key`` to sort in descending order.
1281 1299
1282 1300 The keys can be:
1283 1301
1284 1302 - ``rev`` for the revision number,
1285 1303 - ``branch`` for the branch name,
1286 1304 - ``desc`` for the commit message (description),
1287 1305 - ``user`` for user name (``author`` can be used as an alias),
1288 1306 - ``date`` for the commit date
1289 1307 """
1290 1308 # i18n: "sort" is a keyword
1291 1309 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1292 1310 keys = "rev"
1293 1311 if len(l) == 2:
1312 # i18n: "sort" is a keyword
1294 1313 keys = getstring(l[1], _("sort spec must be a string"))
1295 1314
1296 1315 s = l[0]
1297 1316 keys = keys.split()
1298 1317 l = []
1299 1318 def invert(s):
1300 1319 return "".join(chr(255 - ord(c)) for c in s)
1301 1320 for r in getset(repo, subset, s):
1302 1321 c = repo[r]
1303 1322 e = []
1304 1323 for k in keys:
1305 1324 if k == 'rev':
1306 1325 e.append(r)
1307 1326 elif k == '-rev':
1308 1327 e.append(-r)
1309 1328 elif k == 'branch':
1310 1329 e.append(c.branch())
1311 1330 elif k == '-branch':
1312 1331 e.append(invert(c.branch()))
1313 1332 elif k == 'desc':
1314 1333 e.append(c.description())
1315 1334 elif k == '-desc':
1316 1335 e.append(invert(c.description()))
1317 1336 elif k in 'user author':
1318 1337 e.append(c.user())
1319 1338 elif k in '-user -author':
1320 1339 e.append(invert(c.user()))
1321 1340 elif k == 'date':
1322 1341 e.append(c.date()[0])
1323 1342 elif k == '-date':
1324 1343 e.append(-c.date()[0])
1325 1344 else:
1326 1345 raise error.ParseError(_("unknown sort key %r") % k)
1327 1346 e.append(r)
1328 1347 l.append(e)
1329 1348 l.sort()
1330 1349 return [e[-1] for e in l]
1331 1350
1332 1351 def _stringmatcher(pattern):
1333 1352 """
1334 1353 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1335 1354 returns the matcher name, pattern, and matcher function.
1336 1355 missing or unknown prefixes are treated as literal matches.
1337 1356
1338 1357 helper for tests:
1339 1358 >>> def test(pattern, *tests):
1340 1359 ... kind, pattern, matcher = _stringmatcher(pattern)
1341 1360 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1342 1361
1343 1362 exact matching (no prefix):
1344 1363 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1345 1364 ('literal', 'abcdefg', [False, False, True])
1346 1365
1347 1366 regex matching ('re:' prefix)
1348 1367 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1349 1368 ('re', 'a.+b', [False, False, True])
1350 1369
1351 1370 force exact matches ('literal:' prefix)
1352 1371 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1353 1372 ('literal', 're:foobar', [False, True])
1354 1373
1355 1374 unknown prefixes are ignored and treated as literals
1356 1375 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1357 1376 ('literal', 'foo:bar', [False, False, True])
1358 1377 """
1359 1378 if pattern.startswith('re:'):
1360 1379 pattern = pattern[3:]
1361 1380 try:
1362 1381 regex = re.compile(pattern)
1363 1382 except re.error, e:
1364 1383 raise error.ParseError(_('invalid regular expression: %s')
1365 1384 % e)
1366 1385 return 're', pattern, regex.search
1367 1386 elif pattern.startswith('literal:'):
1368 1387 pattern = pattern[8:]
1369 1388 return 'literal', pattern, pattern.__eq__
1370 1389
1371 1390 def _substringmatcher(pattern):
1372 1391 kind, pattern, matcher = _stringmatcher(pattern)
1373 1392 if kind == 'literal':
1374 1393 matcher = lambda s: pattern in s
1375 1394 return kind, pattern, matcher
1376 1395
1377 1396 def tag(repo, subset, x):
1378 1397 """``tag([name])``
1379 1398 The specified tag by name, or all tagged revisions if no name is given.
1380 1399 """
1381 1400 # i18n: "tag" is a keyword
1382 1401 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1383 1402 cl = repo.changelog
1384 1403 if args:
1385 1404 pattern = getstring(args[0],
1386 1405 # i18n: "tag" is a keyword
1387 1406 _('the argument to tag must be a string'))
1388 1407 kind, pattern, matcher = _stringmatcher(pattern)
1389 1408 if kind == 'literal':
1390 1409 # avoid resolving all tags
1391 1410 tn = repo._tagscache.tags.get(pattern, None)
1392 1411 if tn is None:
1393 1412 raise util.Abort(_("tag '%s' does not exist") % pattern)
1394 1413 s = set([repo[tn].rev()])
1395 1414 else:
1396 1415 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1397 1416 if not s:
1398 1417 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1399 1418 else:
1400 1419 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1401 1420 return [r for r in subset if r in s]
1402 1421
1403 1422 def tagged(repo, subset, x):
1404 1423 return tag(repo, subset, x)
1405 1424
1406 1425 def unstable(repo, subset, x):
1407 1426 """``unstable()``
1408 1427 Unstable changesets are non-obsolete with obsolete descendants."""
1428 # i18n: "unstable" is a keyword
1409 1429 getargs(x, 0, 0, _("unstable takes no arguments"))
1410 1430 unstableset = set(repo.revs('(obsolete()::) - obsolete()'))
1411 1431 return [r for r in subset if r in unstableset]
1412 1432
1413 1433
1414 1434 def user(repo, subset, x):
1415 1435 """``user(string)``
1416 1436 User name contains string. The match is case-insensitive.
1417 1437
1418 1438 If `string` starts with `re:`, the remainder of the string is treated as
1419 1439 a regular expression. To match a user that actually contains `re:`, use
1420 1440 the prefix `literal:`.
1421 1441 """
1422 1442 return author(repo, subset, x)
1423 1443
1424 1444 # for internal use
1425 1445 def _list(repo, subset, x):
1426 1446 s = getstring(x, "internal error")
1427 1447 if not s:
1428 1448 return []
1429 1449 if not isinstance(subset, set):
1430 1450 subset = set(subset)
1431 1451 ls = [repo[r].rev() for r in s.split('\0')]
1432 1452 return [r for r in ls if r in subset]
1433 1453
1434 1454 symbols = {
1435 1455 "adds": adds,
1436 1456 "all": getall,
1437 1457 "ancestor": ancestor,
1438 1458 "ancestors": ancestors,
1439 1459 "_firstancestors": _firstancestors,
1440 1460 "author": author,
1441 1461 "bisect": bisect,
1442 1462 "bisected": bisected,
1443 1463 "bookmark": bookmark,
1444 1464 "branch": branch,
1445 1465 "children": children,
1446 1466 "closed": closed,
1447 1467 "contains": contains,
1448 1468 "converted": converted,
1449 1469 "date": date,
1450 1470 "desc": desc,
1451 1471 "descendants": descendants,
1452 1472 "_firstdescendants": _firstdescendants,
1453 1473 "destination": destination,
1454 1474 "draft": draft,
1455 1475 "extinct": extinct,
1456 1476 "extra": extra,
1457 1477 "file": hasfile,
1458 1478 "filelog": filelog,
1459 1479 "first": first,
1460 1480 "follow": follow,
1461 1481 "_followfirst": _followfirst,
1462 1482 "grep": grep,
1463 1483 "head": head,
1464 1484 "heads": heads,
1465 1485 "id": node_,
1466 1486 "keyword": keyword,
1467 1487 "last": last,
1468 1488 "limit": limit,
1469 1489 "_matchfiles": _matchfiles,
1470 1490 "max": maxrev,
1471 1491 "merge": merge,
1472 1492 "min": minrev,
1473 1493 "modifies": modifies,
1474 1494 "obsolete": obsolete,
1475 1495 "origin": origin,
1476 1496 "outgoing": outgoing,
1477 1497 "p1": p1,
1478 1498 "p2": p2,
1479 1499 "parents": parents,
1480 1500 "present": present,
1481 1501 "public": public,
1482 1502 "remote": remote,
1483 1503 "removes": removes,
1484 1504 "rev": rev,
1485 1505 "reverse": reverse,
1486 1506 "roots": roots,
1487 1507 "sort": sort,
1488 1508 "secret": secret,
1489 1509 "matching": matching,
1490 1510 "tag": tag,
1491 1511 "tagged": tagged,
1492 1512 "user": user,
1493 1513 "unstable": unstable,
1494 1514 "_list": _list,
1495 1515 }
1496 1516
1497 1517 methods = {
1498 1518 "range": rangeset,
1499 1519 "dagrange": dagrange,
1500 1520 "string": stringset,
1501 1521 "symbol": symbolset,
1502 1522 "and": andset,
1503 1523 "or": orset,
1504 1524 "not": notset,
1505 1525 "list": listset,
1506 1526 "func": func,
1507 1527 "ancestor": ancestorspec,
1508 1528 "parent": parentspec,
1509 1529 "parentpost": p1,
1510 1530 }
1511 1531
1512 1532 def optimize(x, small):
1513 1533 if x is None:
1514 1534 return 0, x
1515 1535
1516 1536 smallbonus = 1
1517 1537 if small:
1518 1538 smallbonus = .5
1519 1539
1520 1540 op = x[0]
1521 1541 if op == 'minus':
1522 1542 return optimize(('and', x[1], ('not', x[2])), small)
1523 1543 elif op == 'dagrangepre':
1524 1544 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1525 1545 elif op == 'dagrangepost':
1526 1546 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1527 1547 elif op == 'rangepre':
1528 1548 return optimize(('range', ('string', '0'), x[1]), small)
1529 1549 elif op == 'rangepost':
1530 1550 return optimize(('range', x[1], ('string', 'tip')), small)
1531 1551 elif op == 'negate':
1532 1552 return optimize(('string',
1533 1553 '-' + getstring(x[1], _("can't negate that"))), small)
1534 1554 elif op in 'string symbol negate':
1535 1555 return smallbonus, x # single revisions are small
1536 1556 elif op == 'and':
1537 1557 wa, ta = optimize(x[1], True)
1538 1558 wb, tb = optimize(x[2], True)
1539 1559 w = min(wa, wb)
1540 1560 if wa > wb:
1541 1561 return w, (op, tb, ta)
1542 1562 return w, (op, ta, tb)
1543 1563 elif op == 'or':
1544 1564 wa, ta = optimize(x[1], False)
1545 1565 wb, tb = optimize(x[2], False)
1546 1566 if wb < wa:
1547 1567 wb, wa = wa, wb
1548 1568 return max(wa, wb), (op, ta, tb)
1549 1569 elif op == 'not':
1550 1570 o = optimize(x[1], not small)
1551 1571 return o[0], (op, o[1])
1552 1572 elif op == 'parentpost':
1553 1573 o = optimize(x[1], small)
1554 1574 return o[0], (op, o[1])
1555 1575 elif op == 'group':
1556 1576 return optimize(x[1], small)
1557 1577 elif op in 'dagrange range list parent ancestorspec':
1558 1578 if op == 'parent':
1559 1579 # x^:y means (x^) : y, not x ^ (:y)
1560 1580 post = ('parentpost', x[1])
1561 1581 if x[2][0] == 'dagrangepre':
1562 1582 return optimize(('dagrange', post, x[2][1]), small)
1563 1583 elif x[2][0] == 'rangepre':
1564 1584 return optimize(('range', post, x[2][1]), small)
1565 1585
1566 1586 wa, ta = optimize(x[1], small)
1567 1587 wb, tb = optimize(x[2], small)
1568 1588 return wa + wb, (op, ta, tb)
1569 1589 elif op == 'func':
1570 1590 f = getstring(x[1], _("not a symbol"))
1571 1591 wa, ta = optimize(x[2], small)
1572 1592 if f in ("author branch closed date desc file grep keyword "
1573 1593 "outgoing user"):
1574 1594 w = 10 # slow
1575 1595 elif f in "modifies adds removes":
1576 1596 w = 30 # slower
1577 1597 elif f == "contains":
1578 1598 w = 100 # very slow
1579 1599 elif f == "ancestor":
1580 1600 w = 1 * smallbonus
1581 1601 elif f in "reverse limit first":
1582 1602 w = 0
1583 1603 elif f in "sort":
1584 1604 w = 10 # assume most sorts look at changelog
1585 1605 else:
1586 1606 w = 1
1587 1607 return w + wa, (op, x[1], ta)
1588 1608 return 1, x
1589 1609
1590 1610 _aliasarg = ('func', ('symbol', '_aliasarg'))
1591 1611 def _getaliasarg(tree):
1592 1612 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1593 1613 return X, None otherwise.
1594 1614 """
1595 1615 if (len(tree) == 3 and tree[:2] == _aliasarg
1596 1616 and tree[2][0] == 'string'):
1597 1617 return tree[2][1]
1598 1618 return None
1599 1619
1600 1620 def _checkaliasarg(tree, known=None):
1601 1621 """Check tree contains no _aliasarg construct or only ones which
1602 1622 value is in known. Used to avoid alias placeholders injection.
1603 1623 """
1604 1624 if isinstance(tree, tuple):
1605 1625 arg = _getaliasarg(tree)
1606 1626 if arg is not None and (not known or arg not in known):
1607 1627 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1608 1628 for t in tree:
1609 1629 _checkaliasarg(t, known)
1610 1630
1611 1631 class revsetalias(object):
1612 1632 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1613 1633 args = None
1614 1634
1615 1635 def __init__(self, name, value):
1616 1636 '''Aliases like:
1617 1637
1618 1638 h = heads(default)
1619 1639 b($1) = ancestors($1) - ancestors(default)
1620 1640 '''
1621 1641 m = self.funcre.search(name)
1622 1642 if m:
1623 1643 self.name = m.group(1)
1624 1644 self.tree = ('func', ('symbol', m.group(1)))
1625 1645 self.args = [x.strip() for x in m.group(2).split(',')]
1626 1646 for arg in self.args:
1627 1647 # _aliasarg() is an unknown symbol only used separate
1628 1648 # alias argument placeholders from regular strings.
1629 1649 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1630 1650 else:
1631 1651 self.name = name
1632 1652 self.tree = ('symbol', name)
1633 1653
1634 1654 self.replacement, pos = parse(value)
1635 1655 if pos != len(value):
1636 1656 raise error.ParseError(_('invalid token'), pos)
1637 1657 # Check for placeholder injection
1638 1658 _checkaliasarg(self.replacement, self.args)
1639 1659
1640 1660 def _getalias(aliases, tree):
1641 1661 """If tree looks like an unexpanded alias, return it. Return None
1642 1662 otherwise.
1643 1663 """
1644 1664 if isinstance(tree, tuple) and tree:
1645 1665 if tree[0] == 'symbol' and len(tree) == 2:
1646 1666 name = tree[1]
1647 1667 alias = aliases.get(name)
1648 1668 if alias and alias.args is None and alias.tree == tree:
1649 1669 return alias
1650 1670 if tree[0] == 'func' and len(tree) > 1:
1651 1671 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1652 1672 name = tree[1][1]
1653 1673 alias = aliases.get(name)
1654 1674 if alias and alias.args is not None and alias.tree == tree[:2]:
1655 1675 return alias
1656 1676 return None
1657 1677
1658 1678 def _expandargs(tree, args):
1659 1679 """Replace _aliasarg instances with the substitution value of the
1660 1680 same name in args, recursively.
1661 1681 """
1662 1682 if not tree or not isinstance(tree, tuple):
1663 1683 return tree
1664 1684 arg = _getaliasarg(tree)
1665 1685 if arg is not None:
1666 1686 return args[arg]
1667 1687 return tuple(_expandargs(t, args) for t in tree)
1668 1688
1669 1689 def _expandaliases(aliases, tree, expanding, cache):
1670 1690 """Expand aliases in tree, recursively.
1671 1691
1672 1692 'aliases' is a dictionary mapping user defined aliases to
1673 1693 revsetalias objects.
1674 1694 """
1675 1695 if not isinstance(tree, tuple):
1676 1696 # Do not expand raw strings
1677 1697 return tree
1678 1698 alias = _getalias(aliases, tree)
1679 1699 if alias is not None:
1680 1700 if alias in expanding:
1681 1701 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1682 1702 'detected') % alias.name)
1683 1703 expanding.append(alias)
1684 1704 if alias.name not in cache:
1685 1705 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1686 1706 expanding, cache)
1687 1707 result = cache[alias.name]
1688 1708 expanding.pop()
1689 1709 if alias.args is not None:
1690 1710 l = getlist(tree[2])
1691 1711 if len(l) != len(alias.args):
1692 1712 raise error.ParseError(
1693 1713 _('invalid number of arguments: %s') % len(l))
1694 1714 l = [_expandaliases(aliases, a, [], cache) for a in l]
1695 1715 result = _expandargs(result, dict(zip(alias.args, l)))
1696 1716 else:
1697 1717 result = tuple(_expandaliases(aliases, t, expanding, cache)
1698 1718 for t in tree)
1699 1719 return result
1700 1720
1701 1721 def findaliases(ui, tree):
1702 1722 _checkaliasarg(tree)
1703 1723 aliases = {}
1704 1724 for k, v in ui.configitems('revsetalias'):
1705 1725 alias = revsetalias(k, v)
1706 1726 aliases[alias.name] = alias
1707 1727 return _expandaliases(aliases, tree, [], {})
1708 1728
1709 1729 parse = parser.parser(tokenize, elements).parse
1710 1730
1711 1731 def match(ui, spec):
1712 1732 if not spec:
1713 1733 raise error.ParseError(_("empty query"))
1714 1734 tree, pos = parse(spec)
1715 1735 if (pos != len(spec)):
1716 1736 raise error.ParseError(_("invalid token"), pos)
1717 1737 if ui:
1718 1738 tree = findaliases(ui, tree)
1719 1739 weight, tree = optimize(tree, True)
1720 1740 def mfunc(repo, subset):
1721 1741 return getset(repo, subset, tree)
1722 1742 return mfunc
1723 1743
1724 1744 def formatspec(expr, *args):
1725 1745 '''
1726 1746 This is a convenience function for using revsets internally, and
1727 1747 escapes arguments appropriately. Aliases are intentionally ignored
1728 1748 so that intended expression behavior isn't accidentally subverted.
1729 1749
1730 1750 Supported arguments:
1731 1751
1732 1752 %r = revset expression, parenthesized
1733 1753 %d = int(arg), no quoting
1734 1754 %s = string(arg), escaped and single-quoted
1735 1755 %b = arg.branch(), escaped and single-quoted
1736 1756 %n = hex(arg), single-quoted
1737 1757 %% = a literal '%'
1738 1758
1739 1759 Prefixing the type with 'l' specifies a parenthesized list of that type.
1740 1760
1741 1761 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1742 1762 '(10 or 11):: and ((this()) or (that()))'
1743 1763 >>> formatspec('%d:: and not %d::', 10, 20)
1744 1764 '10:: and not 20::'
1745 1765 >>> formatspec('%ld or %ld', [], [1])
1746 1766 "_list('') or 1"
1747 1767 >>> formatspec('keyword(%s)', 'foo\\xe9')
1748 1768 "keyword('foo\\\\xe9')"
1749 1769 >>> b = lambda: 'default'
1750 1770 >>> b.branch = b
1751 1771 >>> formatspec('branch(%b)', b)
1752 1772 "branch('default')"
1753 1773 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1754 1774 "root(_list('a\\x00b\\x00c\\x00d'))"
1755 1775 '''
1756 1776
1757 1777 def quote(s):
1758 1778 return repr(str(s))
1759 1779
1760 1780 def argtype(c, arg):
1761 1781 if c == 'd':
1762 1782 return str(int(arg))
1763 1783 elif c == 's':
1764 1784 return quote(arg)
1765 1785 elif c == 'r':
1766 1786 parse(arg) # make sure syntax errors are confined
1767 1787 return '(%s)' % arg
1768 1788 elif c == 'n':
1769 1789 return quote(node.hex(arg))
1770 1790 elif c == 'b':
1771 1791 return quote(arg.branch())
1772 1792
1773 1793 def listexp(s, t):
1774 1794 l = len(s)
1775 1795 if l == 0:
1776 1796 return "_list('')"
1777 1797 elif l == 1:
1778 1798 return argtype(t, s[0])
1779 1799 elif t == 'd':
1780 1800 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1781 1801 elif t == 's':
1782 1802 return "_list('%s')" % "\0".join(s)
1783 1803 elif t == 'n':
1784 1804 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1785 1805 elif t == 'b':
1786 1806 return "_list('%s')" % "\0".join(a.branch() for a in s)
1787 1807
1788 1808 m = l // 2
1789 1809 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1790 1810
1791 1811 ret = ''
1792 1812 pos = 0
1793 1813 arg = 0
1794 1814 while pos < len(expr):
1795 1815 c = expr[pos]
1796 1816 if c == '%':
1797 1817 pos += 1
1798 1818 d = expr[pos]
1799 1819 if d == '%':
1800 1820 ret += d
1801 1821 elif d in 'dsnbr':
1802 1822 ret += argtype(d, args[arg])
1803 1823 arg += 1
1804 1824 elif d == 'l':
1805 1825 # a list of some type
1806 1826 pos += 1
1807 1827 d = expr[pos]
1808 1828 ret += listexp(list(args[arg]), d)
1809 1829 arg += 1
1810 1830 else:
1811 1831 raise util.Abort('unexpected revspec format character %s' % d)
1812 1832 else:
1813 1833 ret += c
1814 1834 pos += 1
1815 1835
1816 1836 return ret
1817 1837
1818 1838 def prettyformat(tree):
1819 1839 def _prettyformat(tree, level, lines):
1820 1840 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1821 1841 lines.append((level, str(tree)))
1822 1842 else:
1823 1843 lines.append((level, '(%s' % tree[0]))
1824 1844 for s in tree[1:]:
1825 1845 _prettyformat(s, level + 1, lines)
1826 1846 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1827 1847
1828 1848 lines = []
1829 1849 _prettyformat(tree, 0, lines)
1830 1850 output = '\n'.join((' '*l + s) for l, s in lines)
1831 1851 return output
1832 1852
1833 1853 # tell hggettext to extract docstrings from these functions:
1834 1854 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now