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