##// END OF EJS Templates
revset: avoid demandimport bug...
Matt Mackall -
r16417:b4b0c693 default
parent child Browse files
Show More
@@ -1,1467 +1,1468 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re
9 9 import parser, util, error, discovery, hbisect, phases
10 import node as nodemod
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 = list(revs)
21 seen = set([nodemod.nullrev])
21 seen = set([node.nullrev])
22 22 while visit:
23 23 for parent in cl.parentrevs(visit.pop(0))[: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 if first == nodemod.nullrev:
34 nullrev = node.nullrev
35 if first == nullrev:
35 36 # Are there nodes with a null first parent and a non-null
36 37 # second one? Maybe. Do we care? Probably not.
37 38 for i in cl:
38 39 yield i
39 40 return
40 41
41 42 seen = set(revs)
42 43 for i in xrange(first + 1, len(cl)):
43 44 for x in cl.parentrevs(i)[:cut]:
44 if x != nodemod.nullrev and x in seen:
45 if x != nullrev and x in seen:
45 46 seen.add(i)
46 47 yield i
47 48 break
48 49
49 50 elements = {
50 51 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
51 52 "~": (18, None, ("ancestor", 18)),
52 53 "^": (18, None, ("parent", 18), ("parentpost", 18)),
53 54 "-": (5, ("negate", 19), ("minus", 5)),
54 55 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
55 56 ("dagrangepost", 17)),
56 57 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
57 58 ("dagrangepost", 17)),
58 59 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
59 60 "not": (10, ("not", 10)),
60 61 "!": (10, ("not", 10)),
61 62 "and": (5, None, ("and", 5)),
62 63 "&": (5, None, ("and", 5)),
63 64 "or": (4, None, ("or", 4)),
64 65 "|": (4, None, ("or", 4)),
65 66 "+": (4, None, ("or", 4)),
66 67 ",": (2, None, ("list", 2)),
67 68 ")": (0, None, None),
68 69 "symbol": (0, ("symbol",), None),
69 70 "string": (0, ("string",), None),
70 71 "end": (0, None, None),
71 72 }
72 73
73 74 keywords = set(['and', 'or', 'not'])
74 75
75 76 def tokenize(program):
76 77 pos, l = 0, len(program)
77 78 while pos < l:
78 79 c = program[pos]
79 80 if c.isspace(): # skip inter-token whitespace
80 81 pass
81 82 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
82 83 yield ('::', None, pos)
83 84 pos += 1 # skip ahead
84 85 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
85 86 yield ('..', None, pos)
86 87 pos += 1 # skip ahead
87 88 elif c in "():,-|&+!~^": # handle simple operators
88 89 yield (c, None, pos)
89 90 elif (c in '"\'' or c == 'r' and
90 91 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
91 92 if c == 'r':
92 93 pos += 1
93 94 c = program[pos]
94 95 decode = lambda x: x
95 96 else:
96 97 decode = lambda x: x.decode('string-escape')
97 98 pos += 1
98 99 s = pos
99 100 while pos < l: # find closing quote
100 101 d = program[pos]
101 102 if d == '\\': # skip over escaped characters
102 103 pos += 2
103 104 continue
104 105 if d == c:
105 106 yield ('string', decode(program[s:pos]), s)
106 107 break
107 108 pos += 1
108 109 else:
109 110 raise error.ParseError(_("unterminated string"), s)
110 111 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
111 112 s = pos
112 113 pos += 1
113 114 while pos < l: # find end of symbol
114 115 d = program[pos]
115 116 if not (d.isalnum() or d in "._/" or ord(d) > 127):
116 117 break
117 118 if d == '.' and program[pos - 1] == '.': # special case for ..
118 119 pos -= 1
119 120 break
120 121 pos += 1
121 122 sym = program[s:pos]
122 123 if sym in keywords: # operator keywords
123 124 yield (sym, None, s)
124 125 else:
125 126 yield ('symbol', sym, s)
126 127 pos -= 1
127 128 else:
128 129 raise error.ParseError(_("syntax error"), pos)
129 130 pos += 1
130 131 yield ('end', None, pos)
131 132
132 133 # helpers
133 134
134 135 def getstring(x, err):
135 136 if x and (x[0] == 'string' or x[0] == 'symbol'):
136 137 return x[1]
137 138 raise error.ParseError(err)
138 139
139 140 def getlist(x):
140 141 if not x:
141 142 return []
142 143 if x[0] == 'list':
143 144 return getlist(x[1]) + [x[2]]
144 145 return [x]
145 146
146 147 def getargs(x, min, max, err):
147 148 l = getlist(x)
148 149 if len(l) < min or (max >= 0 and len(l) > max):
149 150 raise error.ParseError(err)
150 151 return l
151 152
152 153 def getset(repo, subset, x):
153 154 if not x:
154 155 raise error.ParseError(_("missing argument"))
155 156 return methods[x[0]](repo, subset, *x[1:])
156 157
157 158 # operator methods
158 159
159 160 def stringset(repo, subset, x):
160 161 x = repo[x].rev()
161 162 if x == -1 and len(subset) == len(repo):
162 163 return [-1]
163 164 if len(subset) == len(repo) or x in subset:
164 165 return [x]
165 166 return []
166 167
167 168 def symbolset(repo, subset, x):
168 169 if x in symbols:
169 170 raise error.ParseError(_("can't use %s here") % x)
170 171 return stringset(repo, subset, x)
171 172
172 173 def rangeset(repo, subset, x, y):
173 174 m = getset(repo, subset, x)
174 175 if not m:
175 176 m = getset(repo, range(len(repo)), x)
176 177
177 178 n = getset(repo, subset, y)
178 179 if not n:
179 180 n = getset(repo, range(len(repo)), y)
180 181
181 182 if not m or not n:
182 183 return []
183 184 m, n = m[0], n[-1]
184 185
185 186 if m < n:
186 187 r = range(m, n + 1)
187 188 else:
188 189 r = range(m, n - 1, -1)
189 190 s = set(subset)
190 191 return [x for x in r if x in s]
191 192
192 193 def andset(repo, subset, x, y):
193 194 return getset(repo, getset(repo, subset, x), y)
194 195
195 196 def orset(repo, subset, x, y):
196 197 xl = getset(repo, subset, x)
197 198 s = set(xl)
198 199 yl = getset(repo, [r for r in subset if r not in s], y)
199 200 return xl + yl
200 201
201 202 def notset(repo, subset, x):
202 203 s = set(getset(repo, subset, x))
203 204 return [r for r in subset if r not in s]
204 205
205 206 def listset(repo, subset, a, b):
206 207 raise error.ParseError(_("can't use a list in this context"))
207 208
208 209 def func(repo, subset, a, b):
209 210 if a[0] == 'symbol' and a[1] in symbols:
210 211 return symbols[a[1]](repo, subset, b)
211 212 raise error.ParseError(_("not a function: %s") % a[1])
212 213
213 214 # functions
214 215
215 216 def adds(repo, subset, x):
216 217 """``adds(pattern)``
217 218 Changesets that add a file matching pattern.
218 219 """
219 220 # i18n: "adds" is a keyword
220 221 pat = getstring(x, _("adds requires a pattern"))
221 222 return checkstatus(repo, subset, pat, 1)
222 223
223 224 def ancestor(repo, subset, x):
224 225 """``ancestor(single, single)``
225 226 Greatest common ancestor of the two changesets.
226 227 """
227 228 # i18n: "ancestor" is a keyword
228 229 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
229 230 r = range(len(repo))
230 231 a = getset(repo, r, l[0])
231 232 b = getset(repo, r, l[1])
232 233 if len(a) != 1 or len(b) != 1:
233 234 # i18n: "ancestor" is a keyword
234 235 raise error.ParseError(_("ancestor arguments must be single revisions"))
235 236 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
236 237
237 238 return [r for r in an if r in subset]
238 239
239 240 def _ancestors(repo, subset, x, followfirst=False):
240 241 args = getset(repo, range(len(repo)), x)
241 242 if not args:
242 243 return []
243 244 s = set(_revancestors(repo, args, followfirst)) | set(args)
244 245 return [r for r in subset if r in s]
245 246
246 247 def ancestors(repo, subset, x):
247 248 """``ancestors(set)``
248 249 Changesets that are ancestors of a changeset in set.
249 250 """
250 251 return _ancestors(repo, subset, x)
251 252
252 253 def _firstancestors(repo, subset, x):
253 254 # ``_firstancestors(set)``
254 255 # Like ``ancestors(set)`` but follows only the first parents.
255 256 return _ancestors(repo, subset, x, followfirst=True)
256 257
257 258 def ancestorspec(repo, subset, x, n):
258 259 """``set~n``
259 260 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
260 261 """
261 262 try:
262 263 n = int(n[1])
263 264 except (TypeError, ValueError):
264 265 raise error.ParseError(_("~ expects a number"))
265 266 ps = set()
266 267 cl = repo.changelog
267 268 for r in getset(repo, subset, x):
268 269 for i in range(n):
269 270 r = cl.parentrevs(r)[0]
270 271 ps.add(r)
271 272 return [r for r in subset if r in ps]
272 273
273 274 def author(repo, subset, x):
274 275 """``author(string)``
275 276 Alias for ``user(string)``.
276 277 """
277 278 # i18n: "author" is a keyword
278 279 n = encoding.lower(getstring(x, _("author requires a string")))
279 280 return [r for r in subset if n in encoding.lower(repo[r].user())]
280 281
281 282 def bisect(repo, subset, x):
282 283 """``bisect(string)``
283 284 Changesets marked in the specified bisect status:
284 285
285 286 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
286 287 - ``goods``, ``bads`` : csets topologicaly good/bad
287 288 - ``range`` : csets taking part in the bisection
288 289 - ``pruned`` : csets that are goods, bads or skipped
289 290 - ``untested`` : csets whose fate is yet unknown
290 291 - ``ignored`` : csets ignored due to DAG topology
291 292 """
292 293 status = getstring(x, _("bisect requires a string")).lower()
293 294 return [r for r in subset if r in hbisect.get(repo, status)]
294 295
295 296 # Backward-compatibility
296 297 # - no help entry so that we do not advertise it any more
297 298 def bisected(repo, subset, x):
298 299 return bisect(repo, subset, x)
299 300
300 301 def bookmark(repo, subset, x):
301 302 """``bookmark([name])``
302 303 The named bookmark or all bookmarks.
303 304 """
304 305 # i18n: "bookmark" is a keyword
305 306 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
306 307 if args:
307 308 bm = getstring(args[0],
308 309 # i18n: "bookmark" is a keyword
309 310 _('the argument to bookmark must be a string'))
310 311 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
311 312 if not bmrev:
312 313 raise util.Abort(_("bookmark '%s' does not exist") % bm)
313 314 bmrev = repo[bmrev].rev()
314 315 return [r for r in subset if r == bmrev]
315 316 bms = set([repo[r].rev()
316 317 for r in bookmarksmod.listbookmarks(repo).values()])
317 318 return [r for r in subset if r in bms]
318 319
319 320 def branch(repo, subset, x):
320 321 """``branch(string or set)``
321 322 All changesets belonging to the given branch or the branches of the given
322 323 changesets.
323 324 """
324 325 try:
325 326 b = getstring(x, '')
326 327 if b in repo.branchmap():
327 328 return [r for r in subset if repo[r].branch() == b]
328 329 except error.ParseError:
329 330 # not a string, but another revspec, e.g. tip()
330 331 pass
331 332
332 333 s = getset(repo, range(len(repo)), x)
333 334 b = set()
334 335 for r in s:
335 336 b.add(repo[r].branch())
336 337 s = set(s)
337 338 return [r for r in subset if r in s or repo[r].branch() in b]
338 339
339 340 def checkstatus(repo, subset, pat, field):
340 341 m = None
341 342 s = []
342 343 fast = not matchmod.patkind(pat)
343 344 for r in subset:
344 345 c = repo[r]
345 346 if fast:
346 347 if pat not in c.files():
347 348 continue
348 349 else:
349 350 if not m or matchmod.patkind(pat) == 'set':
350 351 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
351 352 for f in c.files():
352 353 if m(f):
353 354 break
354 355 else:
355 356 continue
356 357 files = repo.status(c.p1().node(), c.node())[field]
357 358 if fast:
358 359 if pat in files:
359 360 s.append(r)
360 361 else:
361 362 for f in files:
362 363 if m(f):
363 364 s.append(r)
364 365 break
365 366 return s
366 367
367 368 def _children(repo, narrow, parentset):
368 369 cs = set()
369 370 pr = repo.changelog.parentrevs
370 371 for r in narrow:
371 372 for p in pr(r):
372 373 if p in parentset:
373 374 cs.add(r)
374 375 return cs
375 376
376 377 def children(repo, subset, x):
377 378 """``children(set)``
378 379 Child changesets of changesets in set.
379 380 """
380 381 s = set(getset(repo, range(len(repo)), x))
381 382 cs = _children(repo, subset, s)
382 383 return [r for r in subset if r in cs]
383 384
384 385 def closed(repo, subset, x):
385 386 """``closed()``
386 387 Changeset is closed.
387 388 """
388 389 # i18n: "closed" is a keyword
389 390 getargs(x, 0, 0, _("closed takes no arguments"))
390 391 return [r for r in subset if repo[r].extra().get('close')]
391 392
392 393 def contains(repo, subset, x):
393 394 """``contains(pattern)``
394 395 Revision contains a file matching pattern. See :hg:`help patterns`
395 396 for information about file patterns.
396 397 """
397 398 # i18n: "contains" is a keyword
398 399 pat = getstring(x, _("contains requires a pattern"))
399 400 m = None
400 401 s = []
401 402 if not matchmod.patkind(pat):
402 403 for r in subset:
403 404 if pat in repo[r]:
404 405 s.append(r)
405 406 else:
406 407 for r in subset:
407 408 c = repo[r]
408 409 if not m or matchmod.patkind(pat) == 'set':
409 410 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
410 411 for f in c.manifest():
411 412 if m(f):
412 413 s.append(r)
413 414 break
414 415 return s
415 416
416 417 def date(repo, subset, x):
417 418 """``date(interval)``
418 419 Changesets within the interval, see :hg:`help dates`.
419 420 """
420 421 # i18n: "date" is a keyword
421 422 ds = getstring(x, _("date requires a string"))
422 423 dm = util.matchdate(ds)
423 424 return [r for r in subset if dm(repo[r].date()[0])]
424 425
425 426 def desc(repo, subset, x):
426 427 """``desc(string)``
427 428 Search commit message for string. The match is case-insensitive.
428 429 """
429 430 # i18n: "desc" is a keyword
430 431 ds = encoding.lower(getstring(x, _("desc requires a string")))
431 432 l = []
432 433 for r in subset:
433 434 c = repo[r]
434 435 if ds in encoding.lower(c.description()):
435 436 l.append(r)
436 437 return l
437 438
438 439 def _descendants(repo, subset, x, followfirst=False):
439 440 args = getset(repo, range(len(repo)), x)
440 441 if not args:
441 442 return []
442 443 s = set(_revdescendants(repo, args, followfirst)) | set(args)
443 444 return [r for r in subset if r in s]
444 445
445 446 def descendants(repo, subset, x):
446 447 """``descendants(set)``
447 448 Changesets which are descendants of changesets in set.
448 449 """
449 450 return _descendants(repo, subset, x)
450 451
451 452 def _firstdescendants(repo, subset, x):
452 453 # ``_firstdescendants(set)``
453 454 # Like ``descendants(set)`` but follows only the first parents.
454 455 return _descendants(repo, subset, x, followfirst=True)
455 456
456 457 def draft(repo, subset, x):
457 458 """``draft()``
458 459 Changeset in draft phase."""
459 460 getargs(x, 0, 0, _("draft takes no arguments"))
460 461 return [r for r in subset if repo._phaserev[r] == phases.draft]
461 462
462 463 def filelog(repo, subset, x):
463 464 """``filelog(pattern)``
464 465 Changesets connected to the specified filelog.
465 466 """
466 467
467 468 pat = getstring(x, _("filelog requires a pattern"))
468 469 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
469 470 ctx=repo[None])
470 471 s = set()
471 472
472 473 if not matchmod.patkind(pat):
473 474 for f in m.files():
474 475 fl = repo.file(f)
475 476 for fr in fl:
476 477 s.add(fl.linkrev(fr))
477 478 else:
478 479 for f in repo[None]:
479 480 if m(f):
480 481 fl = repo.file(f)
481 482 for fr in fl:
482 483 s.add(fl.linkrev(fr))
483 484
484 485 return [r for r in subset if r in s]
485 486
486 487 def first(repo, subset, x):
487 488 """``first(set, [n])``
488 489 An alias for limit().
489 490 """
490 491 return limit(repo, subset, x)
491 492
492 493 def _follow(repo, subset, x, name, followfirst=False):
493 494 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
494 495 c = repo['.']
495 496 if l:
496 497 x = getstring(l[0], _("%s expected a filename") % name)
497 498 if x in c:
498 499 cx = c[x]
499 500 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
500 501 # include the revision responsible for the most recent version
501 502 s.add(cx.linkrev())
502 503 else:
503 504 return []
504 505 else:
505 506 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
506 507
507 508 return [r for r in subset if r in s]
508 509
509 510 def follow(repo, subset, x):
510 511 """``follow([file])``
511 512 An alias for ``::.`` (ancestors of the working copy's first parent).
512 513 If a filename is specified, the history of the given file is followed,
513 514 including copies.
514 515 """
515 516 return _follow(repo, subset, x, 'follow')
516 517
517 518 def _followfirst(repo, subset, x):
518 519 # ``followfirst([file])``
519 520 # Like ``follow([file])`` but follows only the first parent of
520 521 # every revision or file revision.
521 522 return _follow(repo, subset, x, '_followfirst', followfirst=True)
522 523
523 524 def getall(repo, subset, x):
524 525 """``all()``
525 526 All changesets, the same as ``0:tip``.
526 527 """
527 528 # i18n: "all" is a keyword
528 529 getargs(x, 0, 0, _("all takes no arguments"))
529 530 return subset
530 531
531 532 def grep(repo, subset, x):
532 533 """``grep(regex)``
533 534 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
534 535 to ensure special escape characters are handled correctly. Unlike
535 536 ``keyword(string)``, the match is case-sensitive.
536 537 """
537 538 try:
538 539 # i18n: "grep" is a keyword
539 540 gr = re.compile(getstring(x, _("grep requires a string")))
540 541 except re.error, e:
541 542 raise error.ParseError(_('invalid match pattern: %s') % e)
542 543 l = []
543 544 for r in subset:
544 545 c = repo[r]
545 546 for e in c.files() + [c.user(), c.description()]:
546 547 if gr.search(e):
547 548 l.append(r)
548 549 break
549 550 return l
550 551
551 552 def _matchfiles(repo, subset, x):
552 553 # _matchfiles takes a revset list of prefixed arguments:
553 554 #
554 555 # [p:foo, i:bar, x:baz]
555 556 #
556 557 # builds a match object from them and filters subset. Allowed
557 558 # prefixes are 'p:' for regular patterns, 'i:' for include
558 559 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
559 560 # a revision identifier, or the empty string to reference the
560 561 # working directory, from which the match object is
561 562 # initialized. Use 'd:' to set the default matching mode, default
562 563 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
563 564
564 565 # i18n: "_matchfiles" is a keyword
565 566 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
566 567 pats, inc, exc = [], [], []
567 568 hasset = False
568 569 rev, default = None, None
569 570 for arg in l:
570 571 s = getstring(arg, _("_matchfiles requires string arguments"))
571 572 prefix, value = s[:2], s[2:]
572 573 if prefix == 'p:':
573 574 pats.append(value)
574 575 elif prefix == 'i:':
575 576 inc.append(value)
576 577 elif prefix == 'x:':
577 578 exc.append(value)
578 579 elif prefix == 'r:':
579 580 if rev is not None:
580 581 raise error.ParseError(_('_matchfiles expected at most one '
581 582 'revision'))
582 583 rev = value
583 584 elif prefix == 'd:':
584 585 if default is not None:
585 586 raise error.ParseError(_('_matchfiles expected at most one '
586 587 'default mode'))
587 588 default = value
588 589 else:
589 590 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
590 591 if not hasset and matchmod.patkind(value) == 'set':
591 592 hasset = True
592 593 if not default:
593 594 default = 'glob'
594 595 m = None
595 596 s = []
596 597 for r in subset:
597 598 c = repo[r]
598 599 if not m or (hasset and rev is None):
599 600 ctx = c
600 601 if rev is not None:
601 602 ctx = repo[rev or None]
602 603 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
603 604 exclude=exc, ctx=ctx, default=default)
604 605 for f in c.files():
605 606 if m(f):
606 607 s.append(r)
607 608 break
608 609 return s
609 610
610 611 def hasfile(repo, subset, x):
611 612 """``file(pattern)``
612 613 Changesets affecting files matched by pattern.
613 614 """
614 615 # i18n: "file" is a keyword
615 616 pat = getstring(x, _("file requires a pattern"))
616 617 return _matchfiles(repo, subset, ('string', 'p:' + pat))
617 618
618 619 def head(repo, subset, x):
619 620 """``head()``
620 621 Changeset is a named branch head.
621 622 """
622 623 # i18n: "head" is a keyword
623 624 getargs(x, 0, 0, _("head takes no arguments"))
624 625 hs = set()
625 626 for b, ls in repo.branchmap().iteritems():
626 627 hs.update(repo[h].rev() for h in ls)
627 628 return [r for r in subset if r in hs]
628 629
629 630 def heads(repo, subset, x):
630 631 """``heads(set)``
631 632 Members of set with no children in set.
632 633 """
633 634 s = getset(repo, subset, x)
634 635 ps = set(parents(repo, subset, x))
635 636 return [r for r in s if r not in ps]
636 637
637 638 def keyword(repo, subset, x):
638 639 """``keyword(string)``
639 640 Search commit message, user name, and names of changed files for
640 641 string. The match is case-insensitive.
641 642 """
642 643 # i18n: "keyword" is a keyword
643 644 kw = encoding.lower(getstring(x, _("keyword requires a string")))
644 645 l = []
645 646 for r in subset:
646 647 c = repo[r]
647 648 t = " ".join(c.files() + [c.user(), c.description()])
648 649 if kw in encoding.lower(t):
649 650 l.append(r)
650 651 return l
651 652
652 653 def limit(repo, subset, x):
653 654 """``limit(set, [n])``
654 655 First n members of set, defaulting to 1.
655 656 """
656 657 # i18n: "limit" is a keyword
657 658 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
658 659 try:
659 660 lim = 1
660 661 if len(l) == 2:
661 662 # i18n: "limit" is a keyword
662 663 lim = int(getstring(l[1], _("limit requires a number")))
663 664 except (TypeError, ValueError):
664 665 # i18n: "limit" is a keyword
665 666 raise error.ParseError(_("limit expects a number"))
666 667 ss = set(subset)
667 668 os = getset(repo, range(len(repo)), l[0])[:lim]
668 669 return [r for r in os if r in ss]
669 670
670 671 def last(repo, subset, x):
671 672 """``last(set, [n])``
672 673 Last n members of set, defaulting to 1.
673 674 """
674 675 # i18n: "last" is a keyword
675 676 l = getargs(x, 1, 2, _("last requires one or two arguments"))
676 677 try:
677 678 lim = 1
678 679 if len(l) == 2:
679 680 # i18n: "last" is a keyword
680 681 lim = int(getstring(l[1], _("last requires a number")))
681 682 except (TypeError, ValueError):
682 683 # i18n: "last" is a keyword
683 684 raise error.ParseError(_("last expects a number"))
684 685 ss = set(subset)
685 686 os = getset(repo, range(len(repo)), l[0])[-lim:]
686 687 return [r for r in os if r in ss]
687 688
688 689 def maxrev(repo, subset, x):
689 690 """``max(set)``
690 691 Changeset with highest revision number in set.
691 692 """
692 693 os = getset(repo, range(len(repo)), x)
693 694 if os:
694 695 m = max(os)
695 696 if m in subset:
696 697 return [m]
697 698 return []
698 699
699 700 def merge(repo, subset, x):
700 701 """``merge()``
701 702 Changeset is a merge changeset.
702 703 """
703 704 # i18n: "merge" is a keyword
704 705 getargs(x, 0, 0, _("merge takes no arguments"))
705 706 cl = repo.changelog
706 707 return [r for r in subset if cl.parentrevs(r)[1] != -1]
707 708
708 709 def minrev(repo, subset, x):
709 710 """``min(set)``
710 711 Changeset with lowest revision number in set.
711 712 """
712 713 os = getset(repo, range(len(repo)), x)
713 714 if os:
714 715 m = min(os)
715 716 if m in subset:
716 717 return [m]
717 718 return []
718 719
719 720 def modifies(repo, subset, x):
720 721 """``modifies(pattern)``
721 722 Changesets modifying files matched by pattern.
722 723 """
723 724 # i18n: "modifies" is a keyword
724 725 pat = getstring(x, _("modifies requires a pattern"))
725 726 return checkstatus(repo, subset, pat, 0)
726 727
727 def node(repo, subset, x):
728 def node_(repo, subset, x):
728 729 """``id(string)``
729 730 Revision non-ambiguously specified by the given hex string prefix.
730 731 """
731 732 # i18n: "id" is a keyword
732 733 l = getargs(x, 1, 1, _("id requires one argument"))
733 734 # i18n: "id" is a keyword
734 735 n = getstring(l[0], _("id requires a string"))
735 736 if len(n) == 40:
736 737 rn = repo[n].rev()
737 738 else:
738 739 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
739 740 return [r for r in subset if r == rn]
740 741
741 742 def outgoing(repo, subset, x):
742 743 """``outgoing([path])``
743 744 Changesets not found in the specified destination repository, or the
744 745 default push location.
745 746 """
746 747 import hg # avoid start-up nasties
747 748 # i18n: "outgoing" is a keyword
748 749 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
749 750 # i18n: "outgoing" is a keyword
750 751 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
751 752 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
752 753 dest, branches = hg.parseurl(dest)
753 754 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
754 755 if revs:
755 756 revs = [repo.lookup(rev) for rev in revs]
756 757 other = hg.peer(repo, {}, dest)
757 758 repo.ui.pushbuffer()
758 759 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
759 760 repo.ui.popbuffer()
760 761 cl = repo.changelog
761 762 o = set([cl.rev(r) for r in outgoing.missing])
762 763 return [r for r in subset if r in o]
763 764
764 765 def p1(repo, subset, x):
765 766 """``p1([set])``
766 767 First parent of changesets in set, or the working directory.
767 768 """
768 769 if x is None:
769 770 p = repo[x].p1().rev()
770 771 return [r for r in subset if r == p]
771 772
772 773 ps = set()
773 774 cl = repo.changelog
774 775 for r in getset(repo, range(len(repo)), x):
775 776 ps.add(cl.parentrevs(r)[0])
776 777 return [r for r in subset if r in ps]
777 778
778 779 def p2(repo, subset, x):
779 780 """``p2([set])``
780 781 Second parent of changesets in set, or the working directory.
781 782 """
782 783 if x is None:
783 784 ps = repo[x].parents()
784 785 try:
785 786 p = ps[1].rev()
786 787 return [r for r in subset if r == p]
787 788 except IndexError:
788 789 return []
789 790
790 791 ps = set()
791 792 cl = repo.changelog
792 793 for r in getset(repo, range(len(repo)), x):
793 794 ps.add(cl.parentrevs(r)[1])
794 795 return [r for r in subset if r in ps]
795 796
796 797 def parents(repo, subset, x):
797 798 """``parents([set])``
798 799 The set of all parents for all changesets in set, or the working directory.
799 800 """
800 801 if x is None:
801 802 ps = tuple(p.rev() for p in repo[x].parents())
802 803 return [r for r in subset if r in ps]
803 804
804 805 ps = set()
805 806 cl = repo.changelog
806 807 for r in getset(repo, range(len(repo)), x):
807 808 ps.update(cl.parentrevs(r))
808 809 return [r for r in subset if r in ps]
809 810
810 811 def parentspec(repo, subset, x, n):
811 812 """``set^0``
812 813 The set.
813 814 ``set^1`` (or ``set^``), ``set^2``
814 815 First or second parent, respectively, of all changesets in set.
815 816 """
816 817 try:
817 818 n = int(n[1])
818 819 if n not in (0, 1, 2):
819 820 raise ValueError
820 821 except (TypeError, ValueError):
821 822 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
822 823 ps = set()
823 824 cl = repo.changelog
824 825 for r in getset(repo, subset, x):
825 826 if n == 0:
826 827 ps.add(r)
827 828 elif n == 1:
828 829 ps.add(cl.parentrevs(r)[0])
829 830 elif n == 2:
830 831 parents = cl.parentrevs(r)
831 832 if len(parents) > 1:
832 833 ps.add(parents[1])
833 834 return [r for r in subset if r in ps]
834 835
835 836 def present(repo, subset, x):
836 837 """``present(set)``
837 838 An empty set, if any revision in set isn't found; otherwise,
838 839 all revisions in set.
839 840 """
840 841 try:
841 842 return getset(repo, subset, x)
842 843 except error.RepoLookupError:
843 844 return []
844 845
845 846 def public(repo, subset, x):
846 847 """``public()``
847 848 Changeset in public phase."""
848 849 getargs(x, 0, 0, _("public takes no arguments"))
849 850 return [r for r in subset if repo._phaserev[r] == phases.public]
850 851
851 852 def remote(repo, subset, x):
852 853 """``remote([id [,path]])``
853 854 Local revision that corresponds to the given identifier in a
854 855 remote repository, if present. Here, the '.' identifier is a
855 856 synonym for the current local branch.
856 857 """
857 858
858 859 import hg # avoid start-up nasties
859 860 # i18n: "remote" is a keyword
860 861 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
861 862
862 863 q = '.'
863 864 if len(l) > 0:
864 865 # i18n: "remote" is a keyword
865 866 q = getstring(l[0], _("remote requires a string id"))
866 867 if q == '.':
867 868 q = repo['.'].branch()
868 869
869 870 dest = ''
870 871 if len(l) > 1:
871 872 # i18n: "remote" is a keyword
872 873 dest = getstring(l[1], _("remote requires a repository path"))
873 874 dest = repo.ui.expandpath(dest or 'default')
874 875 dest, branches = hg.parseurl(dest)
875 876 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
876 877 if revs:
877 878 revs = [repo.lookup(rev) for rev in revs]
878 879 other = hg.peer(repo, {}, dest)
879 880 n = other.lookup(q)
880 881 if n in repo:
881 882 r = repo[n].rev()
882 883 if r in subset:
883 884 return [r]
884 885 return []
885 886
886 887 def removes(repo, subset, x):
887 888 """``removes(pattern)``
888 889 Changesets which remove files matching pattern.
889 890 """
890 891 # i18n: "removes" is a keyword
891 892 pat = getstring(x, _("removes requires a pattern"))
892 893 return checkstatus(repo, subset, pat, 2)
893 894
894 895 def rev(repo, subset, x):
895 896 """``rev(number)``
896 897 Revision with the given numeric identifier.
897 898 """
898 899 # i18n: "rev" is a keyword
899 900 l = getargs(x, 1, 1, _("rev requires one argument"))
900 901 try:
901 902 # i18n: "rev" is a keyword
902 903 l = int(getstring(l[0], _("rev requires a number")))
903 904 except (TypeError, ValueError):
904 905 # i18n: "rev" is a keyword
905 906 raise error.ParseError(_("rev expects a number"))
906 907 return [r for r in subset if r == l]
907 908
908 909 def matching(repo, subset, x):
909 910 """``matching(revision [, field])``
910 911 Changesets in which a given set of fields match the set of fields in the
911 912 selected revision or set.
912 913 To match more than one field pass the list of fields to match separated
913 914 by spaces (e.g. 'author description').
914 915 Valid fields are most regular revision fields and some special fields:
915 916 * regular fields:
916 917 - description, author, branch, date, files, phase, parents,
917 918 substate, user.
918 919 Note that author and user are synonyms.
919 920 * special fields: summary, metadata.
920 921 - summary: matches the first line of the description.
921 922 - metatadata: It is equivalent to matching 'description user date'
922 923 (i.e. it matches the main metadata fields).
923 924 metadata is the default field which is used when no fields are specified.
924 925 You can match more than one field at a time.
925 926 """
926 927 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
927 928
928 929 revs = getset(repo, xrange(len(repo)), l[0])
929 930
930 931 fieldlist = ['metadata']
931 932 if len(l) > 1:
932 933 fieldlist = getstring(l[1],
933 934 _("matching requires a string "
934 935 "as its second argument")).split()
935 936
936 937 # Make sure that there are no repeated fields, and expand the
937 938 # 'special' 'metadata' field type
938 939 fields = []
939 940 for field in fieldlist:
940 941 if field == 'metadata':
941 942 fields += ['user', 'description', 'date']
942 943 else:
943 944 if field == 'author':
944 945 field = 'user'
945 946 fields.append(field)
946 947 fields = set(fields)
947 948
948 949 # We may want to match more than one field
949 950 # Each field will be matched with its own "getfield" function
950 951 # which will be added to the getfieldfuncs array of functions
951 952 getfieldfuncs = []
952 953 _funcs = {
953 954 'user': lambda r: repo[r].user(),
954 955 'branch': lambda r: repo[r].branch(),
955 956 'date': lambda r: repo[r].date(),
956 957 'description': lambda r: repo[r].description(),
957 958 'files': lambda r: repo[r].files(),
958 959 'parents': lambda r: repo[r].parents(),
959 960 'phase': lambda r: repo[r].phase(),
960 961 'substate': lambda r: repo[r].substate,
961 962 'summary': lambda r: repo[r].description().splitlines()[0],
962 963 }
963 964 for info in fields:
964 965 getfield = _funcs.get(info, None)
965 966 if getfield is None:
966 967 raise error.ParseError(
967 968 _("unexpected field name passed to matching: %s") % info)
968 969 getfieldfuncs.append(getfield)
969 970
970 971 # convert the getfield array of functions into a "getinfo" function
971 972 # which returns an array of field values (or a single value if there
972 973 # is only one field to match)
973 974 if len(getfieldfuncs) == 1:
974 975 getinfo = getfieldfuncs[0]
975 976 else:
976 977 getinfo = lambda r: [f(r) for f in getfieldfuncs]
977 978
978 979 matches = []
979 980 for rev in revs:
980 981 target = getinfo(rev)
981 982 matches += [r for r in subset if getinfo(r) == target]
982 983 if len(revs) > 1:
983 984 matches = sorted(set(matches))
984 985 return matches
985 986
986 987 def reverse(repo, subset, x):
987 988 """``reverse(set)``
988 989 Reverse order of set.
989 990 """
990 991 l = getset(repo, subset, x)
991 992 l.reverse()
992 993 return l
993 994
994 995 def roots(repo, subset, x):
995 996 """``roots(set)``
996 997 Changesets in set with no parent changeset in set.
997 998 """
998 999 s = set(getset(repo, xrange(len(repo)), x))
999 1000 subset = [r for r in subset if r in s]
1000 1001 cs = _children(repo, subset, s)
1001 1002 return [r for r in subset if r not in cs]
1002 1003
1003 1004 def secret(repo, subset, x):
1004 1005 """``secret()``
1005 1006 Changeset in secret phase."""
1006 1007 getargs(x, 0, 0, _("secret takes no arguments"))
1007 1008 return [r for r in subset if repo._phaserev[r] == phases.secret]
1008 1009
1009 1010 def sort(repo, subset, x):
1010 1011 """``sort(set[, [-]key...])``
1011 1012 Sort set by keys. The default sort order is ascending, specify a key
1012 1013 as ``-key`` to sort in descending order.
1013 1014
1014 1015 The keys can be:
1015 1016
1016 1017 - ``rev`` for the revision number,
1017 1018 - ``branch`` for the branch name,
1018 1019 - ``desc`` for the commit message (description),
1019 1020 - ``user`` for user name (``author`` can be used as an alias),
1020 1021 - ``date`` for the commit date
1021 1022 """
1022 1023 # i18n: "sort" is a keyword
1023 1024 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1024 1025 keys = "rev"
1025 1026 if len(l) == 2:
1026 1027 keys = getstring(l[1], _("sort spec must be a string"))
1027 1028
1028 1029 s = l[0]
1029 1030 keys = keys.split()
1030 1031 l = []
1031 1032 def invert(s):
1032 1033 return "".join(chr(255 - ord(c)) for c in s)
1033 1034 for r in getset(repo, subset, s):
1034 1035 c = repo[r]
1035 1036 e = []
1036 1037 for k in keys:
1037 1038 if k == 'rev':
1038 1039 e.append(r)
1039 1040 elif k == '-rev':
1040 1041 e.append(-r)
1041 1042 elif k == 'branch':
1042 1043 e.append(c.branch())
1043 1044 elif k == '-branch':
1044 1045 e.append(invert(c.branch()))
1045 1046 elif k == 'desc':
1046 1047 e.append(c.description())
1047 1048 elif k == '-desc':
1048 1049 e.append(invert(c.description()))
1049 1050 elif k in 'user author':
1050 1051 e.append(c.user())
1051 1052 elif k in '-user -author':
1052 1053 e.append(invert(c.user()))
1053 1054 elif k == 'date':
1054 1055 e.append(c.date()[0])
1055 1056 elif k == '-date':
1056 1057 e.append(-c.date()[0])
1057 1058 else:
1058 1059 raise error.ParseError(_("unknown sort key %r") % k)
1059 1060 e.append(r)
1060 1061 l.append(e)
1061 1062 l.sort()
1062 1063 return [e[-1] for e in l]
1063 1064
1064 1065 def tag(repo, subset, x):
1065 1066 """``tag([name])``
1066 1067 The specified tag by name, or all tagged revisions if no name is given.
1067 1068 """
1068 1069 # i18n: "tag" is a keyword
1069 1070 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1070 1071 cl = repo.changelog
1071 1072 if args:
1072 1073 tn = getstring(args[0],
1073 1074 # i18n: "tag" is a keyword
1074 1075 _('the argument to tag must be a string'))
1075 1076 if not repo.tags().get(tn, None):
1076 1077 raise util.Abort(_("tag '%s' does not exist") % tn)
1077 1078 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
1078 1079 else:
1079 1080 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1080 1081 return [r for r in subset if r in s]
1081 1082
1082 1083 def tagged(repo, subset, x):
1083 1084 return tag(repo, subset, x)
1084 1085
1085 1086 def user(repo, subset, x):
1086 1087 """``user(string)``
1087 1088 User name contains string. The match is case-insensitive.
1088 1089 """
1089 1090 return author(repo, subset, x)
1090 1091
1091 1092 # for internal use
1092 1093 def _list(repo, subset, x):
1093 1094 s = getstring(x, "internal error")
1094 1095 if not s:
1095 1096 return []
1096 1097 if not isinstance(subset, set):
1097 1098 subset = set(subset)
1098 1099 ls = [repo[r].rev() for r in s.split('\0')]
1099 1100 return [r for r in ls if r in subset]
1100 1101
1101 1102 symbols = {
1102 1103 "adds": adds,
1103 1104 "all": getall,
1104 1105 "ancestor": ancestor,
1105 1106 "ancestors": ancestors,
1106 1107 "_firstancestors": _firstancestors,
1107 1108 "author": author,
1108 1109 "bisect": bisect,
1109 1110 "bisected": bisected,
1110 1111 "bookmark": bookmark,
1111 1112 "branch": branch,
1112 1113 "children": children,
1113 1114 "closed": closed,
1114 1115 "contains": contains,
1115 1116 "date": date,
1116 1117 "desc": desc,
1117 1118 "descendants": descendants,
1118 1119 "_firstdescendants": _firstdescendants,
1119 1120 "draft": draft,
1120 1121 "file": hasfile,
1121 1122 "filelog": filelog,
1122 1123 "first": first,
1123 1124 "follow": follow,
1124 1125 "_followfirst": _followfirst,
1125 1126 "grep": grep,
1126 1127 "head": head,
1127 1128 "heads": heads,
1128 "id": node,
1129 "id": node_,
1129 1130 "keyword": keyword,
1130 1131 "last": last,
1131 1132 "limit": limit,
1132 1133 "_matchfiles": _matchfiles,
1133 1134 "max": maxrev,
1134 1135 "merge": merge,
1135 1136 "min": minrev,
1136 1137 "modifies": modifies,
1137 1138 "outgoing": outgoing,
1138 1139 "p1": p1,
1139 1140 "p2": p2,
1140 1141 "parents": parents,
1141 1142 "present": present,
1142 1143 "public": public,
1143 1144 "remote": remote,
1144 1145 "removes": removes,
1145 1146 "rev": rev,
1146 1147 "reverse": reverse,
1147 1148 "roots": roots,
1148 1149 "sort": sort,
1149 1150 "secret": secret,
1150 1151 "matching": matching,
1151 1152 "tag": tag,
1152 1153 "tagged": tagged,
1153 1154 "user": user,
1154 1155 "_list": _list,
1155 1156 }
1156 1157
1157 1158 methods = {
1158 1159 "range": rangeset,
1159 1160 "string": stringset,
1160 1161 "symbol": symbolset,
1161 1162 "and": andset,
1162 1163 "or": orset,
1163 1164 "not": notset,
1164 1165 "list": listset,
1165 1166 "func": func,
1166 1167 "ancestor": ancestorspec,
1167 1168 "parent": parentspec,
1168 1169 "parentpost": p1,
1169 1170 }
1170 1171
1171 1172 def optimize(x, small):
1172 1173 if x is None:
1173 1174 return 0, x
1174 1175
1175 1176 smallbonus = 1
1176 1177 if small:
1177 1178 smallbonus = .5
1178 1179
1179 1180 op = x[0]
1180 1181 if op == 'minus':
1181 1182 return optimize(('and', x[1], ('not', x[2])), small)
1182 1183 elif op == 'dagrange':
1183 1184 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1184 1185 ('func', ('symbol', 'ancestors'), x[2])), small)
1185 1186 elif op == 'dagrangepre':
1186 1187 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1187 1188 elif op == 'dagrangepost':
1188 1189 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1189 1190 elif op == 'rangepre':
1190 1191 return optimize(('range', ('string', '0'), x[1]), small)
1191 1192 elif op == 'rangepost':
1192 1193 return optimize(('range', x[1], ('string', 'tip')), small)
1193 1194 elif op == 'negate':
1194 1195 return optimize(('string',
1195 1196 '-' + getstring(x[1], _("can't negate that"))), small)
1196 1197 elif op in 'string symbol negate':
1197 1198 return smallbonus, x # single revisions are small
1198 1199 elif op == 'and' or op == 'dagrange':
1199 1200 wa, ta = optimize(x[1], True)
1200 1201 wb, tb = optimize(x[2], True)
1201 1202 w = min(wa, wb)
1202 1203 if wa > wb:
1203 1204 return w, (op, tb, ta)
1204 1205 return w, (op, ta, tb)
1205 1206 elif op == 'or':
1206 1207 wa, ta = optimize(x[1], False)
1207 1208 wb, tb = optimize(x[2], False)
1208 1209 if wb < wa:
1209 1210 wb, wa = wa, wb
1210 1211 return max(wa, wb), (op, ta, tb)
1211 1212 elif op == 'not':
1212 1213 o = optimize(x[1], not small)
1213 1214 return o[0], (op, o[1])
1214 1215 elif op == 'parentpost':
1215 1216 o = optimize(x[1], small)
1216 1217 return o[0], (op, o[1])
1217 1218 elif op == 'group':
1218 1219 return optimize(x[1], small)
1219 1220 elif op in 'range list parent ancestorspec':
1220 1221 if op == 'parent':
1221 1222 # x^:y means (x^) : y, not x ^ (:y)
1222 1223 post = ('parentpost', x[1])
1223 1224 if x[2][0] == 'dagrangepre':
1224 1225 return optimize(('dagrange', post, x[2][1]), small)
1225 1226 elif x[2][0] == 'rangepre':
1226 1227 return optimize(('range', post, x[2][1]), small)
1227 1228
1228 1229 wa, ta = optimize(x[1], small)
1229 1230 wb, tb = optimize(x[2], small)
1230 1231 return wa + wb, (op, ta, tb)
1231 1232 elif op == 'func':
1232 1233 f = getstring(x[1], _("not a symbol"))
1233 1234 wa, ta = optimize(x[2], small)
1234 1235 if f in ("author branch closed date desc file grep keyword "
1235 1236 "outgoing user"):
1236 1237 w = 10 # slow
1237 1238 elif f in "modifies adds removes":
1238 1239 w = 30 # slower
1239 1240 elif f == "contains":
1240 1241 w = 100 # very slow
1241 1242 elif f == "ancestor":
1242 1243 w = 1 * smallbonus
1243 1244 elif f in "reverse limit first":
1244 1245 w = 0
1245 1246 elif f in "sort":
1246 1247 w = 10 # assume most sorts look at changelog
1247 1248 else:
1248 1249 w = 1
1249 1250 return w + wa, (op, x[1], ta)
1250 1251 return 1, x
1251 1252
1252 1253 class revsetalias(object):
1253 1254 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1254 1255 args = None
1255 1256
1256 1257 def __init__(self, name, value):
1257 1258 '''Aliases like:
1258 1259
1259 1260 h = heads(default)
1260 1261 b($1) = ancestors($1) - ancestors(default)
1261 1262 '''
1262 1263 m = self.funcre.search(name)
1263 1264 if m:
1264 1265 self.name = m.group(1)
1265 1266 self.tree = ('func', ('symbol', m.group(1)))
1266 1267 self.args = [x.strip() for x in m.group(2).split(',')]
1267 1268 for arg in self.args:
1268 1269 value = value.replace(arg, repr(arg))
1269 1270 else:
1270 1271 self.name = name
1271 1272 self.tree = ('symbol', name)
1272 1273
1273 1274 self.replacement, pos = parse(value)
1274 1275 if pos != len(value):
1275 1276 raise error.ParseError(_('invalid token'), pos)
1276 1277
1277 1278 def _getalias(aliases, tree):
1278 1279 """If tree looks like an unexpanded alias, return it. Return None
1279 1280 otherwise.
1280 1281 """
1281 1282 if isinstance(tree, tuple) and tree:
1282 1283 if tree[0] == 'symbol' and len(tree) == 2:
1283 1284 name = tree[1]
1284 1285 alias = aliases.get(name)
1285 1286 if alias and alias.args is None and alias.tree == tree:
1286 1287 return alias
1287 1288 if tree[0] == 'func' and len(tree) > 1:
1288 1289 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1289 1290 name = tree[1][1]
1290 1291 alias = aliases.get(name)
1291 1292 if alias and alias.args is not None and alias.tree == tree[:2]:
1292 1293 return alias
1293 1294 return None
1294 1295
1295 1296 def _expandargs(tree, args):
1296 1297 """Replace all occurences of ('string', name) with the
1297 1298 substitution value of the same name in args, recursively.
1298 1299 """
1299 1300 if not isinstance(tree, tuple):
1300 1301 return tree
1301 1302 if len(tree) == 2 and tree[0] == 'string':
1302 1303 return args.get(tree[1], tree)
1303 1304 return tuple(_expandargs(t, args) for t in tree)
1304 1305
1305 1306 def _expandaliases(aliases, tree, expanding):
1306 1307 """Expand aliases in tree, recursively.
1307 1308
1308 1309 'aliases' is a dictionary mapping user defined aliases to
1309 1310 revsetalias objects.
1310 1311 """
1311 1312 if not isinstance(tree, tuple):
1312 1313 # Do not expand raw strings
1313 1314 return tree
1314 1315 alias = _getalias(aliases, tree)
1315 1316 if alias is not None:
1316 1317 if alias in expanding:
1317 1318 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1318 1319 'detected') % alias.name)
1319 1320 expanding.append(alias)
1320 1321 result = alias.replacement
1321 1322 if alias.args is not None:
1322 1323 l = getlist(tree[2])
1323 1324 if len(l) != len(alias.args):
1324 1325 raise error.ParseError(
1325 1326 _('invalid number of arguments: %s') % len(l))
1326 1327 result = _expandargs(result, dict(zip(alias.args, l)))
1327 1328 # Recurse in place, the base expression may have been rewritten
1328 1329 result = _expandaliases(aliases, result, expanding)
1329 1330 expanding.pop()
1330 1331 else:
1331 1332 result = tuple(_expandaliases(aliases, t, expanding)
1332 1333 for t in tree)
1333 1334 return result
1334 1335
1335 1336 def findaliases(ui, tree):
1336 1337 aliases = {}
1337 1338 for k, v in ui.configitems('revsetalias'):
1338 1339 alias = revsetalias(k, v)
1339 1340 aliases[alias.name] = alias
1340 1341 return _expandaliases(aliases, tree, [])
1341 1342
1342 1343 parse = parser.parser(tokenize, elements).parse
1343 1344
1344 1345 def match(ui, spec):
1345 1346 if not spec:
1346 1347 raise error.ParseError(_("empty query"))
1347 1348 tree, pos = parse(spec)
1348 1349 if (pos != len(spec)):
1349 1350 raise error.ParseError(_("invalid token"), pos)
1350 1351 if ui:
1351 1352 tree = findaliases(ui, tree)
1352 1353 weight, tree = optimize(tree, True)
1353 1354 def mfunc(repo, subset):
1354 1355 return getset(repo, subset, tree)
1355 1356 return mfunc
1356 1357
1357 1358 def formatspec(expr, *args):
1358 1359 '''
1359 1360 This is a convenience function for using revsets internally, and
1360 1361 escapes arguments appropriately. Aliases are intentionally ignored
1361 1362 so that intended expression behavior isn't accidentally subverted.
1362 1363
1363 1364 Supported arguments:
1364 1365
1365 1366 %r = revset expression, parenthesized
1366 1367 %d = int(arg), no quoting
1367 1368 %s = string(arg), escaped and single-quoted
1368 1369 %b = arg.branch(), escaped and single-quoted
1369 1370 %n = hex(arg), single-quoted
1370 1371 %% = a literal '%'
1371 1372
1372 1373 Prefixing the type with 'l' specifies a parenthesized list of that type.
1373 1374
1374 1375 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1375 1376 '(10 or 11):: and ((this()) or (that()))'
1376 1377 >>> formatspec('%d:: and not %d::', 10, 20)
1377 1378 '10:: and not 20::'
1378 1379 >>> formatspec('%ld or %ld', [], [1])
1379 1380 "_list('') or 1"
1380 1381 >>> formatspec('keyword(%s)', 'foo\\xe9')
1381 1382 "keyword('foo\\\\xe9')"
1382 1383 >>> b = lambda: 'default'
1383 1384 >>> b.branch = b
1384 1385 >>> formatspec('branch(%b)', b)
1385 1386 "branch('default')"
1386 1387 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1387 1388 "root(_list('a\\x00b\\x00c\\x00d'))"
1388 1389 '''
1389 1390
1390 1391 def quote(s):
1391 1392 return repr(str(s))
1392 1393
1393 1394 def argtype(c, arg):
1394 1395 if c == 'd':
1395 1396 return str(int(arg))
1396 1397 elif c == 's':
1397 1398 return quote(arg)
1398 1399 elif c == 'r':
1399 1400 parse(arg) # make sure syntax errors are confined
1400 1401 return '(%s)' % arg
1401 1402 elif c == 'n':
1402 return quote(nodemod.hex(arg))
1403 return quote(node.hex(arg))
1403 1404 elif c == 'b':
1404 1405 return quote(arg.branch())
1405 1406
1406 1407 def listexp(s, t):
1407 1408 l = len(s)
1408 1409 if l == 0:
1409 1410 return "_list('')"
1410 1411 elif l == 1:
1411 1412 return argtype(t, s[0])
1412 1413 elif t == 'd':
1413 1414 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1414 1415 elif t == 's':
1415 1416 return "_list('%s')" % "\0".join(s)
1416 1417 elif t == 'n':
1417 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1418 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1418 1419 elif t == 'b':
1419 1420 return "_list('%s')" % "\0".join(a.branch() for a in s)
1420 1421
1421 1422 m = l // 2
1422 1423 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1423 1424
1424 1425 ret = ''
1425 1426 pos = 0
1426 1427 arg = 0
1427 1428 while pos < len(expr):
1428 1429 c = expr[pos]
1429 1430 if c == '%':
1430 1431 pos += 1
1431 1432 d = expr[pos]
1432 1433 if d == '%':
1433 1434 ret += d
1434 1435 elif d in 'dsnbr':
1435 1436 ret += argtype(d, args[arg])
1436 1437 arg += 1
1437 1438 elif d == 'l':
1438 1439 # a list of some type
1439 1440 pos += 1
1440 1441 d = expr[pos]
1441 1442 ret += listexp(list(args[arg]), d)
1442 1443 arg += 1
1443 1444 else:
1444 1445 raise util.Abort('unexpected revspec format character %s' % d)
1445 1446 else:
1446 1447 ret += c
1447 1448 pos += 1
1448 1449
1449 1450 return ret
1450 1451
1451 1452 def prettyformat(tree):
1452 1453 def _prettyformat(tree, level, lines):
1453 1454 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1454 1455 lines.append((level, str(tree)))
1455 1456 else:
1456 1457 lines.append((level, '(%s' % tree[0]))
1457 1458 for s in tree[1:]:
1458 1459 _prettyformat(s, level + 1, lines)
1459 1460 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1460 1461
1461 1462 lines = []
1462 1463 _prettyformat(tree, 0, lines)
1463 1464 output = '\n'.join((' '*l + s) for l, s in lines)
1464 1465 return output
1465 1466
1466 1467 # tell hggettext to extract docstrings from these functions:
1467 1468 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now