##// END OF EJS Templates
revset: optimize building large lists in formatrevspec...
Matt Mackall -
r15898:6902e13d default
parent child Browse files
Show More
@@ -1,1163 +1,1182 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 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 = matchmod.match(repo.root, repo.getcwd(), [pat])
300 300 s = []
301 301 fast = (m.files() == [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 for f in c.files():
309 309 if m(f):
310 310 break
311 311 else:
312 312 continue
313 313 files = repo.status(c.p1().node(), c.node())[field]
314 314 if fast:
315 315 if pat in files:
316 316 s.append(r)
317 317 else:
318 318 for f in files:
319 319 if m(f):
320 320 s.append(r)
321 321 break
322 322 return s
323 323
324 324 def children(repo, subset, x):
325 325 """``children(set)``
326 326 Child changesets of changesets in set.
327 327 """
328 328 cs = set()
329 329 cl = repo.changelog
330 330 s = set(getset(repo, range(len(repo)), x))
331 331 for r in xrange(0, len(repo)):
332 332 for p in cl.parentrevs(r):
333 333 if p in s:
334 334 cs.add(r)
335 335 return [r for r in subset if r in cs]
336 336
337 337 def closed(repo, subset, x):
338 338 """``closed()``
339 339 Changeset is closed.
340 340 """
341 341 # i18n: "closed" is a keyword
342 342 getargs(x, 0, 0, _("closed takes no arguments"))
343 343 return [r for r in subset if repo[r].extra().get('close')]
344 344
345 345 def contains(repo, subset, x):
346 346 """``contains(pattern)``
347 347 Revision contains a file matching pattern. See :hg:`help patterns`
348 348 for information about file patterns.
349 349 """
350 350 # i18n: "contains" is a keyword
351 351 pat = getstring(x, _("contains requires a pattern"))
352 352 m = matchmod.match(repo.root, repo.getcwd(), [pat])
353 353 s = []
354 354 if m.files() == [pat]:
355 355 for r in subset:
356 356 if pat in repo[r]:
357 357 s.append(r)
358 358 else:
359 359 for r in subset:
360 360 for f in repo[r].manifest():
361 361 if m(f):
362 362 s.append(r)
363 363 break
364 364 return s
365 365
366 366 def date(repo, subset, x):
367 367 """``date(interval)``
368 368 Changesets within the interval, see :hg:`help dates`.
369 369 """
370 370 # i18n: "date" is a keyword
371 371 ds = getstring(x, _("date requires a string"))
372 372 dm = util.matchdate(ds)
373 373 return [r for r in subset if dm(repo[r].date()[0])]
374 374
375 375 def desc(repo, subset, x):
376 376 """``desc(string)``
377 377 Search commit message for string. The match is case-insensitive.
378 378 """
379 379 # i18n: "desc" is a keyword
380 380 ds = encoding.lower(getstring(x, _("desc requires a string")))
381 381 l = []
382 382 for r in subset:
383 383 c = repo[r]
384 384 if ds in encoding.lower(c.description()):
385 385 l.append(r)
386 386 return l
387 387
388 388 def descendants(repo, subset, x):
389 389 """``descendants(set)``
390 390 Changesets which are descendants of changesets in set.
391 391 """
392 392 args = getset(repo, range(len(repo)), x)
393 393 if not args:
394 394 return []
395 395 s = set(repo.changelog.descendants(*args)) | set(args)
396 396 return [r for r in subset if r in s]
397 397
398 398 def draft(repo, subset, x):
399 399 """``draft()``
400 400 Changeset in draft phase."""
401 401 getargs(x, 0, 0, _("draft takes no arguments"))
402 402 return [r for r in subset if repo._phaserev[r] == phases.draft]
403 403
404 404 def filelog(repo, subset, x):
405 405 """``filelog(pattern)``
406 406 Changesets connected to the specified filelog.
407 407 """
408 408
409 409 pat = getstring(x, _("filelog requires a pattern"))
410 410 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
411 411 s = set()
412 412
413 413 if not m.anypats():
414 414 for f in m.files():
415 415 fl = repo.file(f)
416 416 for fr in fl:
417 417 s.add(fl.linkrev(fr))
418 418 else:
419 419 for f in repo[None]:
420 420 if m(f):
421 421 fl = repo.file(f)
422 422 for fr in fl:
423 423 s.add(fl.linkrev(fr))
424 424
425 425 return [r for r in subset if r in s]
426 426
427 427 def first(repo, subset, x):
428 428 """``first(set, [n])``
429 429 An alias for limit().
430 430 """
431 431 return limit(repo, subset, x)
432 432
433 433 def follow(repo, subset, x):
434 434 """``follow([file])``
435 435 An alias for ``::.`` (ancestors of the working copy's first parent).
436 436 If a filename is specified, the history of the given file is followed,
437 437 including copies.
438 438 """
439 439 # i18n: "follow" is a keyword
440 440 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
441 441 p = repo['.'].rev()
442 442 if l:
443 443 x = getstring(l[0], _("follow expected a filename"))
444 444 if x in repo['.']:
445 445 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
446 446 else:
447 447 return []
448 448 else:
449 449 s = set(repo.changelog.ancestors(p))
450 450
451 451 s |= set([p])
452 452 return [r for r in subset if r in s]
453 453
454 454 def followfile(repo, subset, x):
455 455 """``follow()``
456 456 An alias for ``::.`` (ancestors of the working copy's first parent).
457 457 """
458 458 # i18n: "follow" is a keyword
459 459 getargs(x, 0, 0, _("follow takes no arguments"))
460 460 p = repo['.'].rev()
461 461 s = set(repo.changelog.ancestors(p)) | set([p])
462 462 return [r for r in subset if r in s]
463 463
464 464 def getall(repo, subset, x):
465 465 """``all()``
466 466 All changesets, the same as ``0:tip``.
467 467 """
468 468 # i18n: "all" is a keyword
469 469 getargs(x, 0, 0, _("all takes no arguments"))
470 470 return subset
471 471
472 472 def grep(repo, subset, x):
473 473 """``grep(regex)``
474 474 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
475 475 to ensure special escape characters are handled correctly. Unlike
476 476 ``keyword(string)``, the match is case-sensitive.
477 477 """
478 478 try:
479 479 # i18n: "grep" is a keyword
480 480 gr = re.compile(getstring(x, _("grep requires a string")))
481 481 except re.error, e:
482 482 raise error.ParseError(_('invalid match pattern: %s') % e)
483 483 l = []
484 484 for r in subset:
485 485 c = repo[r]
486 486 for e in c.files() + [c.user(), c.description()]:
487 487 if gr.search(e):
488 488 l.append(r)
489 489 break
490 490 return l
491 491
492 492 def hasfile(repo, subset, x):
493 493 """``file(pattern)``
494 494 Changesets affecting files matched by pattern.
495 495 """
496 496 # i18n: "file" is a keyword
497 497 pat = getstring(x, _("file requires a pattern"))
498 498 m = matchmod.match(repo.root, repo.getcwd(), [pat])
499 499 s = []
500 500 for r in subset:
501 501 for f in repo[r].files():
502 502 if m(f):
503 503 s.append(r)
504 504 break
505 505 return s
506 506
507 507 def head(repo, subset, x):
508 508 """``head()``
509 509 Changeset is a named branch head.
510 510 """
511 511 # i18n: "head" is a keyword
512 512 getargs(x, 0, 0, _("head takes no arguments"))
513 513 hs = set()
514 514 for b, ls in repo.branchmap().iteritems():
515 515 hs.update(repo[h].rev() for h in ls)
516 516 return [r for r in subset if r in hs]
517 517
518 518 def heads(repo, subset, x):
519 519 """``heads(set)``
520 520 Members of set with no children in set.
521 521 """
522 522 s = getset(repo, subset, x)
523 523 ps = set(parents(repo, subset, x))
524 524 return [r for r in s if r not in ps]
525 525
526 526 def keyword(repo, subset, x):
527 527 """``keyword(string)``
528 528 Search commit message, user name, and names of changed files for
529 529 string. The match is case-insensitive.
530 530 """
531 531 # i18n: "keyword" is a keyword
532 532 kw = encoding.lower(getstring(x, _("keyword requires a string")))
533 533 l = []
534 534 for r in subset:
535 535 c = repo[r]
536 536 t = " ".join(c.files() + [c.user(), c.description()])
537 537 if kw in encoding.lower(t):
538 538 l.append(r)
539 539 return l
540 540
541 541 def limit(repo, subset, x):
542 542 """``limit(set, [n])``
543 543 First n members of set, defaulting to 1.
544 544 """
545 545 # i18n: "limit" is a keyword
546 546 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
547 547 try:
548 548 lim = 1
549 549 if len(l) == 2:
550 550 # i18n: "limit" is a keyword
551 551 lim = int(getstring(l[1], _("limit requires a number")))
552 552 except (TypeError, ValueError):
553 553 # i18n: "limit" is a keyword
554 554 raise error.ParseError(_("limit expects a number"))
555 555 ss = set(subset)
556 556 os = getset(repo, range(len(repo)), l[0])[:lim]
557 557 return [r for r in os if r in ss]
558 558
559 559 def last(repo, subset, x):
560 560 """``last(set, [n])``
561 561 Last n members of set, defaulting to 1.
562 562 """
563 563 # i18n: "last" is a keyword
564 564 l = getargs(x, 1, 2, _("last requires one or two arguments"))
565 565 try:
566 566 lim = 1
567 567 if len(l) == 2:
568 568 # i18n: "last" is a keyword
569 569 lim = int(getstring(l[1], _("last requires a number")))
570 570 except (TypeError, ValueError):
571 571 # i18n: "last" is a keyword
572 572 raise error.ParseError(_("last expects a number"))
573 573 ss = set(subset)
574 574 os = getset(repo, range(len(repo)), l[0])[-lim:]
575 575 return [r for r in os if r in ss]
576 576
577 577 def maxrev(repo, subset, x):
578 578 """``max(set)``
579 579 Changeset with highest revision number in set.
580 580 """
581 581 os = getset(repo, range(len(repo)), x)
582 582 if os:
583 583 m = max(os)
584 584 if m in subset:
585 585 return [m]
586 586 return []
587 587
588 588 def merge(repo, subset, x):
589 589 """``merge()``
590 590 Changeset is a merge changeset.
591 591 """
592 592 # i18n: "merge" is a keyword
593 593 getargs(x, 0, 0, _("merge takes no arguments"))
594 594 cl = repo.changelog
595 595 return [r for r in subset if cl.parentrevs(r)[1] != -1]
596 596
597 597 def minrev(repo, subset, x):
598 598 """``min(set)``
599 599 Changeset with lowest revision number in set.
600 600 """
601 601 os = getset(repo, range(len(repo)), x)
602 602 if os:
603 603 m = min(os)
604 604 if m in subset:
605 605 return [m]
606 606 return []
607 607
608 608 def modifies(repo, subset, x):
609 609 """``modifies(pattern)``
610 610 Changesets modifying files matched by pattern.
611 611 """
612 612 # i18n: "modifies" is a keyword
613 613 pat = getstring(x, _("modifies requires a pattern"))
614 614 return checkstatus(repo, subset, pat, 0)
615 615
616 616 def node(repo, subset, x):
617 617 """``id(string)``
618 618 Revision non-ambiguously specified by the given hex string prefix.
619 619 """
620 620 # i18n: "id" is a keyword
621 621 l = getargs(x, 1, 1, _("id requires one argument"))
622 622 # i18n: "id" is a keyword
623 623 n = getstring(l[0], _("id requires a string"))
624 624 if len(n) == 40:
625 625 rn = repo[n].rev()
626 626 else:
627 627 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
628 628 return [r for r in subset if r == rn]
629 629
630 630 def outgoing(repo, subset, x):
631 631 """``outgoing([path])``
632 632 Changesets not found in the specified destination repository, or the
633 633 default push location.
634 634 """
635 635 import hg # avoid start-up nasties
636 636 # i18n: "outgoing" is a keyword
637 637 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
638 638 # i18n: "outgoing" is a keyword
639 639 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
640 640 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
641 641 dest, branches = hg.parseurl(dest)
642 642 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
643 643 if revs:
644 644 revs = [repo.lookup(rev) for rev in revs]
645 645 other = hg.peer(repo, {}, dest)
646 646 repo.ui.pushbuffer()
647 647 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
648 648 repo.ui.popbuffer()
649 649 cl = repo.changelog
650 650 o = set([cl.rev(r) for r in outgoing.missing])
651 651 return [r for r in subset if r in o]
652 652
653 653 def p1(repo, subset, x):
654 654 """``p1([set])``
655 655 First parent of changesets in set, or the working directory.
656 656 """
657 657 if x is None:
658 658 p = repo[x].p1().rev()
659 659 return [r for r in subset if r == p]
660 660
661 661 ps = set()
662 662 cl = repo.changelog
663 663 for r in getset(repo, range(len(repo)), x):
664 664 ps.add(cl.parentrevs(r)[0])
665 665 return [r for r in subset if r in ps]
666 666
667 667 def p2(repo, subset, x):
668 668 """``p2([set])``
669 669 Second parent of changesets in set, or the working directory.
670 670 """
671 671 if x is None:
672 672 ps = repo[x].parents()
673 673 try:
674 674 p = ps[1].rev()
675 675 return [r for r in subset if r == p]
676 676 except IndexError:
677 677 return []
678 678
679 679 ps = set()
680 680 cl = repo.changelog
681 681 for r in getset(repo, range(len(repo)), x):
682 682 ps.add(cl.parentrevs(r)[1])
683 683 return [r for r in subset if r in ps]
684 684
685 685 def parents(repo, subset, x):
686 686 """``parents([set])``
687 687 The set of all parents for all changesets in set, or the working directory.
688 688 """
689 689 if x is None:
690 690 ps = tuple(p.rev() for p in repo[x].parents())
691 691 return [r for r in subset if r in ps]
692 692
693 693 ps = set()
694 694 cl = repo.changelog
695 695 for r in getset(repo, range(len(repo)), x):
696 696 ps.update(cl.parentrevs(r))
697 697 return [r for r in subset if r in ps]
698 698
699 699 def parentspec(repo, subset, x, n):
700 700 """``set^0``
701 701 The set.
702 702 ``set^1`` (or ``set^``), ``set^2``
703 703 First or second parent, respectively, of all changesets in set.
704 704 """
705 705 try:
706 706 n = int(n[1])
707 707 if n not in (0, 1, 2):
708 708 raise ValueError
709 709 except (TypeError, ValueError):
710 710 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
711 711 ps = set()
712 712 cl = repo.changelog
713 713 for r in getset(repo, subset, x):
714 714 if n == 0:
715 715 ps.add(r)
716 716 elif n == 1:
717 717 ps.add(cl.parentrevs(r)[0])
718 718 elif n == 2:
719 719 parents = cl.parentrevs(r)
720 720 if len(parents) > 1:
721 721 ps.add(parents[1])
722 722 return [r for r in subset if r in ps]
723 723
724 724 def present(repo, subset, x):
725 725 """``present(set)``
726 726 An empty set, if any revision in set isn't found; otherwise,
727 727 all revisions in set.
728 728 """
729 729 try:
730 730 return getset(repo, subset, x)
731 731 except error.RepoLookupError:
732 732 return []
733 733
734 734 def public(repo, subset, x):
735 735 """``public()``
736 736 Changeset in public phase."""
737 737 getargs(x, 0, 0, _("public takes no arguments"))
738 738 return [r for r in subset if repo._phaserev[r] == phases.public]
739 739
740 740 def removes(repo, subset, x):
741 741 """``removes(pattern)``
742 742 Changesets which remove files matching pattern.
743 743 """
744 744 # i18n: "removes" is a keyword
745 745 pat = getstring(x, _("removes requires a pattern"))
746 746 return checkstatus(repo, subset, pat, 2)
747 747
748 748 def rev(repo, subset, x):
749 749 """``rev(number)``
750 750 Revision with the given numeric identifier.
751 751 """
752 752 # i18n: "rev" is a keyword
753 753 l = getargs(x, 1, 1, _("rev requires one argument"))
754 754 try:
755 755 # i18n: "rev" is a keyword
756 756 l = int(getstring(l[0], _("rev requires a number")))
757 757 except (TypeError, ValueError):
758 758 # i18n: "rev" is a keyword
759 759 raise error.ParseError(_("rev expects a number"))
760 760 return [r for r in subset if r == l]
761 761
762 762 def reverse(repo, subset, x):
763 763 """``reverse(set)``
764 764 Reverse order of set.
765 765 """
766 766 l = getset(repo, subset, x)
767 767 l.reverse()
768 768 return l
769 769
770 770 def roots(repo, subset, x):
771 771 """``roots(set)``
772 772 Changesets with no parent changeset in set.
773 773 """
774 774 s = getset(repo, subset, x)
775 775 cs = set(children(repo, subset, x))
776 776 return [r for r in s if r not in cs]
777 777
778 778 def secret(repo, subset, x):
779 779 """``secret()``
780 780 Changeset in secret phase."""
781 781 getargs(x, 0, 0, _("secret takes no arguments"))
782 782 return [r for r in subset if repo._phaserev[r] == phases.secret]
783 783
784 784 def sort(repo, subset, x):
785 785 """``sort(set[, [-]key...])``
786 786 Sort set by keys. The default sort order is ascending, specify a key
787 787 as ``-key`` to sort in descending order.
788 788
789 789 The keys can be:
790 790
791 791 - ``rev`` for the revision number,
792 792 - ``branch`` for the branch name,
793 793 - ``desc`` for the commit message (description),
794 794 - ``user`` for user name (``author`` can be used as an alias),
795 795 - ``date`` for the commit date
796 796 """
797 797 # i18n: "sort" is a keyword
798 798 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
799 799 keys = "rev"
800 800 if len(l) == 2:
801 801 keys = getstring(l[1], _("sort spec must be a string"))
802 802
803 803 s = l[0]
804 804 keys = keys.split()
805 805 l = []
806 806 def invert(s):
807 807 return "".join(chr(255 - ord(c)) for c in s)
808 808 for r in getset(repo, subset, s):
809 809 c = repo[r]
810 810 e = []
811 811 for k in keys:
812 812 if k == 'rev':
813 813 e.append(r)
814 814 elif k == '-rev':
815 815 e.append(-r)
816 816 elif k == 'branch':
817 817 e.append(c.branch())
818 818 elif k == '-branch':
819 819 e.append(invert(c.branch()))
820 820 elif k == 'desc':
821 821 e.append(c.description())
822 822 elif k == '-desc':
823 823 e.append(invert(c.description()))
824 824 elif k in 'user author':
825 825 e.append(c.user())
826 826 elif k in '-user -author':
827 827 e.append(invert(c.user()))
828 828 elif k == 'date':
829 829 e.append(c.date()[0])
830 830 elif k == '-date':
831 831 e.append(-c.date()[0])
832 832 else:
833 833 raise error.ParseError(_("unknown sort key %r") % k)
834 834 e.append(r)
835 835 l.append(e)
836 836 l.sort()
837 837 return [e[-1] for e in l]
838 838
839 839 def tag(repo, subset, x):
840 840 """``tag([name])``
841 841 The specified tag by name, or all tagged revisions if no name is given.
842 842 """
843 843 # i18n: "tag" is a keyword
844 844 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
845 845 cl = repo.changelog
846 846 if args:
847 847 tn = getstring(args[0],
848 848 # i18n: "tag" is a keyword
849 849 _('the argument to tag must be a string'))
850 850 if not repo.tags().get(tn, None):
851 851 raise util.Abort(_("tag '%s' does not exist") % tn)
852 852 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
853 853 else:
854 854 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
855 855 return [r for r in subset if r in s]
856 856
857 857 def tagged(repo, subset, x):
858 858 return tag(repo, subset, x)
859 859
860 860 def user(repo, subset, x):
861 861 """``user(string)``
862 862 User name contains string. The match is case-insensitive.
863 863 """
864 864 return author(repo, subset, x)
865 865
866 # for internal use
867 def _list(repo, subset, x):
868 s = getstring(x, "internal error")
869 if not s:
870 return []
871 if not isinstance(subset, set):
872 subset = set(subset)
873 ls = [repo[r].rev() for r in s.split('\0')]
874 return [r for r in ls if r in subset]
875
866 876 symbols = {
867 877 "adds": adds,
868 878 "all": getall,
869 879 "ancestor": ancestor,
870 880 "ancestors": ancestors,
871 881 "author": author,
872 882 "bisect": bisect,
873 883 "bisected": bisected,
874 884 "bookmark": bookmark,
875 885 "branch": branch,
876 886 "children": children,
877 887 "closed": closed,
878 888 "contains": contains,
879 889 "date": date,
880 890 "desc": desc,
881 891 "descendants": descendants,
882 892 "draft": draft,
883 893 "file": hasfile,
884 894 "filelog": filelog,
885 895 "first": first,
886 896 "follow": follow,
887 897 "grep": grep,
888 898 "head": head,
889 899 "heads": heads,
890 900 "id": node,
891 901 "keyword": keyword,
892 902 "last": last,
893 903 "limit": limit,
894 904 "max": maxrev,
895 905 "merge": merge,
896 906 "min": minrev,
897 907 "modifies": modifies,
898 908 "outgoing": outgoing,
899 909 "p1": p1,
900 910 "p2": p2,
901 911 "parents": parents,
902 912 "present": present,
903 913 "public": public,
904 914 "removes": removes,
905 915 "rev": rev,
906 916 "reverse": reverse,
907 917 "roots": roots,
908 918 "sort": sort,
909 919 "secret": secret,
910 920 "tag": tag,
911 921 "tagged": tagged,
912 922 "user": user,
923 "_list": _list,
913 924 }
914 925
915 926 methods = {
916 927 "range": rangeset,
917 928 "string": stringset,
918 929 "symbol": symbolset,
919 930 "and": andset,
920 931 "or": orset,
921 932 "not": notset,
922 933 "list": listset,
923 934 "func": func,
924 935 "ancestor": ancestorspec,
925 936 "parent": parentspec,
926 937 "parentpost": p1,
927 938 }
928 939
929 940 def optimize(x, small):
930 941 if x is None:
931 942 return 0, x
932 943
933 944 smallbonus = 1
934 945 if small:
935 946 smallbonus = .5
936 947
937 948 op = x[0]
938 949 if op == 'minus':
939 950 return optimize(('and', x[1], ('not', x[2])), small)
940 951 elif op == 'dagrange':
941 952 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
942 953 ('func', ('symbol', 'ancestors'), x[2])), small)
943 954 elif op == 'dagrangepre':
944 955 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
945 956 elif op == 'dagrangepost':
946 957 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
947 958 elif op == 'rangepre':
948 959 return optimize(('range', ('string', '0'), x[1]), small)
949 960 elif op == 'rangepost':
950 961 return optimize(('range', x[1], ('string', 'tip')), small)
951 962 elif op == 'negate':
952 963 return optimize(('string',
953 964 '-' + getstring(x[1], _("can't negate that"))), small)
954 965 elif op in 'string symbol negate':
955 966 return smallbonus, x # single revisions are small
956 967 elif op == 'and' or op == 'dagrange':
957 968 wa, ta = optimize(x[1], True)
958 969 wb, tb = optimize(x[2], True)
959 970 w = min(wa, wb)
960 971 if wa > wb:
961 972 return w, (op, tb, ta)
962 973 return w, (op, ta, tb)
963 974 elif op == 'or':
964 975 wa, ta = optimize(x[1], False)
965 976 wb, tb = optimize(x[2], False)
966 977 if wb < wa:
967 978 wb, wa = wa, wb
968 979 return max(wa, wb), (op, ta, tb)
969 980 elif op == 'not':
970 981 o = optimize(x[1], not small)
971 982 return o[0], (op, o[1])
972 983 elif op == 'parentpost':
973 984 o = optimize(x[1], small)
974 985 return o[0], (op, o[1])
975 986 elif op == 'group':
976 987 return optimize(x[1], small)
977 988 elif op in 'range list parent ancestorspec':
978 989 if op == 'parent':
979 990 # x^:y means (x^) : y, not x ^ (:y)
980 991 post = ('parentpost', x[1])
981 992 if x[2][0] == 'dagrangepre':
982 993 return optimize(('dagrange', post, x[2][1]), small)
983 994 elif x[2][0] == 'rangepre':
984 995 return optimize(('range', post, x[2][1]), small)
985 996
986 997 wa, ta = optimize(x[1], small)
987 998 wb, tb = optimize(x[2], small)
988 999 return wa + wb, (op, ta, tb)
989 1000 elif op == 'func':
990 1001 f = getstring(x[1], _("not a symbol"))
991 1002 wa, ta = optimize(x[2], small)
992 1003 if f in ("author branch closed date desc file grep keyword "
993 1004 "outgoing user"):
994 1005 w = 10 # slow
995 1006 elif f in "modifies adds removes":
996 1007 w = 30 # slower
997 1008 elif f == "contains":
998 1009 w = 100 # very slow
999 1010 elif f == "ancestor":
1000 1011 w = 1 * smallbonus
1001 1012 elif f in "reverse limit first":
1002 1013 w = 0
1003 1014 elif f in "sort":
1004 1015 w = 10 # assume most sorts look at changelog
1005 1016 else:
1006 1017 w = 1
1007 1018 return w + wa, (op, x[1], ta)
1008 1019 return 1, x
1009 1020
1010 1021 class revsetalias(object):
1011 1022 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1012 1023 args = None
1013 1024
1014 1025 def __init__(self, name, value):
1015 1026 '''Aliases like:
1016 1027
1017 1028 h = heads(default)
1018 1029 b($1) = ancestors($1) - ancestors(default)
1019 1030 '''
1020 1031 if isinstance(name, tuple): # parameter substitution
1021 1032 self.tree = name
1022 1033 self.replacement = value
1023 1034 else: # alias definition
1024 1035 m = self.funcre.search(name)
1025 1036 if m:
1026 1037 self.tree = ('func', ('symbol', m.group(1)))
1027 1038 self.args = [x.strip() for x in m.group(2).split(',')]
1028 1039 for arg in self.args:
1029 1040 value = value.replace(arg, repr(arg))
1030 1041 else:
1031 1042 self.tree = ('symbol', name)
1032 1043
1033 1044 self.replacement, pos = parse(value)
1034 1045 if pos != len(value):
1035 1046 raise error.ParseError(_('invalid token'), pos)
1036 1047
1037 1048 def process(self, tree):
1038 1049 if isinstance(tree, tuple):
1039 1050 if self.args is None:
1040 1051 if tree == self.tree:
1041 1052 return self.replacement
1042 1053 elif tree[:2] == self.tree:
1043 1054 l = getlist(tree[2])
1044 1055 if len(l) != len(self.args):
1045 1056 raise error.ParseError(
1046 1057 _('invalid number of arguments: %s') % len(l))
1047 1058 result = self.replacement
1048 1059 for a, v in zip(self.args, l):
1049 1060 valalias = revsetalias(('string', a), v)
1050 1061 result = valalias.process(result)
1051 1062 return result
1052 1063 return tuple(map(self.process, tree))
1053 1064 return tree
1054 1065
1055 1066 def findaliases(ui, tree):
1056 1067 for k, v in ui.configitems('revsetalias'):
1057 1068 alias = revsetalias(k, v)
1058 1069 tree = alias.process(tree)
1059 1070 return tree
1060 1071
1061 1072 parse = parser.parser(tokenize, elements).parse
1062 1073
1063 1074 def match(ui, spec):
1064 1075 if not spec:
1065 1076 raise error.ParseError(_("empty query"))
1066 1077 tree, pos = parse(spec)
1067 1078 if (pos != len(spec)):
1068 1079 raise error.ParseError(_("invalid token"), pos)
1069 1080 if ui:
1070 1081 tree = findaliases(ui, tree)
1071 1082 weight, tree = optimize(tree, True)
1072 1083 def mfunc(repo, subset):
1073 1084 return getset(repo, subset, tree)
1074 1085 return mfunc
1075 1086
1076 1087 def formatspec(expr, *args):
1077 1088 '''
1078 1089 This is a convenience function for using revsets internally, and
1079 1090 escapes arguments appropriately. Aliases are intentionally ignored
1080 1091 so that intended expression behavior isn't accidentally subverted.
1081 1092
1082 1093 Supported arguments:
1083 1094
1084 1095 %r = revset expression, parenthesized
1085 1096 %d = int(arg), no quoting
1086 1097 %s = string(arg), escaped and single-quoted
1087 1098 %b = arg.branch(), escaped and single-quoted
1088 1099 %n = hex(arg), single-quoted
1089 1100 %% = a literal '%'
1090 1101
1091 1102 Prefixing the type with 'l' specifies a parenthesized list of that type.
1092 1103
1093 1104 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1094 1105 '(10 or 11):: and ((this()) or (that()))'
1095 1106 >>> formatspec('%d:: and not %d::', 10, 20)
1096 1107 '10:: and not 20::'
1097 1108 >>> formatspec('%ld or %ld', [], [1])
1098 '(0-0) or 1'
1109 "_list('') or 1"
1099 1110 >>> formatspec('keyword(%s)', 'foo\\xe9')
1100 1111 "keyword('foo\\\\xe9')"
1101 1112 >>> b = lambda: 'default'
1102 1113 >>> b.branch = b
1103 1114 >>> formatspec('branch(%b)', b)
1104 1115 "branch('default')"
1105 1116 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1106 "root((('a' or 'b') or ('c' or 'd')))"
1117 "root(_list('a\\x00b\\x00c\\x00d'))"
1107 1118 '''
1108 1119
1109 1120 def quote(s):
1110 1121 return repr(str(s))
1111 1122
1112 1123 def argtype(c, arg):
1113 1124 if c == 'd':
1114 1125 return str(int(arg))
1115 1126 elif c == 's':
1116 1127 return quote(arg)
1117 1128 elif c == 'r':
1118 1129 parse(arg) # make sure syntax errors are confined
1119 1130 return '(%s)' % arg
1120 1131 elif c == 'n':
1121 1132 return quote(nodemod.hex(arg))
1122 1133 elif c == 'b':
1123 1134 return quote(arg.branch())
1124 1135
1125 1136 def listexp(s, t):
1126 "balance a list s of type t to limit parse tree depth"
1127 1137 l = len(s)
1128 1138 if l == 0:
1129 return '(0-0)' # a minimal way to represent an empty set
1130 if l == 1:
1139 return "_list('')"
1140 elif l == 1:
1131 1141 return argtype(t, s[0])
1142 elif t == 'd':
1143 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1144 elif t == 's':
1145 return "_list('%s')" % "\0".join(s)
1146 elif t == 'n':
1147 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1148 elif t == 'b':
1149 return "_list('%s')" % "\0".join(a.branch() for a in s)
1150
1132 1151 m = l // 2
1133 1152 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1134 1153
1135 1154 ret = ''
1136 1155 pos = 0
1137 1156 arg = 0
1138 1157 while pos < len(expr):
1139 1158 c = expr[pos]
1140 1159 if c == '%':
1141 1160 pos += 1
1142 1161 d = expr[pos]
1143 1162 if d == '%':
1144 1163 ret += d
1145 1164 elif d in 'dsnbr':
1146 1165 ret += argtype(d, args[arg])
1147 1166 arg += 1
1148 1167 elif d == 'l':
1149 1168 # a list of some type
1150 1169 pos += 1
1151 1170 d = expr[pos]
1152 1171 ret += listexp(list(args[arg]), d)
1153 1172 arg += 1
1154 1173 else:
1155 1174 raise util.Abort('unexpected revspec format character %s' % d)
1156 1175 else:
1157 1176 ret += c
1158 1177 pos += 1
1159 1178
1160 1179 return ret
1161 1180
1162 1181 # tell hggettext to extract docstrings from these functions:
1163 1182 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now