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