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