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