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