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