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