##// END OF EJS Templates
doc: flatten description of 'matching()' predicate to be formatted well...
FUJIWARA Katsunori -
r16528:5d803620 stable
parent child Browse files
Show More
@@ -1,1491 +1,1495 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node
11 11 import bookmarks as bookmarksmod
12 12 import match as matchmod
13 13 from i18n import _
14 14 import encoding
15 15
16 16 def _revancestors(repo, revs, followfirst):
17 17 """Like revlog.ancestors(), but supports followfirst."""
18 18 cut = followfirst and 1 or None
19 19 cl = repo.changelog
20 20 visit = 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 743 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
744 744 return [r for r in subset if r == rn]
745 745
746 746 def outgoing(repo, subset, x):
747 747 """``outgoing([path])``
748 748 Changesets not found in the specified destination repository, or the
749 749 default push location.
750 750 """
751 751 import hg # avoid start-up nasties
752 752 # i18n: "outgoing" is a keyword
753 753 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
754 754 # i18n: "outgoing" is a keyword
755 755 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
756 756 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
757 757 dest, branches = hg.parseurl(dest)
758 758 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
759 759 if revs:
760 760 revs = [repo.lookup(rev) for rev in revs]
761 761 other = hg.peer(repo, {}, dest)
762 762 repo.ui.pushbuffer()
763 763 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
764 764 repo.ui.popbuffer()
765 765 cl = repo.changelog
766 766 o = set([cl.rev(r) for r in outgoing.missing])
767 767 return [r for r in subset if r in o]
768 768
769 769 def p1(repo, subset, x):
770 770 """``p1([set])``
771 771 First parent of changesets in set, or the working directory.
772 772 """
773 773 if x is None:
774 774 p = repo[x].p1().rev()
775 775 return [r for r in subset if r == p]
776 776
777 777 ps = set()
778 778 cl = repo.changelog
779 779 for r in getset(repo, range(len(repo)), x):
780 780 ps.add(cl.parentrevs(r)[0])
781 781 return [r for r in subset if r in ps]
782 782
783 783 def p2(repo, subset, x):
784 784 """``p2([set])``
785 785 Second parent of changesets in set, or the working directory.
786 786 """
787 787 if x is None:
788 788 ps = repo[x].parents()
789 789 try:
790 790 p = ps[1].rev()
791 791 return [r for r in subset if r == p]
792 792 except IndexError:
793 793 return []
794 794
795 795 ps = set()
796 796 cl = repo.changelog
797 797 for r in getset(repo, range(len(repo)), x):
798 798 ps.add(cl.parentrevs(r)[1])
799 799 return [r for r in subset if r in ps]
800 800
801 801 def parents(repo, subset, x):
802 802 """``parents([set])``
803 803 The set of all parents for all changesets in set, or the working directory.
804 804 """
805 805 if x is None:
806 806 ps = tuple(p.rev() for p in repo[x].parents())
807 807 return [r for r in subset if r in ps]
808 808
809 809 ps = set()
810 810 cl = repo.changelog
811 811 for r in getset(repo, range(len(repo)), x):
812 812 ps.update(cl.parentrevs(r))
813 813 return [r for r in subset if r in ps]
814 814
815 815 def parentspec(repo, subset, x, n):
816 816 """``set^0``
817 817 The set.
818 818 ``set^1`` (or ``set^``), ``set^2``
819 819 First or second parent, respectively, of all changesets in set.
820 820 """
821 821 try:
822 822 n = int(n[1])
823 823 if n not in (0, 1, 2):
824 824 raise ValueError
825 825 except (TypeError, ValueError):
826 826 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
827 827 ps = set()
828 828 cl = repo.changelog
829 829 for r in getset(repo, subset, x):
830 830 if n == 0:
831 831 ps.add(r)
832 832 elif n == 1:
833 833 ps.add(cl.parentrevs(r)[0])
834 834 elif n == 2:
835 835 parents = cl.parentrevs(r)
836 836 if len(parents) > 1:
837 837 ps.add(parents[1])
838 838 return [r for r in subset if r in ps]
839 839
840 840 def present(repo, subset, x):
841 841 """``present(set)``
842 842 An empty set, if any revision in set isn't found; otherwise,
843 843 all revisions in set.
844 844 """
845 845 try:
846 846 return getset(repo, subset, x)
847 847 except error.RepoLookupError:
848 848 return []
849 849
850 850 def public(repo, subset, x):
851 851 """``public()``
852 852 Changeset in public phase."""
853 853 getargs(x, 0, 0, _("public takes no arguments"))
854 854 return [r for r in subset if repo._phaserev[r] == phases.public]
855 855
856 856 def remote(repo, subset, x):
857 857 """``remote([id [,path]])``
858 858 Local revision that corresponds to the given identifier in a
859 859 remote repository, if present. Here, the '.' identifier is a
860 860 synonym for the current local branch.
861 861 """
862 862
863 863 import hg # avoid start-up nasties
864 864 # i18n: "remote" is a keyword
865 865 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
866 866
867 867 q = '.'
868 868 if len(l) > 0:
869 869 # i18n: "remote" is a keyword
870 870 q = getstring(l[0], _("remote requires a string id"))
871 871 if q == '.':
872 872 q = repo['.'].branch()
873 873
874 874 dest = ''
875 875 if len(l) > 1:
876 876 # i18n: "remote" is a keyword
877 877 dest = getstring(l[1], _("remote requires a repository path"))
878 878 dest = repo.ui.expandpath(dest or 'default')
879 879 dest, branches = hg.parseurl(dest)
880 880 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
881 881 if revs:
882 882 revs = [repo.lookup(rev) for rev in revs]
883 883 other = hg.peer(repo, {}, dest)
884 884 n = other.lookup(q)
885 885 if n in repo:
886 886 r = repo[n].rev()
887 887 if r in subset:
888 888 return [r]
889 889 return []
890 890
891 891 def removes(repo, subset, x):
892 892 """``removes(pattern)``
893 893 Changesets which remove files matching pattern.
894 894 """
895 895 # i18n: "removes" is a keyword
896 896 pat = getstring(x, _("removes requires a pattern"))
897 897 return checkstatus(repo, subset, pat, 2)
898 898
899 899 def rev(repo, subset, x):
900 900 """``rev(number)``
901 901 Revision with the given numeric identifier.
902 902 """
903 903 # i18n: "rev" is a keyword
904 904 l = getargs(x, 1, 1, _("rev requires one argument"))
905 905 try:
906 906 # i18n: "rev" is a keyword
907 907 l = int(getstring(l[0], _("rev requires a number")))
908 908 except (TypeError, ValueError):
909 909 # i18n: "rev" is a keyword
910 910 raise error.ParseError(_("rev expects a number"))
911 911 return [r for r in subset if r == l]
912 912
913 913 def matching(repo, subset, x):
914 914 """``matching(revision [, field])``
915 915 Changesets in which a given set of fields match the set of fields in the
916 916 selected revision or set.
917
917 918 To match more than one field pass the list of fields to match separated
918 by spaces (e.g. 'author description').
919 Valid fields are most regular revision fields and some special fields:
920 * regular fields:
921 - description, author, branch, date, files, phase, parents,
922 substate, user.
923 Note that author and user are synonyms.
924 * special fields: summary, metadata.
925 - summary: matches the first line of the description.
926 - metatadata: It is equivalent to matching 'description user date'
927 (i.e. it matches the main metadata fields).
928 metadata is the default field which is used when no fields are specified.
929 You can match more than one field at a time.
919 by spaces (e.g. ``author description``).
920
921 Valid fields are most regular revision fields and some special fields.
922
923 Regular revision fields are ``description``, ``author``, ``branch``,
924 ``date``, ``files``, ``phase``, ``parents``, ``substate`` and ``user``.
925 Note that ``author`` and ``user`` are synonyms.
926
927 Special fields are ``summary`` and ``metadata``:
928 ``summary`` matches the first line of the description.
929 ``metatadata`` is equivalent to matching ``description user date``
930 (i.e. it matches the main metadata fields).
931
932 ``metadata`` is the default field which is used when no fields are
933 specified. You can match more than one field at a time.
930 934 """
931 935 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
932 936
933 937 revs = getset(repo, xrange(len(repo)), l[0])
934 938
935 939 fieldlist = ['metadata']
936 940 if len(l) > 1:
937 941 fieldlist = getstring(l[1],
938 942 _("matching requires a string "
939 943 "as its second argument")).split()
940 944
941 945 # Make sure that there are no repeated fields, and expand the
942 946 # 'special' 'metadata' field type
943 947 fields = []
944 948 for field in fieldlist:
945 949 if field == 'metadata':
946 950 fields += ['user', 'description', 'date']
947 951 else:
948 952 if field == 'author':
949 953 field = 'user'
950 954 fields.append(field)
951 955 fields = set(fields)
952 956 if 'summary' in fields and 'description' in fields:
953 957 # If a revision matches its description it also matches its summary
954 958 fields.discard('summary')
955 959
956 960 # We may want to match more than one field
957 961 # Not all fields take the same amount of time to be matched
958 962 # Sort the selected fields in order of increasing matching cost
959 963 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
960 964 'files', 'description', 'substate']
961 965 def fieldkeyfunc(f):
962 966 try:
963 967 return fieldorder.index(f)
964 968 except ValueError:
965 969 # assume an unknown field is very costly
966 970 return len(fieldorder)
967 971 fields = list(fields)
968 972 fields.sort(key=fieldkeyfunc)
969 973
970 974 # Each field will be matched with its own "getfield" function
971 975 # which will be added to the getfieldfuncs array of functions
972 976 getfieldfuncs = []
973 977 _funcs = {
974 978 'user': lambda r: repo[r].user(),
975 979 'branch': lambda r: repo[r].branch(),
976 980 'date': lambda r: repo[r].date(),
977 981 'description': lambda r: repo[r].description(),
978 982 'files': lambda r: repo[r].files(),
979 983 'parents': lambda r: repo[r].parents(),
980 984 'phase': lambda r: repo[r].phase(),
981 985 'substate': lambda r: repo[r].substate,
982 986 'summary': lambda r: repo[r].description().splitlines()[0],
983 987 }
984 988 for info in fields:
985 989 getfield = _funcs.get(info, None)
986 990 if getfield is None:
987 991 raise error.ParseError(
988 992 _("unexpected field name passed to matching: %s") % info)
989 993 getfieldfuncs.append(getfield)
990 994 # convert the getfield array of functions into a "getinfo" function
991 995 # which returns an array of field values (or a single value if there
992 996 # is only one field to match)
993 997 getinfo = lambda r: [f(r) for f in getfieldfuncs]
994 998
995 999 matches = []
996 1000 for rev in revs:
997 1001 target = getinfo(rev)
998 1002 for r in subset:
999 1003 match = True
1000 1004 for n, f in enumerate(getfieldfuncs):
1001 1005 if target[n] != f(r):
1002 1006 match = False
1003 1007 break
1004 1008 if match:
1005 1009 matches.append(r)
1006 1010 if len(revs) > 1:
1007 1011 matches = sorted(set(matches))
1008 1012 return matches
1009 1013
1010 1014 def reverse(repo, subset, x):
1011 1015 """``reverse(set)``
1012 1016 Reverse order of set.
1013 1017 """
1014 1018 l = getset(repo, subset, x)
1015 1019 l.reverse()
1016 1020 return l
1017 1021
1018 1022 def roots(repo, subset, x):
1019 1023 """``roots(set)``
1020 1024 Changesets in set with no parent changeset in set.
1021 1025 """
1022 1026 s = set(getset(repo, xrange(len(repo)), x))
1023 1027 subset = [r for r in subset if r in s]
1024 1028 cs = _children(repo, subset, s)
1025 1029 return [r for r in subset if r not in cs]
1026 1030
1027 1031 def secret(repo, subset, x):
1028 1032 """``secret()``
1029 1033 Changeset in secret phase."""
1030 1034 getargs(x, 0, 0, _("secret takes no arguments"))
1031 1035 return [r for r in subset if repo._phaserev[r] == phases.secret]
1032 1036
1033 1037 def sort(repo, subset, x):
1034 1038 """``sort(set[, [-]key...])``
1035 1039 Sort set by keys. The default sort order is ascending, specify a key
1036 1040 as ``-key`` to sort in descending order.
1037 1041
1038 1042 The keys can be:
1039 1043
1040 1044 - ``rev`` for the revision number,
1041 1045 - ``branch`` for the branch name,
1042 1046 - ``desc`` for the commit message (description),
1043 1047 - ``user`` for user name (``author`` can be used as an alias),
1044 1048 - ``date`` for the commit date
1045 1049 """
1046 1050 # i18n: "sort" is a keyword
1047 1051 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1048 1052 keys = "rev"
1049 1053 if len(l) == 2:
1050 1054 keys = getstring(l[1], _("sort spec must be a string"))
1051 1055
1052 1056 s = l[0]
1053 1057 keys = keys.split()
1054 1058 l = []
1055 1059 def invert(s):
1056 1060 return "".join(chr(255 - ord(c)) for c in s)
1057 1061 for r in getset(repo, subset, s):
1058 1062 c = repo[r]
1059 1063 e = []
1060 1064 for k in keys:
1061 1065 if k == 'rev':
1062 1066 e.append(r)
1063 1067 elif k == '-rev':
1064 1068 e.append(-r)
1065 1069 elif k == 'branch':
1066 1070 e.append(c.branch())
1067 1071 elif k == '-branch':
1068 1072 e.append(invert(c.branch()))
1069 1073 elif k == 'desc':
1070 1074 e.append(c.description())
1071 1075 elif k == '-desc':
1072 1076 e.append(invert(c.description()))
1073 1077 elif k in 'user author':
1074 1078 e.append(c.user())
1075 1079 elif k in '-user -author':
1076 1080 e.append(invert(c.user()))
1077 1081 elif k == 'date':
1078 1082 e.append(c.date()[0])
1079 1083 elif k == '-date':
1080 1084 e.append(-c.date()[0])
1081 1085 else:
1082 1086 raise error.ParseError(_("unknown sort key %r") % k)
1083 1087 e.append(r)
1084 1088 l.append(e)
1085 1089 l.sort()
1086 1090 return [e[-1] for e in l]
1087 1091
1088 1092 def tag(repo, subset, x):
1089 1093 """``tag([name])``
1090 1094 The specified tag by name, or all tagged revisions if no name is given.
1091 1095 """
1092 1096 # i18n: "tag" is a keyword
1093 1097 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1094 1098 cl = repo.changelog
1095 1099 if args:
1096 1100 tn = getstring(args[0],
1097 1101 # i18n: "tag" is a keyword
1098 1102 _('the argument to tag must be a string'))
1099 1103 if not repo.tags().get(tn, None):
1100 1104 raise util.Abort(_("tag '%s' does not exist") % tn)
1101 1105 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
1102 1106 else:
1103 1107 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1104 1108 return [r for r in subset if r in s]
1105 1109
1106 1110 def tagged(repo, subset, x):
1107 1111 return tag(repo, subset, x)
1108 1112
1109 1113 def user(repo, subset, x):
1110 1114 """``user(string)``
1111 1115 User name contains string. The match is case-insensitive.
1112 1116 """
1113 1117 return author(repo, subset, x)
1114 1118
1115 1119 # for internal use
1116 1120 def _list(repo, subset, x):
1117 1121 s = getstring(x, "internal error")
1118 1122 if not s:
1119 1123 return []
1120 1124 if not isinstance(subset, set):
1121 1125 subset = set(subset)
1122 1126 ls = [repo[r].rev() for r in s.split('\0')]
1123 1127 return [r for r in ls if r in subset]
1124 1128
1125 1129 symbols = {
1126 1130 "adds": adds,
1127 1131 "all": getall,
1128 1132 "ancestor": ancestor,
1129 1133 "ancestors": ancestors,
1130 1134 "_firstancestors": _firstancestors,
1131 1135 "author": author,
1132 1136 "bisect": bisect,
1133 1137 "bisected": bisected,
1134 1138 "bookmark": bookmark,
1135 1139 "branch": branch,
1136 1140 "children": children,
1137 1141 "closed": closed,
1138 1142 "contains": contains,
1139 1143 "date": date,
1140 1144 "desc": desc,
1141 1145 "descendants": descendants,
1142 1146 "_firstdescendants": _firstdescendants,
1143 1147 "draft": draft,
1144 1148 "file": hasfile,
1145 1149 "filelog": filelog,
1146 1150 "first": first,
1147 1151 "follow": follow,
1148 1152 "_followfirst": _followfirst,
1149 1153 "grep": grep,
1150 1154 "head": head,
1151 1155 "heads": heads,
1152 1156 "id": node_,
1153 1157 "keyword": keyword,
1154 1158 "last": last,
1155 1159 "limit": limit,
1156 1160 "_matchfiles": _matchfiles,
1157 1161 "max": maxrev,
1158 1162 "merge": merge,
1159 1163 "min": minrev,
1160 1164 "modifies": modifies,
1161 1165 "outgoing": outgoing,
1162 1166 "p1": p1,
1163 1167 "p2": p2,
1164 1168 "parents": parents,
1165 1169 "present": present,
1166 1170 "public": public,
1167 1171 "remote": remote,
1168 1172 "removes": removes,
1169 1173 "rev": rev,
1170 1174 "reverse": reverse,
1171 1175 "roots": roots,
1172 1176 "sort": sort,
1173 1177 "secret": secret,
1174 1178 "matching": matching,
1175 1179 "tag": tag,
1176 1180 "tagged": tagged,
1177 1181 "user": user,
1178 1182 "_list": _list,
1179 1183 }
1180 1184
1181 1185 methods = {
1182 1186 "range": rangeset,
1183 1187 "string": stringset,
1184 1188 "symbol": symbolset,
1185 1189 "and": andset,
1186 1190 "or": orset,
1187 1191 "not": notset,
1188 1192 "list": listset,
1189 1193 "func": func,
1190 1194 "ancestor": ancestorspec,
1191 1195 "parent": parentspec,
1192 1196 "parentpost": p1,
1193 1197 }
1194 1198
1195 1199 def optimize(x, small):
1196 1200 if x is None:
1197 1201 return 0, x
1198 1202
1199 1203 smallbonus = 1
1200 1204 if small:
1201 1205 smallbonus = .5
1202 1206
1203 1207 op = x[0]
1204 1208 if op == 'minus':
1205 1209 return optimize(('and', x[1], ('not', x[2])), small)
1206 1210 elif op == 'dagrange':
1207 1211 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1208 1212 ('func', ('symbol', 'ancestors'), x[2])), small)
1209 1213 elif op == 'dagrangepre':
1210 1214 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1211 1215 elif op == 'dagrangepost':
1212 1216 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1213 1217 elif op == 'rangepre':
1214 1218 return optimize(('range', ('string', '0'), x[1]), small)
1215 1219 elif op == 'rangepost':
1216 1220 return optimize(('range', x[1], ('string', 'tip')), small)
1217 1221 elif op == 'negate':
1218 1222 return optimize(('string',
1219 1223 '-' + getstring(x[1], _("can't negate that"))), small)
1220 1224 elif op in 'string symbol negate':
1221 1225 return smallbonus, x # single revisions are small
1222 1226 elif op == 'and' or op == 'dagrange':
1223 1227 wa, ta = optimize(x[1], True)
1224 1228 wb, tb = optimize(x[2], True)
1225 1229 w = min(wa, wb)
1226 1230 if wa > wb:
1227 1231 return w, (op, tb, ta)
1228 1232 return w, (op, ta, tb)
1229 1233 elif op == 'or':
1230 1234 wa, ta = optimize(x[1], False)
1231 1235 wb, tb = optimize(x[2], False)
1232 1236 if wb < wa:
1233 1237 wb, wa = wa, wb
1234 1238 return max(wa, wb), (op, ta, tb)
1235 1239 elif op == 'not':
1236 1240 o = optimize(x[1], not small)
1237 1241 return o[0], (op, o[1])
1238 1242 elif op == 'parentpost':
1239 1243 o = optimize(x[1], small)
1240 1244 return o[0], (op, o[1])
1241 1245 elif op == 'group':
1242 1246 return optimize(x[1], small)
1243 1247 elif op in 'range list parent ancestorspec':
1244 1248 if op == 'parent':
1245 1249 # x^:y means (x^) : y, not x ^ (:y)
1246 1250 post = ('parentpost', x[1])
1247 1251 if x[2][0] == 'dagrangepre':
1248 1252 return optimize(('dagrange', post, x[2][1]), small)
1249 1253 elif x[2][0] == 'rangepre':
1250 1254 return optimize(('range', post, x[2][1]), small)
1251 1255
1252 1256 wa, ta = optimize(x[1], small)
1253 1257 wb, tb = optimize(x[2], small)
1254 1258 return wa + wb, (op, ta, tb)
1255 1259 elif op == 'func':
1256 1260 f = getstring(x[1], _("not a symbol"))
1257 1261 wa, ta = optimize(x[2], small)
1258 1262 if f in ("author branch closed date desc file grep keyword "
1259 1263 "outgoing user"):
1260 1264 w = 10 # slow
1261 1265 elif f in "modifies adds removes":
1262 1266 w = 30 # slower
1263 1267 elif f == "contains":
1264 1268 w = 100 # very slow
1265 1269 elif f == "ancestor":
1266 1270 w = 1 * smallbonus
1267 1271 elif f in "reverse limit first":
1268 1272 w = 0
1269 1273 elif f in "sort":
1270 1274 w = 10 # assume most sorts look at changelog
1271 1275 else:
1272 1276 w = 1
1273 1277 return w + wa, (op, x[1], ta)
1274 1278 return 1, x
1275 1279
1276 1280 class revsetalias(object):
1277 1281 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1278 1282 args = None
1279 1283
1280 1284 def __init__(self, name, value):
1281 1285 '''Aliases like:
1282 1286
1283 1287 h = heads(default)
1284 1288 b($1) = ancestors($1) - ancestors(default)
1285 1289 '''
1286 1290 m = self.funcre.search(name)
1287 1291 if m:
1288 1292 self.name = m.group(1)
1289 1293 self.tree = ('func', ('symbol', m.group(1)))
1290 1294 self.args = [x.strip() for x in m.group(2).split(',')]
1291 1295 for arg in self.args:
1292 1296 value = value.replace(arg, repr(arg))
1293 1297 else:
1294 1298 self.name = name
1295 1299 self.tree = ('symbol', name)
1296 1300
1297 1301 self.replacement, pos = parse(value)
1298 1302 if pos != len(value):
1299 1303 raise error.ParseError(_('invalid token'), pos)
1300 1304
1301 1305 def _getalias(aliases, tree):
1302 1306 """If tree looks like an unexpanded alias, return it. Return None
1303 1307 otherwise.
1304 1308 """
1305 1309 if isinstance(tree, tuple) and tree:
1306 1310 if tree[0] == 'symbol' and len(tree) == 2:
1307 1311 name = tree[1]
1308 1312 alias = aliases.get(name)
1309 1313 if alias and alias.args is None and alias.tree == tree:
1310 1314 return alias
1311 1315 if tree[0] == 'func' and len(tree) > 1:
1312 1316 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1313 1317 name = tree[1][1]
1314 1318 alias = aliases.get(name)
1315 1319 if alias and alias.args is not None and alias.tree == tree[:2]:
1316 1320 return alias
1317 1321 return None
1318 1322
1319 1323 def _expandargs(tree, args):
1320 1324 """Replace all occurences of ('string', name) with the
1321 1325 substitution value of the same name in args, recursively.
1322 1326 """
1323 1327 if not isinstance(tree, tuple):
1324 1328 return tree
1325 1329 if len(tree) == 2 and tree[0] == 'string':
1326 1330 return args.get(tree[1], tree)
1327 1331 return tuple(_expandargs(t, args) for t in tree)
1328 1332
1329 1333 def _expandaliases(aliases, tree, expanding):
1330 1334 """Expand aliases in tree, recursively.
1331 1335
1332 1336 'aliases' is a dictionary mapping user defined aliases to
1333 1337 revsetalias objects.
1334 1338 """
1335 1339 if not isinstance(tree, tuple):
1336 1340 # Do not expand raw strings
1337 1341 return tree
1338 1342 alias = _getalias(aliases, tree)
1339 1343 if alias is not None:
1340 1344 if alias in expanding:
1341 1345 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1342 1346 'detected') % alias.name)
1343 1347 expanding.append(alias)
1344 1348 result = alias.replacement
1345 1349 if alias.args is not None:
1346 1350 l = getlist(tree[2])
1347 1351 if len(l) != len(alias.args):
1348 1352 raise error.ParseError(
1349 1353 _('invalid number of arguments: %s') % len(l))
1350 1354 result = _expandargs(result, dict(zip(alias.args, l)))
1351 1355 # Recurse in place, the base expression may have been rewritten
1352 1356 result = _expandaliases(aliases, result, expanding)
1353 1357 expanding.pop()
1354 1358 else:
1355 1359 result = tuple(_expandaliases(aliases, t, expanding)
1356 1360 for t in tree)
1357 1361 return result
1358 1362
1359 1363 def findaliases(ui, tree):
1360 1364 aliases = {}
1361 1365 for k, v in ui.configitems('revsetalias'):
1362 1366 alias = revsetalias(k, v)
1363 1367 aliases[alias.name] = alias
1364 1368 return _expandaliases(aliases, tree, [])
1365 1369
1366 1370 parse = parser.parser(tokenize, elements).parse
1367 1371
1368 1372 def match(ui, spec):
1369 1373 if not spec:
1370 1374 raise error.ParseError(_("empty query"))
1371 1375 tree, pos = parse(spec)
1372 1376 if (pos != len(spec)):
1373 1377 raise error.ParseError(_("invalid token"), pos)
1374 1378 if ui:
1375 1379 tree = findaliases(ui, tree)
1376 1380 weight, tree = optimize(tree, True)
1377 1381 def mfunc(repo, subset):
1378 1382 return getset(repo, subset, tree)
1379 1383 return mfunc
1380 1384
1381 1385 def formatspec(expr, *args):
1382 1386 '''
1383 1387 This is a convenience function for using revsets internally, and
1384 1388 escapes arguments appropriately. Aliases are intentionally ignored
1385 1389 so that intended expression behavior isn't accidentally subverted.
1386 1390
1387 1391 Supported arguments:
1388 1392
1389 1393 %r = revset expression, parenthesized
1390 1394 %d = int(arg), no quoting
1391 1395 %s = string(arg), escaped and single-quoted
1392 1396 %b = arg.branch(), escaped and single-quoted
1393 1397 %n = hex(arg), single-quoted
1394 1398 %% = a literal '%'
1395 1399
1396 1400 Prefixing the type with 'l' specifies a parenthesized list of that type.
1397 1401
1398 1402 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1399 1403 '(10 or 11):: and ((this()) or (that()))'
1400 1404 >>> formatspec('%d:: and not %d::', 10, 20)
1401 1405 '10:: and not 20::'
1402 1406 >>> formatspec('%ld or %ld', [], [1])
1403 1407 "_list('') or 1"
1404 1408 >>> formatspec('keyword(%s)', 'foo\\xe9')
1405 1409 "keyword('foo\\\\xe9')"
1406 1410 >>> b = lambda: 'default'
1407 1411 >>> b.branch = b
1408 1412 >>> formatspec('branch(%b)', b)
1409 1413 "branch('default')"
1410 1414 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1411 1415 "root(_list('a\\x00b\\x00c\\x00d'))"
1412 1416 '''
1413 1417
1414 1418 def quote(s):
1415 1419 return repr(str(s))
1416 1420
1417 1421 def argtype(c, arg):
1418 1422 if c == 'd':
1419 1423 return str(int(arg))
1420 1424 elif c == 's':
1421 1425 return quote(arg)
1422 1426 elif c == 'r':
1423 1427 parse(arg) # make sure syntax errors are confined
1424 1428 return '(%s)' % arg
1425 1429 elif c == 'n':
1426 1430 return quote(node.hex(arg))
1427 1431 elif c == 'b':
1428 1432 return quote(arg.branch())
1429 1433
1430 1434 def listexp(s, t):
1431 1435 l = len(s)
1432 1436 if l == 0:
1433 1437 return "_list('')"
1434 1438 elif l == 1:
1435 1439 return argtype(t, s[0])
1436 1440 elif t == 'd':
1437 1441 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1438 1442 elif t == 's':
1439 1443 return "_list('%s')" % "\0".join(s)
1440 1444 elif t == 'n':
1441 1445 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1442 1446 elif t == 'b':
1443 1447 return "_list('%s')" % "\0".join(a.branch() for a in s)
1444 1448
1445 1449 m = l // 2
1446 1450 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1447 1451
1448 1452 ret = ''
1449 1453 pos = 0
1450 1454 arg = 0
1451 1455 while pos < len(expr):
1452 1456 c = expr[pos]
1453 1457 if c == '%':
1454 1458 pos += 1
1455 1459 d = expr[pos]
1456 1460 if d == '%':
1457 1461 ret += d
1458 1462 elif d in 'dsnbr':
1459 1463 ret += argtype(d, args[arg])
1460 1464 arg += 1
1461 1465 elif d == 'l':
1462 1466 # a list of some type
1463 1467 pos += 1
1464 1468 d = expr[pos]
1465 1469 ret += listexp(list(args[arg]), d)
1466 1470 arg += 1
1467 1471 else:
1468 1472 raise util.Abort('unexpected revspec format character %s' % d)
1469 1473 else:
1470 1474 ret += c
1471 1475 pos += 1
1472 1476
1473 1477 return ret
1474 1478
1475 1479 def prettyformat(tree):
1476 1480 def _prettyformat(tree, level, lines):
1477 1481 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1478 1482 lines.append((level, str(tree)))
1479 1483 else:
1480 1484 lines.append((level, '(%s' % tree[0]))
1481 1485 for s in tree[1:]:
1482 1486 _prettyformat(s, level + 1, lines)
1483 1487 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1484 1488
1485 1489 lines = []
1486 1490 _prettyformat(tree, 0, lines)
1487 1491 output = '\n'.join((' '*l + s) for l, s in lines)
1488 1492 return output
1489 1493
1490 1494 # tell hggettext to extract docstrings from these functions:
1491 1495 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now