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