##// END OF EJS Templates
revset: update sorting of symbols
Thomas Arendsen Hein -
r14649:a6a8809c default
parent child Browse files
Show More
@@ -1,1029 +1,1029
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
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 a file matching pattern. See :hg:`help patterns`
337 337 for information about file patterns.
338 338 """
339 339 # i18n: "contains" is a keyword
340 340 pat = getstring(x, _("contains requires a pattern"))
341 341 m = matchmod.match(repo.root, repo.getcwd(), [pat])
342 342 s = []
343 343 if m.files() == [pat]:
344 344 for r in subset:
345 345 if pat in repo[r]:
346 346 s.append(r)
347 347 else:
348 348 for r in subset:
349 349 for f in repo[r].manifest():
350 350 if m(f):
351 351 s.append(r)
352 352 break
353 353 return s
354 354
355 355 def date(repo, subset, x):
356 356 """``date(interval)``
357 357 Changesets within the interval, see :hg:`help dates`.
358 358 """
359 359 # i18n: "date" is a keyword
360 360 ds = getstring(x, _("date requires a string"))
361 361 dm = util.matchdate(ds)
362 362 return [r for r in subset if dm(repo[r].date()[0])]
363 363
364 364 def descendants(repo, subset, x):
365 365 """``descendants(set)``
366 366 Changesets which are descendants of changesets in set.
367 367 """
368 368 args = getset(repo, range(len(repo)), x)
369 369 if not args:
370 370 return []
371 371 s = set(repo.changelog.descendants(*args)) | set(args)
372 372 return [r for r in subset if r in s]
373 373
374 374 def filelog(repo, subset, x):
375 375 """``filelog(pattern)``
376 376 Changesets connected to the specified filelog.
377 377 """
378 378
379 379 pat = getstring(x, _("filelog requires a pattern"))
380 380 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
381 381 s = set()
382 382
383 383 if not m.anypats():
384 384 for f in m.files():
385 385 fl = repo.file(f)
386 386 for fr in fl:
387 387 s.add(fl.linkrev(fr))
388 388 else:
389 389 for f in repo[None]:
390 390 if m(f):
391 391 fl = repo.file(f)
392 392 for fr in fl:
393 393 s.add(fl.linkrev(fr))
394 394
395 395 return [r for r in subset if r in s]
396 396
397 397 def follow(repo, subset, x):
398 398 """``follow([file])``
399 399 An alias for ``::.`` (ancestors of the working copy's first parent).
400 400 If a filename is specified, the history of the given file is followed,
401 401 including copies.
402 402 """
403 403 # i18n: "follow" is a keyword
404 404 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
405 405 p = repo['.'].rev()
406 406 if l:
407 407 x = getstring(l[0], "follow expected a filename")
408 408 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
409 409 else:
410 410 s = set(repo.changelog.ancestors(p))
411 411
412 412 s |= set([p])
413 413 return [r for r in subset if r in s]
414 414
415 415 def followfile(repo, subset, f):
416 416 """``follow()``
417 417 An alias for ``::.`` (ancestors of the working copy's first parent).
418 418 """
419 419 # i18n: "follow" is a keyword
420 420 getargs(x, 0, 0, _("follow takes no arguments"))
421 421 p = repo['.'].rev()
422 422 s = set(repo.changelog.ancestors(p)) | set([p])
423 423 return [r for r in subset if r in s]
424 424
425 425 def getall(repo, subset, x):
426 426 """``all()``
427 427 All changesets, the same as ``0:tip``.
428 428 """
429 429 # i18n: "all" is a keyword
430 430 getargs(x, 0, 0, _("all takes no arguments"))
431 431 return subset
432 432
433 433 def grep(repo, subset, x):
434 434 """``grep(regex)``
435 435 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
436 436 to ensure special escape characters are handled correctly. Unlike
437 437 ``keyword(string)``, the match is case-sensitive.
438 438 """
439 439 try:
440 440 # i18n: "grep" is a keyword
441 441 gr = re.compile(getstring(x, _("grep requires a string")))
442 442 except re.error, e:
443 443 raise error.ParseError(_('invalid match pattern: %s') % e)
444 444 l = []
445 445 for r in subset:
446 446 c = repo[r]
447 447 for e in c.files() + [c.user(), c.description()]:
448 448 if gr.search(e):
449 449 l.append(r)
450 450 break
451 451 return l
452 452
453 453 def hasfile(repo, subset, x):
454 454 """``file(pattern)``
455 455 Changesets affecting files matched by pattern.
456 456 """
457 457 # i18n: "file" is a keyword
458 458 pat = getstring(x, _("file requires a pattern"))
459 459 m = matchmod.match(repo.root, repo.getcwd(), [pat])
460 460 s = []
461 461 for r in subset:
462 462 for f in repo[r].files():
463 463 if m(f):
464 464 s.append(r)
465 465 break
466 466 return s
467 467
468 468 def head(repo, subset, x):
469 469 """``head()``
470 470 Changeset is a named branch head.
471 471 """
472 472 # i18n: "head" is a keyword
473 473 getargs(x, 0, 0, _("head takes no arguments"))
474 474 hs = set()
475 475 for b, ls in repo.branchmap().iteritems():
476 476 hs.update(repo[h].rev() for h in ls)
477 477 return [r for r in subset if r in hs]
478 478
479 479 def heads(repo, subset, x):
480 480 """``heads(set)``
481 481 Members of set with no children in set.
482 482 """
483 483 s = getset(repo, subset, x)
484 484 ps = set(parents(repo, subset, x))
485 485 return [r for r in s if r not in ps]
486 486
487 487 def keyword(repo, subset, x):
488 488 """``keyword(string)``
489 489 Search commit message, user name, and names of changed files for
490 490 string. The match is case-insensitive.
491 491 """
492 492 # i18n: "keyword" is a keyword
493 493 kw = getstring(x, _("keyword requires a string")).lower()
494 494 l = []
495 495 for r in subset:
496 496 c = repo[r]
497 497 t = " ".join(c.files() + [c.user(), c.description()])
498 498 if kw in t.lower():
499 499 l.append(r)
500 500 return l
501 501
502 502 def limit(repo, subset, x):
503 503 """``limit(set, n)``
504 504 First n members of set.
505 505 """
506 506 # i18n: "limit" is a keyword
507 507 l = getargs(x, 2, 2, _("limit requires two arguments"))
508 508 try:
509 509 # i18n: "limit" is a keyword
510 510 lim = int(getstring(l[1], _("limit requires a number")))
511 511 except ValueError:
512 512 # i18n: "limit" is a keyword
513 513 raise error.ParseError(_("limit expects a number"))
514 514 ss = set(subset)
515 515 os = getset(repo, range(len(repo)), l[0])[:lim]
516 516 return [r for r in os if r in ss]
517 517
518 518 def last(repo, subset, x):
519 519 """``last(set, n)``
520 520 Last n members of set.
521 521 """
522 522 # i18n: "last" is a keyword
523 523 l = getargs(x, 2, 2, _("last requires two arguments"))
524 524 try:
525 525 # i18n: "last" is a keyword
526 526 lim = int(getstring(l[1], _("last requires a number")))
527 527 except ValueError:
528 528 # i18n: "last" is a keyword
529 529 raise error.ParseError(_("last expects a number"))
530 530 ss = set(subset)
531 531 os = getset(repo, range(len(repo)), l[0])[-lim:]
532 532 return [r for r in os if r in ss]
533 533
534 534 def maxrev(repo, subset, x):
535 535 """``max(set)``
536 536 Changeset with highest revision number in set.
537 537 """
538 538 os = getset(repo, range(len(repo)), x)
539 539 if os:
540 540 m = max(os)
541 541 if m in subset:
542 542 return [m]
543 543 return []
544 544
545 545 def merge(repo, subset, x):
546 546 """``merge()``
547 547 Changeset is a merge changeset.
548 548 """
549 549 # i18n: "merge" is a keyword
550 550 getargs(x, 0, 0, _("merge takes no arguments"))
551 551 cl = repo.changelog
552 552 return [r for r in subset if cl.parentrevs(r)[1] != -1]
553 553
554 554 def minrev(repo, subset, x):
555 555 """``min(set)``
556 556 Changeset with lowest revision number in set.
557 557 """
558 558 os = getset(repo, range(len(repo)), x)
559 559 if os:
560 560 m = min(os)
561 561 if m in subset:
562 562 return [m]
563 563 return []
564 564
565 565 def modifies(repo, subset, x):
566 566 """``modifies(pattern)``
567 567 Changesets modifying files matched by pattern.
568 568 """
569 569 # i18n: "modifies" is a keyword
570 570 pat = getstring(x, _("modifies requires a pattern"))
571 571 return checkstatus(repo, subset, pat, 0)
572 572
573 573 def node(repo, subset, x):
574 574 """``id(string)``
575 575 Revision non-ambiguously specified by the given hex string prefix.
576 576 """
577 577 # i18n: "id" is a keyword
578 578 l = getargs(x, 1, 1, _("id requires one argument"))
579 579 # i18n: "id" is a keyword
580 580 n = getstring(l[0], _("id requires a string"))
581 581 if len(n) == 40:
582 582 rn = repo[n].rev()
583 583 else:
584 584 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
585 585 return [r for r in subset if r == rn]
586 586
587 587 def outgoing(repo, subset, x):
588 588 """``outgoing([path])``
589 589 Changesets not found in the specified destination repository, or the
590 590 default push location.
591 591 """
592 592 import hg # avoid start-up nasties
593 593 # i18n: "outgoing" is a keyword
594 594 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
595 595 # i18n: "outgoing" is a keyword
596 596 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
597 597 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
598 598 dest, branches = hg.parseurl(dest)
599 599 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
600 600 if revs:
601 601 revs = [repo.lookup(rev) for rev in revs]
602 602 other = hg.peer(repo, {}, dest)
603 603 repo.ui.pushbuffer()
604 604 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
605 605 repo.ui.popbuffer()
606 606 cl = repo.changelog
607 607 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
608 608 return [r for r in subset if r in o]
609 609
610 610 def p1(repo, subset, x):
611 611 """``p1([set])``
612 612 First parent of changesets in set, or the working directory.
613 613 """
614 614 if x is None:
615 615 p = repo[x].p1().rev()
616 616 return [r for r in subset if r == p]
617 617
618 618 ps = set()
619 619 cl = repo.changelog
620 620 for r in getset(repo, range(len(repo)), x):
621 621 ps.add(cl.parentrevs(r)[0])
622 622 return [r for r in subset if r in ps]
623 623
624 624 def p2(repo, subset, x):
625 625 """``p2([set])``
626 626 Second parent of changesets in set, or the working directory.
627 627 """
628 628 if x is None:
629 629 ps = repo[x].parents()
630 630 try:
631 631 p = ps[1].rev()
632 632 return [r for r in subset if r == p]
633 633 except IndexError:
634 634 return []
635 635
636 636 ps = set()
637 637 cl = repo.changelog
638 638 for r in getset(repo, range(len(repo)), x):
639 639 ps.add(cl.parentrevs(r)[1])
640 640 return [r for r in subset if r in ps]
641 641
642 642 def parents(repo, subset, x):
643 643 """``parents([set])``
644 644 The set of all parents for all changesets in set, or the working directory.
645 645 """
646 646 if x is None:
647 647 ps = tuple(p.rev() for p in repo[x].parents())
648 648 return [r for r in subset if r in ps]
649 649
650 650 ps = set()
651 651 cl = repo.changelog
652 652 for r in getset(repo, range(len(repo)), x):
653 653 ps.update(cl.parentrevs(r))
654 654 return [r for r in subset if r in ps]
655 655
656 656 def parentspec(repo, subset, x, n):
657 657 """``set^0``
658 658 The set.
659 659 ``set^1`` (or ``set^``), ``set^2``
660 660 First or second parent, respectively, of all changesets in set.
661 661 """
662 662 try:
663 663 n = int(n[1])
664 664 if n not in (0, 1, 2):
665 665 raise ValueError
666 666 except ValueError:
667 667 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
668 668 ps = set()
669 669 cl = repo.changelog
670 670 for r in getset(repo, subset, x):
671 671 if n == 0:
672 672 ps.add(r)
673 673 elif n == 1:
674 674 ps.add(cl.parentrevs(r)[0])
675 675 elif n == 2:
676 676 parents = cl.parentrevs(r)
677 677 if len(parents) > 1:
678 678 ps.add(parents[1])
679 679 return [r for r in subset if r in ps]
680 680
681 681 def present(repo, subset, x):
682 682 """``present(set)``
683 683 An empty set, if any revision in set isn't found; otherwise,
684 684 all revisions in set.
685 685 """
686 686 try:
687 687 return getset(repo, subset, x)
688 688 except error.RepoLookupError:
689 689 return []
690 690
691 691 def removes(repo, subset, x):
692 692 """``removes(pattern)``
693 693 Changesets which remove files matching pattern.
694 694 """
695 695 # i18n: "removes" is a keyword
696 696 pat = getstring(x, _("removes requires a pattern"))
697 697 return checkstatus(repo, subset, pat, 2)
698 698
699 699 def rev(repo, subset, x):
700 700 """``rev(number)``
701 701 Revision with the given numeric identifier.
702 702 """
703 703 # i18n: "rev" is a keyword
704 704 l = getargs(x, 1, 1, _("rev requires one argument"))
705 705 try:
706 706 # i18n: "rev" is a keyword
707 707 l = int(getstring(l[0], _("rev requires a number")))
708 708 except ValueError:
709 709 # i18n: "rev" is a keyword
710 710 raise error.ParseError(_("rev expects a number"))
711 711 return [r for r in subset if r == l]
712 712
713 713 def reverse(repo, subset, x):
714 714 """``reverse(set)``
715 715 Reverse order of set.
716 716 """
717 717 l = getset(repo, subset, x)
718 718 l.reverse()
719 719 return l
720 720
721 721 def roots(repo, subset, x):
722 722 """``roots(set)``
723 723 Changesets with no parent changeset in set.
724 724 """
725 725 s = getset(repo, subset, x)
726 726 cs = set(children(repo, subset, x))
727 727 return [r for r in s if r not in cs]
728 728
729 729 def sort(repo, subset, x):
730 730 """``sort(set[, [-]key...])``
731 731 Sort set by keys. The default sort order is ascending, specify a key
732 732 as ``-key`` to sort in descending order.
733 733
734 734 The keys can be:
735 735
736 736 - ``rev`` for the revision number,
737 737 - ``branch`` for the branch name,
738 738 - ``desc`` for the commit message (description),
739 739 - ``user`` for user name (``author`` can be used as an alias),
740 740 - ``date`` for the commit date
741 741 """
742 742 # i18n: "sort" is a keyword
743 743 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
744 744 keys = "rev"
745 745 if len(l) == 2:
746 746 keys = getstring(l[1], _("sort spec must be a string"))
747 747
748 748 s = l[0]
749 749 keys = keys.split()
750 750 l = []
751 751 def invert(s):
752 752 return "".join(chr(255 - ord(c)) for c in s)
753 753 for r in getset(repo, subset, s):
754 754 c = repo[r]
755 755 e = []
756 756 for k in keys:
757 757 if k == 'rev':
758 758 e.append(r)
759 759 elif k == '-rev':
760 760 e.append(-r)
761 761 elif k == 'branch':
762 762 e.append(c.branch())
763 763 elif k == '-branch':
764 764 e.append(invert(c.branch()))
765 765 elif k == 'desc':
766 766 e.append(c.description())
767 767 elif k == '-desc':
768 768 e.append(invert(c.description()))
769 769 elif k in 'user author':
770 770 e.append(c.user())
771 771 elif k in '-user -author':
772 772 e.append(invert(c.user()))
773 773 elif k == 'date':
774 774 e.append(c.date()[0])
775 775 elif k == '-date':
776 776 e.append(-c.date()[0])
777 777 else:
778 778 raise error.ParseError(_("unknown sort key %r") % k)
779 779 e.append(r)
780 780 l.append(e)
781 781 l.sort()
782 782 return [e[-1] for e in l]
783 783
784 784 def tag(repo, subset, x):
785 785 """``tag([name])``
786 786 The specified tag by name, or all tagged revisions if no name is given.
787 787 """
788 788 # i18n: "tag" is a keyword
789 789 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
790 790 cl = repo.changelog
791 791 if args:
792 792 tn = getstring(args[0],
793 793 # i18n: "tag" is a keyword
794 794 _('the argument to tag must be a string'))
795 795 if not repo.tags().get(tn, None):
796 796 raise util.Abort(_("tag '%s' does not exist") % tn)
797 797 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
798 798 else:
799 799 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
800 800 return [r for r in subset if r in s]
801 801
802 802 def tagged(repo, subset, x):
803 803 return tag(repo, subset, x)
804 804
805 805 def user(repo, subset, x):
806 806 """``user(string)``
807 807 User name contains string. The match is case-insensitive.
808 808 """
809 809 return author(repo, subset, x)
810 810
811 811 symbols = {
812 812 "adds": adds,
813 813 "all": getall,
814 814 "ancestor": ancestor,
815 815 "ancestors": ancestors,
816 816 "author": author,
817 817 "bisected": bisected,
818 818 "bookmark": bookmark,
819 819 "branch": branch,
820 820 "children": children,
821 821 "closed": closed,
822 822 "contains": contains,
823 823 "date": date,
824 824 "descendants": descendants,
825 825 "file": hasfile,
826 826 "filelog": filelog,
827 827 "follow": follow,
828 828 "grep": grep,
829 829 "head": head,
830 830 "heads": heads,
831 "id": node,
831 832 "keyword": keyword,
832 833 "last": last,
833 834 "limit": limit,
834 835 "max": maxrev,
836 "merge": merge,
835 837 "min": minrev,
836 "merge": merge,
837 838 "modifies": modifies,
838 "id": node,
839 839 "outgoing": outgoing,
840 840 "p1": p1,
841 841 "p2": p2,
842 842 "parents": parents,
843 843 "present": present,
844 844 "removes": removes,
845 "rev": rev,
845 846 "reverse": reverse,
846 "rev": rev,
847 847 "roots": roots,
848 848 "sort": sort,
849 849 "tag": tag,
850 850 "tagged": tagged,
851 851 "user": user,
852 852 }
853 853
854 854 methods = {
855 855 "range": rangeset,
856 856 "string": stringset,
857 857 "symbol": symbolset,
858 858 "and": andset,
859 859 "or": orset,
860 860 "not": notset,
861 861 "list": listset,
862 862 "func": func,
863 863 "ancestor": ancestorspec,
864 864 "parent": parentspec,
865 865 "parentpost": p1,
866 866 }
867 867
868 868 def optimize(x, small):
869 869 if x is None:
870 870 return 0, x
871 871
872 872 smallbonus = 1
873 873 if small:
874 874 smallbonus = .5
875 875
876 876 op = x[0]
877 877 if op == 'minus':
878 878 return optimize(('and', x[1], ('not', x[2])), small)
879 879 elif op == 'dagrange':
880 880 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
881 881 ('func', ('symbol', 'ancestors'), x[2])), small)
882 882 elif op == 'dagrangepre':
883 883 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
884 884 elif op == 'dagrangepost':
885 885 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
886 886 elif op == 'rangepre':
887 887 return optimize(('range', ('string', '0'), x[1]), small)
888 888 elif op == 'rangepost':
889 889 return optimize(('range', x[1], ('string', 'tip')), small)
890 890 elif op == 'negate':
891 891 return optimize(('string',
892 892 '-' + getstring(x[1], _("can't negate that"))), small)
893 893 elif op in 'string symbol negate':
894 894 return smallbonus, x # single revisions are small
895 895 elif op == 'and' or op == 'dagrange':
896 896 wa, ta = optimize(x[1], True)
897 897 wb, tb = optimize(x[2], True)
898 898 w = min(wa, wb)
899 899 if wa > wb:
900 900 return w, (op, tb, ta)
901 901 return w, (op, ta, tb)
902 902 elif op == 'or':
903 903 wa, ta = optimize(x[1], False)
904 904 wb, tb = optimize(x[2], False)
905 905 if wb < wa:
906 906 wb, wa = wa, wb
907 907 return max(wa, wb), (op, ta, tb)
908 908 elif op == 'not':
909 909 o = optimize(x[1], not small)
910 910 return o[0], (op, o[1])
911 911 elif op == 'parentpost':
912 912 o = optimize(x[1], small)
913 913 return o[0], (op, o[1])
914 914 elif op == 'group':
915 915 return optimize(x[1], small)
916 916 elif op in 'range list parent ancestorspec':
917 917 wa, ta = optimize(x[1], small)
918 918 wb, tb = optimize(x[2], small)
919 919 return wa + wb, (op, ta, tb)
920 920 elif op == 'func':
921 921 f = getstring(x[1], _("not a symbol"))
922 922 wa, ta = optimize(x[2], small)
923 923 if f in "grep date user author keyword branch file outgoing closed":
924 924 w = 10 # slow
925 925 elif f in "modifies adds removes":
926 926 w = 30 # slower
927 927 elif f == "contains":
928 928 w = 100 # very slow
929 929 elif f == "ancestor":
930 930 w = 1 * smallbonus
931 931 elif f in "reverse limit":
932 932 w = 0
933 933 elif f in "sort":
934 934 w = 10 # assume most sorts look at changelog
935 935 else:
936 936 w = 1
937 937 return w + wa, (op, x[1], ta)
938 938 return 1, x
939 939
940 940 class revsetalias(object):
941 941 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
942 942 args = ()
943 943
944 944 def __init__(self, token, value):
945 945 '''Aliases like:
946 946
947 947 h = heads(default)
948 948 b($1) = ancestors($1) - ancestors(default)
949 949 '''
950 950 if isinstance(token, tuple):
951 951 self.type, self.name = token
952 952 else:
953 953 m = self.funcre.search(token)
954 954 if m:
955 955 self.type = 'func'
956 956 self.name = m.group(1)
957 957 self.args = [x.strip() for x in m.group(2).split(',')]
958 958 else:
959 959 self.type = 'symbol'
960 960 self.name = token
961 961
962 962 if isinstance(value, str):
963 963 for arg in self.args:
964 964 value = value.replace(arg, repr(arg))
965 965 self.replacement, pos = parse(value)
966 966 if pos != len(value):
967 967 raise error.ParseError('invalid token', pos)
968 968 else:
969 969 self.replacement = value
970 970
971 971 def match(self, tree):
972 972 if not tree:
973 973 return False
974 974 if tree == (self.type, self.name):
975 975 return True
976 976 if tree[0] != self.type:
977 977 return False
978 978 if len(tree) > 1 and tree[1] != ('symbol', self.name):
979 979 return False
980 980 # 'func' + funcname + args
981 981 if ((self.args and len(tree) != 3) or
982 982 (len(self.args) == 1 and tree[2][0] == 'list') or
983 983 (len(self.args) > 1 and (tree[2][0] != 'list' or
984 984 len(tree[2]) - 1 != len(self.args)))):
985 985 raise error.ParseError('invalid amount of arguments', len(tree) - 2)
986 986 return True
987 987
988 988 def replace(self, tree):
989 989 if tree == (self.type, self.name):
990 990 return self.replacement
991 991 result = self.replacement
992 992 def getsubtree(i):
993 993 if tree[2][0] == 'list':
994 994 return tree[2][i + 1]
995 995 return tree[i + 2]
996 996 for i, v in enumerate(self.args):
997 997 valalias = revsetalias(('string', v), getsubtree(i))
998 998 result = valalias.process(result)
999 999 return result
1000 1000
1001 1001 def process(self, tree):
1002 1002 if self.match(tree):
1003 1003 return self.replace(tree)
1004 1004 if isinstance(tree, tuple):
1005 1005 return tuple(map(self.process, tree))
1006 1006 return tree
1007 1007
1008 1008 def findaliases(ui, tree):
1009 1009 for k, v in ui.configitems('revsetalias'):
1010 1010 alias = revsetalias(k, v)
1011 1011 tree = alias.process(tree)
1012 1012 return tree
1013 1013
1014 1014 parse = parser.parser(tokenize, elements).parse
1015 1015
1016 1016 def match(ui, spec):
1017 1017 if not spec:
1018 1018 raise error.ParseError(_("empty query"))
1019 1019 tree, pos = parse(spec)
1020 1020 if (pos != len(spec)):
1021 1021 raise error.ParseError("invalid token", pos)
1022 1022 tree = findaliases(ui, tree)
1023 1023 weight, tree = optimize(tree, True)
1024 1024 def mfunc(repo, subset):
1025 1025 return getset(repo, subset, tree)
1026 1026 return mfunc
1027 1027
1028 1028 # tell hggettext to extract docstrings from these functions:
1029 1029 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now