##// END OF EJS Templates
revset: add missing whitespace
Kevin Gessner -
r14072:2e4d79dc default
parent child Browse files
Show More
@@ -1,909 +1,909 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, help, hbisect
10 10 import bookmarks as bookmarksmod
11 11 import match as matchmod
12 12 from i18n import _
13 13
14 14 elements = {
15 15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 16 "~": (18, None, ("ancestor", 18)),
17 17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
18 18 "-": (5, ("negate", 19), ("minus", 5)),
19 19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
20 20 ("dagrangepost", 17)),
21 21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
22 22 ("dagrangepost", 17)),
23 23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
24 24 "not": (10, ("not", 10)),
25 25 "!": (10, ("not", 10)),
26 26 "and": (5, None, ("and", 5)),
27 27 "&": (5, None, ("and", 5)),
28 28 "or": (4, None, ("or", 4)),
29 29 "|": (4, None, ("or", 4)),
30 30 "+": (4, None, ("or", 4)),
31 31 ",": (2, None, ("list", 2)),
32 32 ")": (0, None, None),
33 33 "symbol": (0, ("symbol",), None),
34 34 "string": (0, ("string",), None),
35 35 "end": (0, None, None),
36 36 }
37 37
38 38 keywords = set(['and', 'or', 'not'])
39 39
40 40 def tokenize(program):
41 41 pos, l = 0, len(program)
42 42 while pos < l:
43 43 c = program[pos]
44 44 if c.isspace(): # skip inter-token whitespace
45 45 pass
46 46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
47 47 yield ('::', None, pos)
48 48 pos += 1 # skip ahead
49 49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
50 50 yield ('..', None, pos)
51 51 pos += 1 # skip ahead
52 52 elif c in "():,-|&+!~^": # handle simple operators
53 53 yield (c, None, pos)
54 54 elif (c in '"\'' or c == 'r' and
55 55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
56 56 if c == 'r':
57 57 pos += 1
58 58 c = program[pos]
59 59 decode = lambda x: x
60 60 else:
61 61 decode = lambda x: x.decode('string-escape')
62 62 pos += 1
63 63 s = pos
64 64 while pos < l: # find closing quote
65 65 d = program[pos]
66 66 if d == '\\': # skip over escaped characters
67 67 pos += 2
68 68 continue
69 69 if d == c:
70 70 yield ('string', decode(program[s:pos]), s)
71 71 break
72 72 pos += 1
73 73 else:
74 74 raise error.ParseError(_("unterminated string"), s)
75 75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
76 76 s = pos
77 77 pos += 1
78 78 while pos < l: # find end of symbol
79 79 d = program[pos]
80 80 if not (d.isalnum() or d in "._" or ord(d) > 127):
81 81 break
82 82 if d == '.' and program[pos - 1] == '.': # special case for ..
83 83 pos -= 1
84 84 break
85 85 pos += 1
86 86 sym = program[s:pos]
87 87 if sym in keywords: # operator keywords
88 88 yield (sym, None, s)
89 89 else:
90 90 yield ('symbol', sym, s)
91 91 pos -= 1
92 92 else:
93 93 raise error.ParseError(_("syntax error"), pos)
94 94 pos += 1
95 95 yield ('end', None, pos)
96 96
97 97 # helpers
98 98
99 99 def getstring(x, err):
100 100 if x and (x[0] == 'string' or x[0] == 'symbol'):
101 101 return x[1]
102 102 raise error.ParseError(err)
103 103
104 104 def getlist(x):
105 105 if not x:
106 106 return []
107 107 if x[0] == 'list':
108 108 return getlist(x[1]) + [x[2]]
109 109 return [x]
110 110
111 111 def getargs(x, min, max, err):
112 112 l = getlist(x)
113 113 if len(l) < min or len(l) > max:
114 114 raise error.ParseError(err)
115 115 return l
116 116
117 117 def getset(repo, subset, x):
118 118 if not x:
119 119 raise error.ParseError(_("missing argument"))
120 120 return methods[x[0]](repo, subset, *x[1:])
121 121
122 122 # operator methods
123 123
124 124 def stringset(repo, subset, x):
125 125 x = repo[x].rev()
126 126 if x == -1 and len(subset) == len(repo):
127 127 return [-1]
128 128 if len(subset) == len(repo) or x in subset:
129 129 return [x]
130 130 return []
131 131
132 132 def symbolset(repo, subset, x):
133 133 if x in symbols:
134 134 raise error.ParseError(_("can't use %s here") % x)
135 135 return stringset(repo, subset, x)
136 136
137 137 def rangeset(repo, subset, x, y):
138 138 m = getset(repo, subset, x)
139 139 if not m:
140 140 m = getset(repo, range(len(repo)), x)
141 141
142 142 n = getset(repo, subset, y)
143 143 if not n:
144 144 n = getset(repo, range(len(repo)), y)
145 145
146 146 if not m or not n:
147 147 return []
148 148 m, n = m[0], n[-1]
149 149
150 150 if m < n:
151 151 r = range(m, n + 1)
152 152 else:
153 153 r = range(m, n - 1, -1)
154 154 s = set(subset)
155 155 return [x for x in r if x in s]
156 156
157 157 def andset(repo, subset, x, y):
158 158 return getset(repo, getset(repo, subset, x), y)
159 159
160 160 def orset(repo, subset, x, y):
161 161 xl = getset(repo, subset, x)
162 162 s = set(xl)
163 163 yl = getset(repo, [r for r in subset if r not in s], y)
164 164 return xl + yl
165 165
166 166 def notset(repo, subset, x):
167 167 s = set(getset(repo, subset, x))
168 168 return [r for r in subset if r not in s]
169 169
170 170 def listset(repo, subset, a, b):
171 171 raise error.ParseError(_("can't use a list in this context"))
172 172
173 173 def func(repo, subset, a, b):
174 174 if a[0] == 'symbol' and a[1] in symbols:
175 175 return symbols[a[1]](repo, subset, b)
176 176 raise error.ParseError(_("not a function: %s") % a[1])
177 177
178 178 # functions
179 179
180 180 def adds(repo, subset, x):
181 181 """``adds(pattern)``
182 182 Changesets that add a file matching pattern.
183 183 """
184 184 # i18n: "adds" is a keyword
185 185 pat = getstring(x, _("adds requires a pattern"))
186 186 return checkstatus(repo, subset, pat, 1)
187 187
188 188 def ancestor(repo, subset, x):
189 189 """``ancestor(single, single)``
190 190 Greatest common ancestor of the two changesets.
191 191 """
192 192 # i18n: "ancestor" is a keyword
193 193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
194 194 r = range(len(repo))
195 195 a = getset(repo, r, l[0])
196 196 b = getset(repo, r, l[1])
197 197 if len(a) != 1 or len(b) != 1:
198 198 # i18n: "ancestor" is a keyword
199 199 raise error.ParseError(_("ancestor arguments must be single revisions"))
200 200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
201 201
202 202 return [r for r in an if r in subset]
203 203
204 204 def ancestors(repo, subset, x):
205 205 """``ancestors(set)``
206 206 Changesets that are ancestors of a changeset in set.
207 207 """
208 208 args = getset(repo, range(len(repo)), x)
209 209 if not args:
210 210 return []
211 211 s = set(repo.changelog.ancestors(*args)) | set(args)
212 212 return [r for r in subset if r in s]
213 213
214 214 def ancestorspec(repo, subset, x, n):
215 215 """``set~n``
216 216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
217 217 """
218 218 try:
219 219 n = int(n[1])
220 220 except ValueError:
221 221 raise error.ParseError(_("~ expects a number"))
222 222 ps = set()
223 223 cl = repo.changelog
224 224 for r in getset(repo, subset, x):
225 225 for i in range(n):
226 226 r = cl.parentrevs(r)[0]
227 227 ps.add(r)
228 228 return [r for r in subset if r in ps]
229 229
230 230 def author(repo, subset, x):
231 231 """``author(string)``
232 232 Alias for ``user(string)``.
233 233 """
234 234 # i18n: "author" is a keyword
235 235 n = getstring(x, _("author requires a string")).lower()
236 236 return [r for r in subset if n in repo[r].user().lower()]
237 237
238 238 def bisected(repo, subset, x):
239 239 """``bisected(string)``
240 240 Changesets marked in the specified bisect state (good, bad, skip).
241 241 """
242 242 state = getstring(x, _("bisect requires a string")).lower()
243 243 if state not in ('good', 'bad', 'skip', 'unknown'):
244 244 raise error.ParseError(_('invalid bisect state'))
245 245 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
246 246 return [r for r in subset if r in marked]
247 247
248 248 def bookmark(repo, subset, x):
249 249 """``bookmark([name])``
250 250 The named bookmark or all bookmarks.
251 251 """
252 252 # i18n: "bookmark" is a keyword
253 253 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
254 254 if args:
255 255 bm = getstring(args[0],
256 256 # i18n: "bookmark" is a keyword
257 257 _('the argument to bookmark must be a string'))
258 258 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
259 259 if not bmrev:
260 260 raise util.Abort(_("bookmark '%s' does not exist") % bm)
261 261 bmrev = repo[bmrev].rev()
262 262 return [r for r in subset if r == bmrev]
263 263 bms = set([repo[r].rev()
264 264 for r in bookmarksmod.listbookmarks(repo).values()])
265 265 return [r for r in subset if r in bms]
266 266
267 267 def branch(repo, subset, x):
268 268 """``branch(string or set)``
269 269 All changesets belonging to the given branch or the branches of the given
270 270 changesets.
271 271 """
272 272 try:
273 273 b = getstring(x, '')
274 274 if b in repo.branchmap():
275 275 return [r for r in subset if repo[r].branch() == b]
276 276 except error.ParseError:
277 277 # not a string, but another revspec, e.g. tip()
278 278 pass
279 279
280 280 s = getset(repo, range(len(repo)), x)
281 281 b = set()
282 282 for r in s:
283 283 b.add(repo[r].branch())
284 284 s = set(s)
285 285 return [r for r in subset if r in s or repo[r].branch() in b]
286 286
287 287 def checkstatus(repo, subset, pat, field):
288 288 m = matchmod.match(repo.root, repo.getcwd(), [pat])
289 289 s = []
290 290 fast = (m.files() == [pat])
291 291 for r in subset:
292 292 c = repo[r]
293 293 if fast:
294 294 if pat not in c.files():
295 295 continue
296 296 else:
297 297 for f in c.files():
298 298 if m(f):
299 299 break
300 300 else:
301 301 continue
302 302 files = repo.status(c.p1().node(), c.node())[field]
303 303 if fast:
304 304 if pat in files:
305 305 s.append(r)
306 306 else:
307 307 for f in files:
308 308 if m(f):
309 309 s.append(r)
310 310 break
311 311 return s
312 312
313 313 def children(repo, subset, x):
314 314 """``children(set)``
315 315 Child changesets of changesets in set.
316 316 """
317 317 cs = set()
318 318 cl = repo.changelog
319 319 s = set(getset(repo, range(len(repo)), x))
320 320 for r in xrange(0, len(repo)):
321 321 for p in cl.parentrevs(r):
322 322 if p in s:
323 323 cs.add(r)
324 324 return [r for r in subset if r in cs]
325 325
326 326 def closed(repo, subset, x):
327 327 """``closed()``
328 328 Changeset is closed.
329 329 """
330 330 # i18n: "closed" is a keyword
331 331 getargs(x, 0, 0, _("closed takes no arguments"))
332 332 return [r for r in subset if repo[r].extra().get('close')]
333 333
334 334 def contains(repo, subset, x):
335 335 """``contains(pattern)``
336 336 Revision contains pattern.
337 337 """
338 338 # i18n: "contains" is a keyword
339 339 pat = getstring(x, _("contains requires a pattern"))
340 340 m = matchmod.match(repo.root, repo.getcwd(), [pat])
341 341 s = []
342 342 if m.files() == [pat]:
343 343 for r in subset:
344 344 if pat in repo[r]:
345 345 s.append(r)
346 346 else:
347 347 for r in subset:
348 348 for f in repo[r].manifest():
349 349 if m(f):
350 350 s.append(r)
351 351 break
352 352 return s
353 353
354 354 def date(repo, subset, x):
355 355 """``date(interval)``
356 356 Changesets within the interval, see :hg:`help dates`.
357 357 """
358 358 # i18n: "date" is a keyword
359 359 ds = getstring(x, _("date requires a string"))
360 360 dm = util.matchdate(ds)
361 361 return [r for r in subset if dm(repo[r].date()[0])]
362 362
363 363 def descendants(repo, subset, x):
364 364 """``descendants(set)``
365 365 Changesets which are descendants of changesets in set.
366 366 """
367 367 args = getset(repo, range(len(repo)), x)
368 368 if not args:
369 369 return []
370 370 s = set(repo.changelog.descendants(*args)) | set(args)
371 371 return [r for r in subset if r in s]
372 372
373 373 def follow(repo, subset, x):
374 374 """``follow()``
375 375 An alias for ``::.`` (ancestors of the working copy's first parent).
376 376 """
377 377 # i18n: "follow" is a keyword
378 378 getargs(x, 0, 0, _("follow takes no arguments"))
379 379 p = repo['.'].rev()
380 380 s = set(repo.changelog.ancestors(p)) | set([p])
381 381 return [r for r in subset if r in s]
382 382
383 383 def getall(repo, subset, x):
384 384 """``all()``
385 385 All changesets, the same as ``0:tip``.
386 386 """
387 387 # i18n: "all" is a keyword
388 388 getargs(x, 0, 0, _("all takes no arguments"))
389 389 return subset
390 390
391 391 def grep(repo, subset, x):
392 392 """``grep(regex)``
393 393 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
394 394 to ensure special escape characters are handled correctly.
395 395 """
396 396 try:
397 397 # i18n: "grep" is a keyword
398 398 gr = re.compile(getstring(x, _("grep requires a string")))
399 399 except re.error, e:
400 400 raise error.ParseError(_('invalid match pattern: %s') % e)
401 401 l = []
402 402 for r in subset:
403 403 c = repo[r]
404 404 for e in c.files() + [c.user(), c.description()]:
405 405 if gr.search(e):
406 406 l.append(r)
407 407 break
408 408 return l
409 409
410 410 def hasfile(repo, subset, x):
411 411 """``file(pattern)``
412 412 Changesets affecting files matched by pattern.
413 413 """
414 414 # i18n: "file" is a keyword
415 415 pat = getstring(x, _("file requires a pattern"))
416 416 m = matchmod.match(repo.root, repo.getcwd(), [pat])
417 417 s = []
418 418 for r in subset:
419 419 for f in repo[r].files():
420 420 if m(f):
421 421 s.append(r)
422 422 break
423 423 return s
424 424
425 425 def head(repo, subset, x):
426 426 """``head()``
427 427 Changeset is a named branch head.
428 428 """
429 429 # i18n: "head" is a keyword
430 430 getargs(x, 0, 0, _("head takes no arguments"))
431 431 hs = set()
432 432 for b, ls in repo.branchmap().iteritems():
433 433 hs.update(repo[h].rev() for h in ls)
434 434 return [r for r in subset if r in hs]
435 435
436 436 def heads(repo, subset, x):
437 437 """``heads(set)``
438 438 Members of set with no children in set.
439 439 """
440 440 s = getset(repo, subset, x)
441 441 ps = set(parents(repo, subset, x))
442 442 return [r for r in s if r not in ps]
443 443
444 444 def keyword(repo, subset, x):
445 445 """``keyword(string)``
446 446 Search commit message, user name, and names of changed files for
447 447 string.
448 448 """
449 449 # i18n: "keyword" is a keyword
450 450 kw = getstring(x, _("keyword requires a string")).lower()
451 451 l = []
452 452 for r in subset:
453 453 c = repo[r]
454 454 t = " ".join(c.files() + [c.user(), c.description()])
455 455 if kw in t.lower():
456 456 l.append(r)
457 457 return l
458 458
459 459 def limit(repo, subset, x):
460 460 """``limit(set, n)``
461 461 First n members of set.
462 462 """
463 463 # i18n: "limit" is a keyword
464 464 l = getargs(x, 2, 2, _("limit requires two arguments"))
465 465 try:
466 466 # i18n: "limit" is a keyword
467 467 lim = int(getstring(l[1], _("limit requires a number")))
468 468 except ValueError:
469 469 # i18n: "limit" is a keyword
470 470 raise error.ParseError(_("limit expects a number"))
471 471 return getset(repo, subset, l[0])[:lim]
472 472
473 473 def last(repo, subset, x):
474 474 """``last(set, n)``
475 475 Last n members of set.
476 476 """
477 477 # i18n: "last" is a keyword
478 478 l = getargs(x, 2, 2, _("last requires two arguments"))
479 479 try:
480 480 # i18n: "last" is a keyword
481 481 lim = int(getstring(l[1], _("last requires a number")))
482 482 except ValueError:
483 483 # i18n: "last" is a keyword
484 484 raise error.ParseError(_("last expects a number"))
485 485 return getset(repo, subset, l[0])[-lim:]
486 486
487 487 def maxrev(repo, subset, x):
488 488 """``max(set)``
489 489 Changeset with highest revision number in set.
490 490 """
491 491 s = getset(repo, subset, x)
492 492 if s:
493 493 m = max(s)
494 494 if m in subset:
495 495 return [m]
496 496 return []
497 497
498 498 def merge(repo, subset, x):
499 499 """``merge()``
500 500 Changeset is a merge changeset.
501 501 """
502 502 # i18n: "merge" is a keyword
503 503 getargs(x, 0, 0, _("merge takes no arguments"))
504 504 cl = repo.changelog
505 505 return [r for r in subset if cl.parentrevs(r)[1] != -1]
506 506
507 507 def minrev(repo, subset, x):
508 508 """``min(set)``
509 509 Changeset with lowest revision number in set.
510 510 """
511 511 s = getset(repo, subset, x)
512 512 if s:
513 513 m = min(s)
514 514 if m in subset:
515 515 return [m]
516 516 return []
517 517
518 518 def modifies(repo, subset, x):
519 519 """``modifies(pattern)``
520 520 Changesets modifying files matched by pattern.
521 521 """
522 522 # i18n: "modifies" is a keyword
523 523 pat = getstring(x, _("modifies requires a pattern"))
524 524 return checkstatus(repo, subset, pat, 0)
525 525
526 526 def node(repo, subset, x):
527 527 """``id(string)``
528 528 Revision non-ambiguously specified by the given hex string prefix.
529 529 """
530 530 # i18n: "id" is a keyword
531 531 l = getargs(x, 1, 1, _("id requires one argument"))
532 532 # i18n: "id" is a keyword
533 533 n = getstring(l[0], _("id requires a string"))
534 534 if len(n) == 40:
535 535 rn = repo[n].rev()
536 536 else:
537 537 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
538 538 return [r for r in subset if r == rn]
539 539
540 540 def outgoing(repo, subset, x):
541 541 """``outgoing([path])``
542 542 Changesets not found in the specified destination repository, or the
543 543 default push location.
544 544 """
545 545 import hg # avoid start-up nasties
546 546 # i18n: "outgoing" is a keyword
547 547 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
548 548 # i18n: "outgoing" is a keyword
549 549 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
550 550 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
551 551 dest, branches = hg.parseurl(dest)
552 552 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
553 553 if revs:
554 554 revs = [repo.lookup(rev) for rev in revs]
555 555 other = hg.repository(hg.remoteui(repo, {}), dest)
556 556 repo.ui.pushbuffer()
557 557 o = discovery.findoutgoing(repo, other)
558 558 repo.ui.popbuffer()
559 559 cl = repo.changelog
560 560 o = set([cl.rev(r) for r in repo.changelog.nodesbetween(o, revs)[0]])
561 561 return [r for r in subset if r in o]
562 562
563 563 def p1(repo, subset, x):
564 564 """``p1([set])``
565 565 First parent of changesets in set, or the working directory.
566 566 """
567 567 if x is None:
568 568 p = repo[x].p1().rev()
569 569 return [r for r in subset if r == p]
570 570
571 571 ps = set()
572 572 cl = repo.changelog
573 573 for r in getset(repo, range(len(repo)), x):
574 574 ps.add(cl.parentrevs(r)[0])
575 575 return [r for r in subset if r in ps]
576 576
577 577 def p2(repo, subset, x):
578 578 """``p2([set])``
579 579 Second parent of changesets in set, or the working directory.
580 580 """
581 581 if x is None:
582 582 ps = repo[x].parents()
583 583 try:
584 584 p = ps[1].rev()
585 585 return [r for r in subset if r == p]
586 586 except IndexError:
587 587 return []
588 588
589 589 ps = set()
590 590 cl = repo.changelog
591 591 for r in getset(repo, range(len(repo)), x):
592 592 ps.add(cl.parentrevs(r)[1])
593 593 return [r for r in subset if r in ps]
594 594
595 595 def parents(repo, subset, x):
596 596 """``parents([set])``
597 597 The set of all parents for all changesets in set, or the working directory.
598 598 """
599 599 if x is None:
600 600 ps = tuple(p.rev() for p in repo[x].parents())
601 601 return [r for r in subset if r in ps]
602 602
603 603 ps = set()
604 604 cl = repo.changelog
605 605 for r in getset(repo, range(len(repo)), x):
606 606 ps.update(cl.parentrevs(r))
607 607 return [r for r in subset if r in ps]
608 608
609 609 def parentspec(repo, subset, x, n):
610 610 """``set^0``
611 611 The set.
612 612 ``set^1`` (or ``set^``), ``set^2``
613 613 First or second parent, respectively, of all changesets in set.
614 614 """
615 615 try:
616 616 n = int(n[1])
617 if n not in (0,1,2):
617 if n not in (0, 1, 2):
618 618 raise ValueError
619 619 except ValueError:
620 620 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
621 621 ps = set()
622 622 cl = repo.changelog
623 623 for r in getset(repo, subset, x):
624 624 if n == 0:
625 625 ps.add(r)
626 626 elif n == 1:
627 627 ps.add(cl.parentrevs(r)[0])
628 628 elif n == 2:
629 629 parents = cl.parentrevs(r)
630 630 if len(parents) > 1:
631 631 ps.add(parents[1])
632 632 return [r for r in subset if r in ps]
633 633
634 634 def present(repo, subset, x):
635 635 """``present(set)``
636 636 An empty set, if any revision in set isn't found; otherwise,
637 637 all revisions in set.
638 638 """
639 639 try:
640 640 return getset(repo, subset, x)
641 641 except error.RepoLookupError:
642 642 return []
643 643
644 644 def removes(repo, subset, x):
645 645 """``removes(pattern)``
646 646 Changesets which remove files matching pattern.
647 647 """
648 648 # i18n: "removes" is a keyword
649 649 pat = getstring(x, _("removes requires a pattern"))
650 650 return checkstatus(repo, subset, pat, 2)
651 651
652 652 def rev(repo, subset, x):
653 653 """``rev(number)``
654 654 Revision with the given numeric identifier.
655 655 """
656 656 # i18n: "rev" is a keyword
657 657 l = getargs(x, 1, 1, _("rev requires one argument"))
658 658 try:
659 659 # i18n: "rev" is a keyword
660 660 l = int(getstring(l[0], _("rev requires a number")))
661 661 except ValueError:
662 662 # i18n: "rev" is a keyword
663 663 raise error.ParseError(_("rev expects a number"))
664 664 return [r for r in subset if r == l]
665 665
666 666 def reverse(repo, subset, x):
667 667 """``reverse(set)``
668 668 Reverse order of set.
669 669 """
670 670 l = getset(repo, subset, x)
671 671 l.reverse()
672 672 return l
673 673
674 674 def roots(repo, subset, x):
675 675 """``roots(set)``
676 676 Changesets with no parent changeset in set.
677 677 """
678 678 s = getset(repo, subset, x)
679 679 cs = set(children(repo, subset, x))
680 680 return [r for r in s if r not in cs]
681 681
682 682 def sort(repo, subset, x):
683 683 """``sort(set[, [-]key...])``
684 684 Sort set by keys. The default sort order is ascending, specify a key
685 685 as ``-key`` to sort in descending order.
686 686
687 687 The keys can be:
688 688
689 689 - ``rev`` for the revision number,
690 690 - ``branch`` for the branch name,
691 691 - ``desc`` for the commit message (description),
692 692 - ``user`` for user name (``author`` can be used as an alias),
693 693 - ``date`` for the commit date
694 694 """
695 695 # i18n: "sort" is a keyword
696 696 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
697 697 keys = "rev"
698 698 if len(l) == 2:
699 699 keys = getstring(l[1], _("sort spec must be a string"))
700 700
701 701 s = l[0]
702 702 keys = keys.split()
703 703 l = []
704 704 def invert(s):
705 705 return "".join(chr(255 - ord(c)) for c in s)
706 706 for r in getset(repo, subset, s):
707 707 c = repo[r]
708 708 e = []
709 709 for k in keys:
710 710 if k == 'rev':
711 711 e.append(r)
712 712 elif k == '-rev':
713 713 e.append(-r)
714 714 elif k == 'branch':
715 715 e.append(c.branch())
716 716 elif k == '-branch':
717 717 e.append(invert(c.branch()))
718 718 elif k == 'desc':
719 719 e.append(c.description())
720 720 elif k == '-desc':
721 721 e.append(invert(c.description()))
722 722 elif k in 'user author':
723 723 e.append(c.user())
724 724 elif k in '-user -author':
725 725 e.append(invert(c.user()))
726 726 elif k == 'date':
727 727 e.append(c.date()[0])
728 728 elif k == '-date':
729 729 e.append(-c.date()[0])
730 730 else:
731 731 raise error.ParseError(_("unknown sort key %r") % k)
732 732 e.append(r)
733 733 l.append(e)
734 734 l.sort()
735 735 return [e[-1] for e in l]
736 736
737 737 def tag(repo, subset, x):
738 738 """``tag(name)``
739 739 The specified tag by name, or all tagged revisions if no name is given.
740 740 """
741 741 # i18n: "tag" is a keyword
742 742 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
743 743 cl = repo.changelog
744 744 if args:
745 745 tn = getstring(args[0],
746 746 # i18n: "tag" is a keyword
747 747 _('the argument to tag must be a string'))
748 748 if not repo.tags().get(tn, None):
749 749 raise util.Abort(_("tag '%s' does not exist") % tn)
750 750 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
751 751 else:
752 752 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
753 753 return [r for r in subset if r in s]
754 754
755 755 def tagged(repo, subset, x):
756 756 return tag(repo, subset, x)
757 757
758 758 def user(repo, subset, x):
759 759 """``user(string)``
760 760 User name is string.
761 761 """
762 762 return author(repo, subset, x)
763 763
764 764 symbols = {
765 765 "adds": adds,
766 766 "all": getall,
767 767 "ancestor": ancestor,
768 768 "ancestors": ancestors,
769 769 "author": author,
770 770 "bisected": bisected,
771 771 "bookmark": bookmark,
772 772 "branch": branch,
773 773 "children": children,
774 774 "closed": closed,
775 775 "contains": contains,
776 776 "date": date,
777 777 "descendants": descendants,
778 778 "file": hasfile,
779 779 "follow": follow,
780 780 "grep": grep,
781 781 "head": head,
782 782 "heads": heads,
783 783 "keyword": keyword,
784 784 "last": last,
785 785 "limit": limit,
786 786 "max": maxrev,
787 787 "min": minrev,
788 788 "merge": merge,
789 789 "modifies": modifies,
790 790 "id": node,
791 791 "outgoing": outgoing,
792 792 "p1": p1,
793 793 "p2": p2,
794 794 "parents": parents,
795 795 "present": present,
796 796 "removes": removes,
797 797 "reverse": reverse,
798 798 "rev": rev,
799 799 "roots": roots,
800 800 "sort": sort,
801 801 "tag": tag,
802 802 "tagged": tagged,
803 803 "user": user,
804 804 }
805 805
806 806 methods = {
807 807 "range": rangeset,
808 808 "string": stringset,
809 809 "symbol": symbolset,
810 810 "and": andset,
811 811 "or": orset,
812 812 "not": notset,
813 813 "list": listset,
814 814 "func": func,
815 815 "ancestor": ancestorspec,
816 816 "parent": parentspec,
817 817 "parentpost": p1,
818 818 }
819 819
820 820 def optimize(x, small):
821 821 if x is None:
822 822 return 0, x
823 823
824 824 smallbonus = 1
825 825 if small:
826 826 smallbonus = .5
827 827
828 828 op = x[0]
829 829 if op == 'minus':
830 830 return optimize(('and', x[1], ('not', x[2])), small)
831 831 elif op == 'dagrange':
832 832 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
833 833 ('func', ('symbol', 'ancestors'), x[2])), small)
834 834 elif op == 'dagrangepre':
835 835 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
836 836 elif op == 'dagrangepost':
837 837 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
838 838 elif op == 'rangepre':
839 839 return optimize(('range', ('string', '0'), x[1]), small)
840 840 elif op == 'rangepost':
841 841 return optimize(('range', x[1], ('string', 'tip')), small)
842 842 elif op == 'negate':
843 843 return optimize(('string',
844 844 '-' + getstring(x[1], _("can't negate that"))), small)
845 845 elif op in 'string symbol negate':
846 846 return smallbonus, x # single revisions are small
847 847 elif op == 'and' or op == 'dagrange':
848 848 wa, ta = optimize(x[1], True)
849 849 wb, tb = optimize(x[2], True)
850 850 w = min(wa, wb)
851 851 if wa > wb:
852 852 return w, (op, tb, ta)
853 853 return w, (op, ta, tb)
854 854 elif op == 'or':
855 855 wa, ta = optimize(x[1], False)
856 856 wb, tb = optimize(x[2], False)
857 857 if wb < wa:
858 858 wb, wa = wa, wb
859 859 return max(wa, wb), (op, ta, tb)
860 860 elif op == 'not':
861 861 o = optimize(x[1], not small)
862 862 return o[0], (op, o[1])
863 863 elif op == 'parentpost':
864 864 o = optimize(x[1], small)
865 865 return o[0], (op, o[1])
866 866 elif op == 'group':
867 867 return optimize(x[1], small)
868 868 elif op in 'range list parent ancestorspec':
869 869 wa, ta = optimize(x[1], small)
870 870 wb, tb = optimize(x[2], small)
871 871 return wa + wb, (op, ta, tb)
872 872 elif op == 'func':
873 873 f = getstring(x[1], _("not a symbol"))
874 874 wa, ta = optimize(x[2], small)
875 875 if f in "grep date user author keyword branch file outgoing closed":
876 876 w = 10 # slow
877 877 elif f in "modifies adds removes":
878 878 w = 30 # slower
879 879 elif f == "contains":
880 880 w = 100 # very slow
881 881 elif f == "ancestor":
882 882 w = 1 * smallbonus
883 883 elif f in "reverse limit":
884 884 w = 0
885 885 elif f in "sort":
886 886 w = 10 # assume most sorts look at changelog
887 887 else:
888 888 w = 1
889 889 return w + wa, (op, x[1], ta)
890 890 return 1, x
891 891
892 892 parse = parser.parser(tokenize, elements).parse
893 893
894 894 def match(spec):
895 895 if not spec:
896 896 raise error.ParseError(_("empty query"))
897 897 tree, pos = parse(spec)
898 898 if (pos != len(spec)):
899 899 raise error.ParseError("invalid token", pos)
900 900 weight, tree = optimize(tree, True)
901 901 def mfunc(repo, subset):
902 902 return getset(repo, subset, tree)
903 903 return mfunc
904 904
905 905 def makedoc(topic, doc):
906 906 return help.makeitemsdoc(topic, doc, '.. predicatesmarker', symbols)
907 907
908 908 # tell hggettext to extract docstrings from these functions:
909 909 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now