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