##// END OF EJS Templates
revset: changed ancestors revset to return lazy generators...
Lucas Moscovicz -
r20690:13c0327e default
parent child Browse files
Show More
@@ -1,2471 +1,2482
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
11 import heapq
11 12 import match as matchmod
12 13 import ancestor as ancestormod
13 14 from i18n import _
14 15 import encoding
15 16 import obsolete as obsmod
16 17 import pathutil
17 18 import repoview
18 19
19 20 def _revancestors(repo, revs, followfirst):
20 21 """Like revlog.ancestors(), but supports followfirst."""
21 22 cut = followfirst and 1 or None
22 23 cl = repo.changelog
23 visit = util.deque(revs)
24
25 # Implementation to be changed in later patches based on revs order.
26 h = list(revs)
27 for i in xrange(len(h)):
28 h[i] = -h[i]
29 heapq.heapify(h)
24 30 seen = set([node.nullrev])
25 while visit:
26 for parent in cl.parentrevs(visit.popleft())[:cut]:
27 if parent not in seen:
28 visit.append(parent)
29 seen.add(parent)
30 yield parent
31 def iterate():
32 while h:
33 current = -heapq.heappop(h)
34 if current not in seen:
35 seen.add(current)
36 yield current
37 for parent in cl.parentrevs(current)[:cut]:
38 if parent != node.nullrev:
39 heapq.heappush(h, -parent)
40
41 return descgeneratorset(iterate())
31 42
32 43 def _revdescendants(repo, revs, followfirst):
33 44 """Like revlog.descendants() but supports followfirst."""
34 45 cut = followfirst and 1 or None
35 46 cl = repo.changelog
36 47 first = min(revs)
37 48 nullrev = node.nullrev
38 49 if first == nullrev:
39 50 # Are there nodes with a null first parent and a non-null
40 51 # second one? Maybe. Do we care? Probably not.
41 52 for i in cl:
42 53 yield i
43 54 return
44 55
45 56 seen = set(revs)
46 57 for i in cl.revs(first + 1):
47 58 for x in cl.parentrevs(i)[:cut]:
48 59 if x != nullrev and x in seen:
49 60 seen.add(i)
50 61 yield i
51 62 break
52 63
53 64 def _revsbetween(repo, roots, heads):
54 65 """Return all paths between roots and heads, inclusive of both endpoint
55 66 sets."""
56 67 if not roots:
57 68 return baseset([])
58 69 parentrevs = repo.changelog.parentrevs
59 70 visit = baseset(heads)
60 71 reachable = set()
61 72 seen = {}
62 73 minroot = min(roots)
63 74 roots = set(roots)
64 75 # open-code the post-order traversal due to the tiny size of
65 76 # sys.getrecursionlimit()
66 77 while visit:
67 78 rev = visit.pop()
68 79 if rev in roots:
69 80 reachable.add(rev)
70 81 parents = parentrevs(rev)
71 82 seen[rev] = parents
72 83 for parent in parents:
73 84 if parent >= minroot and parent not in seen:
74 85 visit.append(parent)
75 86 if not reachable:
76 87 return baseset([])
77 88 for rev in sorted(seen):
78 89 for parent in seen[rev]:
79 90 if parent in reachable:
80 91 reachable.add(rev)
81 92 return baseset(sorted(reachable))
82 93
83 94 elements = {
84 95 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
85 96 "~": (18, None, ("ancestor", 18)),
86 97 "^": (18, None, ("parent", 18), ("parentpost", 18)),
87 98 "-": (5, ("negate", 19), ("minus", 5)),
88 99 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
89 100 ("dagrangepost", 17)),
90 101 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
91 102 ("dagrangepost", 17)),
92 103 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
93 104 "not": (10, ("not", 10)),
94 105 "!": (10, ("not", 10)),
95 106 "and": (5, None, ("and", 5)),
96 107 "&": (5, None, ("and", 5)),
97 108 "or": (4, None, ("or", 4)),
98 109 "|": (4, None, ("or", 4)),
99 110 "+": (4, None, ("or", 4)),
100 111 ",": (2, None, ("list", 2)),
101 112 ")": (0, None, None),
102 113 "symbol": (0, ("symbol",), None),
103 114 "string": (0, ("string",), None),
104 115 "end": (0, None, None),
105 116 }
106 117
107 118 keywords = set(['and', 'or', 'not'])
108 119
109 120 def tokenize(program):
110 121 '''
111 122 Parse a revset statement into a stream of tokens
112 123
113 124 Check that @ is a valid unquoted token character (issue3686):
114 125 >>> list(tokenize("@::"))
115 126 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
116 127
117 128 '''
118 129
119 130 pos, l = 0, len(program)
120 131 while pos < l:
121 132 c = program[pos]
122 133 if c.isspace(): # skip inter-token whitespace
123 134 pass
124 135 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
125 136 yield ('::', None, pos)
126 137 pos += 1 # skip ahead
127 138 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
128 139 yield ('..', None, pos)
129 140 pos += 1 # skip ahead
130 141 elif c in "():,-|&+!~^": # handle simple operators
131 142 yield (c, None, pos)
132 143 elif (c in '"\'' or c == 'r' and
133 144 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
134 145 if c == 'r':
135 146 pos += 1
136 147 c = program[pos]
137 148 decode = lambda x: x
138 149 else:
139 150 decode = lambda x: x.decode('string-escape')
140 151 pos += 1
141 152 s = pos
142 153 while pos < l: # find closing quote
143 154 d = program[pos]
144 155 if d == '\\': # skip over escaped characters
145 156 pos += 2
146 157 continue
147 158 if d == c:
148 159 yield ('string', decode(program[s:pos]), s)
149 160 break
150 161 pos += 1
151 162 else:
152 163 raise error.ParseError(_("unterminated string"), s)
153 164 # gather up a symbol/keyword
154 165 elif c.isalnum() or c in '._@' or ord(c) > 127:
155 166 s = pos
156 167 pos += 1
157 168 while pos < l: # find end of symbol
158 169 d = program[pos]
159 170 if not (d.isalnum() or d in "._/@" or ord(d) > 127):
160 171 break
161 172 if d == '.' and program[pos - 1] == '.': # special case for ..
162 173 pos -= 1
163 174 break
164 175 pos += 1
165 176 sym = program[s:pos]
166 177 if sym in keywords: # operator keywords
167 178 yield (sym, None, s)
168 179 else:
169 180 yield ('symbol', sym, s)
170 181 pos -= 1
171 182 else:
172 183 raise error.ParseError(_("syntax error"), pos)
173 184 pos += 1
174 185 yield ('end', None, pos)
175 186
176 187 # helpers
177 188
178 189 def getstring(x, err):
179 190 if x and (x[0] == 'string' or x[0] == 'symbol'):
180 191 return x[1]
181 192 raise error.ParseError(err)
182 193
183 194 def getlist(x):
184 195 if not x:
185 196 return []
186 197 if x[0] == 'list':
187 198 return getlist(x[1]) + [x[2]]
188 199 return [x]
189 200
190 201 def getargs(x, min, max, err):
191 202 l = getlist(x)
192 203 if len(l) < min or (max >= 0 and len(l) > max):
193 204 raise error.ParseError(err)
194 205 return l
195 206
196 207 def getset(repo, subset, x):
197 208 if not x:
198 209 raise error.ParseError(_("missing argument"))
199 210 s = methods[x[0]](repo, subset, *x[1:])
200 211 if util.safehasattr(s, 'set'):
201 212 return s
202 213 return baseset(s)
203 214
204 215 def _getrevsource(repo, r):
205 216 extra = repo[r].extra()
206 217 for label in ('source', 'transplant_source', 'rebase_source'):
207 218 if label in extra:
208 219 try:
209 220 return repo[extra[label]].rev()
210 221 except error.RepoLookupError:
211 222 pass
212 223 return None
213 224
214 225 # operator methods
215 226
216 227 def stringset(repo, subset, x):
217 228 x = repo[x].rev()
218 229 if x == -1 and len(subset) == len(repo):
219 230 return baseset([-1])
220 231 if len(subset) == len(repo) or x in subset:
221 232 return baseset([x])
222 233 return baseset([])
223 234
224 235 def symbolset(repo, subset, x):
225 236 if x in symbols:
226 237 raise error.ParseError(_("can't use %s here") % x)
227 238 return stringset(repo, subset, x)
228 239
229 240 def rangeset(repo, subset, x, y):
230 241 cl = baseset(repo.changelog)
231 242 m = getset(repo, cl, x)
232 243 n = getset(repo, cl, y)
233 244
234 245 if not m or not n:
235 246 return baseset([])
236 247 m, n = m[0], n[-1]
237 248
238 249 if m < n:
239 250 r = spanset(repo, m, n + 1)
240 251 else:
241 252 r = spanset(repo, m, n - 1)
242 253 return r & subset
243 254
244 255 def dagrange(repo, subset, x, y):
245 256 r = spanset(repo)
246 257 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
247 258 s = subset.set()
248 259 return xs.filter(lambda r: r in s)
249 260
250 261 def andset(repo, subset, x, y):
251 262 return getset(repo, getset(repo, subset, x), y)
252 263
253 264 def orset(repo, subset, x, y):
254 265 xl = getset(repo, subset, x)
255 266 yl = getset(repo, subset - xl, y)
256 267 return xl + yl
257 268
258 269 def notset(repo, subset, x):
259 270 return subset - getset(repo, subset, x)
260 271
261 272 def listset(repo, subset, a, b):
262 273 raise error.ParseError(_("can't use a list in this context"))
263 274
264 275 def func(repo, subset, a, b):
265 276 if a[0] == 'symbol' and a[1] in symbols:
266 277 return symbols[a[1]](repo, subset, b)
267 278 raise error.ParseError(_("not a function: %s") % a[1])
268 279
269 280 # functions
270 281
271 282 def adds(repo, subset, x):
272 283 """``adds(pattern)``
273 284 Changesets that add a file matching pattern.
274 285
275 286 The pattern without explicit kind like ``glob:`` is expected to be
276 287 relative to the current directory and match against a file or a
277 288 directory.
278 289 """
279 290 # i18n: "adds" is a keyword
280 291 pat = getstring(x, _("adds requires a pattern"))
281 292 return checkstatus(repo, subset, pat, 1)
282 293
283 294 def ancestor(repo, subset, x):
284 295 """``ancestor(*changeset)``
285 296 Greatest common ancestor of the changesets.
286 297
287 298 Accepts 0 or more changesets.
288 299 Will return empty list when passed no args.
289 300 Greatest common ancestor of a single changeset is that changeset.
290 301 """
291 302 # i18n: "ancestor" is a keyword
292 303 l = getlist(x)
293 304 rl = spanset(repo)
294 305 anc = None
295 306
296 307 # (getset(repo, rl, i) for i in l) generates a list of lists
297 308 rev = repo.changelog.rev
298 309 ancestor = repo.changelog.ancestor
299 310 node = repo.changelog.node
300 311 for revs in (getset(repo, rl, i) for i in l):
301 312 for r in revs:
302 313 if anc is None:
303 314 anc = r
304 315 else:
305 316 anc = rev(ancestor(node(anc), node(r)))
306 317
307 318 if anc is not None and anc in subset:
308 319 return baseset([anc])
309 320 return baseset([])
310 321
311 322 def _ancestors(repo, subset, x, followfirst=False):
312 323 args = getset(repo, spanset(repo), x)
313 324 if not args:
314 325 return baseset([])
315 s = set(_revancestors(repo, args, followfirst)) | set(args)
326 s = _revancestors(repo, args, followfirst)
316 327 return subset.filter(lambda r: r in s)
317 328
318 329 def ancestors(repo, subset, x):
319 330 """``ancestors(set)``
320 331 Changesets that are ancestors of a changeset in set.
321 332 """
322 333 return _ancestors(repo, subset, x)
323 334
324 335 def _firstancestors(repo, subset, x):
325 336 # ``_firstancestors(set)``
326 337 # Like ``ancestors(set)`` but follows only the first parents.
327 338 return _ancestors(repo, subset, x, followfirst=True)
328 339
329 340 def ancestorspec(repo, subset, x, n):
330 341 """``set~n``
331 342 Changesets that are the Nth ancestor (first parents only) of a changeset
332 343 in set.
333 344 """
334 345 try:
335 346 n = int(n[1])
336 347 except (TypeError, ValueError):
337 348 raise error.ParseError(_("~ expects a number"))
338 349 ps = set()
339 350 cl = repo.changelog
340 351 for r in getset(repo, baseset(cl), x):
341 352 for i in range(n):
342 353 r = cl.parentrevs(r)[0]
343 354 ps.add(r)
344 355 return subset.filter(lambda r: r in ps)
345 356
346 357 def author(repo, subset, x):
347 358 """``author(string)``
348 359 Alias for ``user(string)``.
349 360 """
350 361 # i18n: "author" is a keyword
351 362 n = encoding.lower(getstring(x, _("author requires a string")))
352 363 kind, pattern, matcher = _substringmatcher(n)
353 364 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
354 365
355 366 def only(repo, subset, x):
356 367 """``only(set, [set])``
357 368 Changesets that are ancestors of the first set that are not ancestors
358 369 of any other head in the repo. If a second set is specified, the result
359 370 is ancestors of the first set that are not ancestors of the second set
360 371 (i.e. ::<set1> - ::<set2>).
361 372 """
362 373 cl = repo.changelog
363 374 args = getargs(x, 1, 2, _('only takes one or two arguments'))
364 375 include = getset(repo, spanset(repo), args[0]).set()
365 376 if len(args) == 1:
366 377 descendants = set(_revdescendants(repo, include, False))
367 378 exclude = [rev for rev in cl.headrevs()
368 379 if not rev in descendants and not rev in include]
369 380 else:
370 381 exclude = getset(repo, spanset(repo), args[1])
371 382
372 383 results = set(ancestormod.missingancestors(include, exclude, cl.parentrevs))
373 384 return lazyset(subset, lambda x: x in results)
374 385
375 386 def bisect(repo, subset, x):
376 387 """``bisect(string)``
377 388 Changesets marked in the specified bisect status:
378 389
379 390 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
380 391 - ``goods``, ``bads`` : csets topologically good/bad
381 392 - ``range`` : csets taking part in the bisection
382 393 - ``pruned`` : csets that are goods, bads or skipped
383 394 - ``untested`` : csets whose fate is yet unknown
384 395 - ``ignored`` : csets ignored due to DAG topology
385 396 - ``current`` : the cset currently being bisected
386 397 """
387 398 # i18n: "bisect" is a keyword
388 399 status = getstring(x, _("bisect requires a string")).lower()
389 400 state = set(hbisect.get(repo, status))
390 401 return subset.filter(lambda r: r in state)
391 402
392 403 # Backward-compatibility
393 404 # - no help entry so that we do not advertise it any more
394 405 def bisected(repo, subset, x):
395 406 return bisect(repo, subset, x)
396 407
397 408 def bookmark(repo, subset, x):
398 409 """``bookmark([name])``
399 410 The named bookmark or all bookmarks.
400 411
401 412 If `name` starts with `re:`, the remainder of the name is treated as
402 413 a regular expression. To match a bookmark that actually starts with `re:`,
403 414 use the prefix `literal:`.
404 415 """
405 416 # i18n: "bookmark" is a keyword
406 417 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
407 418 if args:
408 419 bm = getstring(args[0],
409 420 # i18n: "bookmark" is a keyword
410 421 _('the argument to bookmark must be a string'))
411 422 kind, pattern, matcher = _stringmatcher(bm)
412 423 if kind == 'literal':
413 424 bmrev = repo._bookmarks.get(bm, None)
414 425 if not bmrev:
415 426 raise util.Abort(_("bookmark '%s' does not exist") % bm)
416 427 bmrev = repo[bmrev].rev()
417 428 return subset.filter(lambda r: r == bmrev)
418 429 else:
419 430 matchrevs = set()
420 431 for name, bmrev in repo._bookmarks.iteritems():
421 432 if matcher(name):
422 433 matchrevs.add(bmrev)
423 434 if not matchrevs:
424 435 raise util.Abort(_("no bookmarks exist that match '%s'")
425 436 % pattern)
426 437 bmrevs = set()
427 438 for bmrev in matchrevs:
428 439 bmrevs.add(repo[bmrev].rev())
429 440 return subset & bmrevs
430 441
431 442 bms = set([repo[r].rev()
432 443 for r in repo._bookmarks.values()])
433 444 return subset.filter(lambda r: r in bms)
434 445
435 446 def branch(repo, subset, x):
436 447 """``branch(string or set)``
437 448 All changesets belonging to the given branch or the branches of the given
438 449 changesets.
439 450
440 451 If `string` starts with `re:`, the remainder of the name is treated as
441 452 a regular expression. To match a branch that actually starts with `re:`,
442 453 use the prefix `literal:`.
443 454 """
444 455 try:
445 456 b = getstring(x, '')
446 457 except error.ParseError:
447 458 # not a string, but another revspec, e.g. tip()
448 459 pass
449 460 else:
450 461 kind, pattern, matcher = _stringmatcher(b)
451 462 if kind == 'literal':
452 463 # note: falls through to the revspec case if no branch with
453 464 # this name exists
454 465 if pattern in repo.branchmap():
455 466 return subset.filter(lambda r: matcher(repo[r].branch()))
456 467 else:
457 468 return subset.filter(lambda r: matcher(repo[r].branch()))
458 469
459 470 s = getset(repo, spanset(repo), x)
460 471 b = set()
461 472 for r in s:
462 473 b.add(repo[r].branch())
463 474 s = s.set()
464 475 return subset.filter(lambda r: r in s or repo[r].branch() in b)
465 476
466 477 def bumped(repo, subset, x):
467 478 """``bumped()``
468 479 Mutable changesets marked as successors of public changesets.
469 480
470 481 Only non-public and non-obsolete changesets can be `bumped`.
471 482 """
472 483 # i18n: "bumped" is a keyword
473 484 getargs(x, 0, 0, _("bumped takes no arguments"))
474 485 bumped = obsmod.getrevs(repo, 'bumped')
475 486 return subset & bumped
476 487
477 488 def bundle(repo, subset, x):
478 489 """``bundle()``
479 490 Changesets in the bundle.
480 491
481 492 Bundle must be specified by the -R option."""
482 493
483 494 try:
484 495 bundlerevs = repo.changelog.bundlerevs
485 496 except AttributeError:
486 497 raise util.Abort(_("no bundle provided - specify with -R"))
487 498 return subset & bundlerevs
488 499
489 500 def checkstatus(repo, subset, pat, field):
490 501 hasset = matchmod.patkind(pat) == 'set'
491 502
492 503 def matches(x):
493 504 m = None
494 505 fname = None
495 506 c = repo[x]
496 507 if not m or hasset:
497 508 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
498 509 if not m.anypats() and len(m.files()) == 1:
499 510 fname = m.files()[0]
500 511 if fname is not None:
501 512 if fname not in c.files():
502 513 return False
503 514 else:
504 515 for f in c.files():
505 516 if m(f):
506 517 break
507 518 else:
508 519 return False
509 520 files = repo.status(c.p1().node(), c.node())[field]
510 521 if fname is not None:
511 522 if fname in files:
512 523 return True
513 524 else:
514 525 for f in files:
515 526 if m(f):
516 527 return True
517 528
518 529 return subset.filter(matches)
519 530
520 531 def _children(repo, narrow, parentset):
521 532 cs = set()
522 533 if not parentset:
523 534 return baseset(cs)
524 535 pr = repo.changelog.parentrevs
525 536 minrev = min(parentset)
526 537 for r in narrow:
527 538 if r <= minrev:
528 539 continue
529 540 for p in pr(r):
530 541 if p in parentset:
531 542 cs.add(r)
532 543 return baseset(cs)
533 544
534 545 def children(repo, subset, x):
535 546 """``children(set)``
536 547 Child changesets of changesets in set.
537 548 """
538 549 s = getset(repo, baseset(repo), x).set()
539 550 cs = _children(repo, subset, s)
540 551 return subset & cs
541 552
542 553 def closed(repo, subset, x):
543 554 """``closed()``
544 555 Changeset is closed.
545 556 """
546 557 # i18n: "closed" is a keyword
547 558 getargs(x, 0, 0, _("closed takes no arguments"))
548 559 return subset.filter(lambda r: repo[r].closesbranch())
549 560
550 561 def contains(repo, subset, x):
551 562 """``contains(pattern)``
552 563 Revision contains a file matching pattern. See :hg:`help patterns`
553 564 for information about file patterns.
554 565
555 566 The pattern without explicit kind like ``glob:`` is expected to be
556 567 relative to the current directory and match against a file exactly
557 568 for efficiency.
558 569 """
559 570 # i18n: "contains" is a keyword
560 571 pat = getstring(x, _("contains requires a pattern"))
561 572
562 573 def matches(x):
563 574 if not matchmod.patkind(pat):
564 575 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
565 576 if pats in repo[x]:
566 577 return True
567 578 else:
568 579 c = repo[x]
569 580 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
570 581 for f in c.manifest():
571 582 if m(f):
572 583 return True
573 584 return False
574 585
575 586 return subset.filter(matches)
576 587
577 588 def converted(repo, subset, x):
578 589 """``converted([id])``
579 590 Changesets converted from the given identifier in the old repository if
580 591 present, or all converted changesets if no identifier is specified.
581 592 """
582 593
583 594 # There is exactly no chance of resolving the revision, so do a simple
584 595 # string compare and hope for the best
585 596
586 597 rev = None
587 598 # i18n: "converted" is a keyword
588 599 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
589 600 if l:
590 601 # i18n: "converted" is a keyword
591 602 rev = getstring(l[0], _('converted requires a revision'))
592 603
593 604 def _matchvalue(r):
594 605 source = repo[r].extra().get('convert_revision', None)
595 606 return source is not None and (rev is None or source.startswith(rev))
596 607
597 608 return subset.filter(lambda r: _matchvalue(r))
598 609
599 610 def date(repo, subset, x):
600 611 """``date(interval)``
601 612 Changesets within the interval, see :hg:`help dates`.
602 613 """
603 614 # i18n: "date" is a keyword
604 615 ds = getstring(x, _("date requires a string"))
605 616 dm = util.matchdate(ds)
606 617 return subset.filter(lambda x: dm(repo[x].date()[0]))
607 618
608 619 def desc(repo, subset, x):
609 620 """``desc(string)``
610 621 Search commit message for string. The match is case-insensitive.
611 622 """
612 623 # i18n: "desc" is a keyword
613 624 ds = encoding.lower(getstring(x, _("desc requires a string")))
614 625
615 626 def matches(x):
616 627 c = repo[x]
617 628 return ds in encoding.lower(c.description())
618 629
619 630 return subset.filter(matches)
620 631
621 632 def _descendants(repo, subset, x, followfirst=False):
622 633 args = getset(repo, spanset(repo), x)
623 634 if not args:
624 635 return baseset([])
625 636 s = set(_revdescendants(repo, args, followfirst)) | set(args)
626 637 return subset & s
627 638
628 639 def descendants(repo, subset, x):
629 640 """``descendants(set)``
630 641 Changesets which are descendants of changesets in set.
631 642 """
632 643 return _descendants(repo, subset, x)
633 644
634 645 def _firstdescendants(repo, subset, x):
635 646 # ``_firstdescendants(set)``
636 647 # Like ``descendants(set)`` but follows only the first parents.
637 648 return _descendants(repo, subset, x, followfirst=True)
638 649
639 650 def destination(repo, subset, x):
640 651 """``destination([set])``
641 652 Changesets that were created by a graft, transplant or rebase operation,
642 653 with the given revisions specified as the source. Omitting the optional set
643 654 is the same as passing all().
644 655 """
645 656 if x is not None:
646 657 args = getset(repo, spanset(repo), x).set()
647 658 else:
648 659 args = getall(repo, spanset(repo), x).set()
649 660
650 661 dests = set()
651 662
652 663 # subset contains all of the possible destinations that can be returned, so
653 664 # iterate over them and see if their source(s) were provided in the args.
654 665 # Even if the immediate src of r is not in the args, src's source (or
655 666 # further back) may be. Scanning back further than the immediate src allows
656 667 # transitive transplants and rebases to yield the same results as transitive
657 668 # grafts.
658 669 for r in subset:
659 670 src = _getrevsource(repo, r)
660 671 lineage = None
661 672
662 673 while src is not None:
663 674 if lineage is None:
664 675 lineage = list()
665 676
666 677 lineage.append(r)
667 678
668 679 # The visited lineage is a match if the current source is in the arg
669 680 # set. Since every candidate dest is visited by way of iterating
670 681 # subset, any dests further back in the lineage will be tested by a
671 682 # different iteration over subset. Likewise, if the src was already
672 683 # selected, the current lineage can be selected without going back
673 684 # further.
674 685 if src in args or src in dests:
675 686 dests.update(lineage)
676 687 break
677 688
678 689 r = src
679 690 src = _getrevsource(repo, r)
680 691
681 692 return subset.filter(lambda r: r in dests)
682 693
683 694 def divergent(repo, subset, x):
684 695 """``divergent()``
685 696 Final successors of changesets with an alternative set of final successors.
686 697 """
687 698 # i18n: "divergent" is a keyword
688 699 getargs(x, 0, 0, _("divergent takes no arguments"))
689 700 divergent = obsmod.getrevs(repo, 'divergent')
690 701 return subset.filter(lambda r: r in divergent)
691 702
692 703 def draft(repo, subset, x):
693 704 """``draft()``
694 705 Changeset in draft phase."""
695 706 # i18n: "draft" is a keyword
696 707 getargs(x, 0, 0, _("draft takes no arguments"))
697 708 pc = repo._phasecache
698 709 return subset.filter(lambda r: pc.phase(repo, r) == phases.draft)
699 710
700 711 def extinct(repo, subset, x):
701 712 """``extinct()``
702 713 Obsolete changesets with obsolete descendants only.
703 714 """
704 715 # i18n: "extinct" is a keyword
705 716 getargs(x, 0, 0, _("extinct takes no arguments"))
706 717 extincts = obsmod.getrevs(repo, 'extinct')
707 718 return subset & extincts
708 719
709 720 def extra(repo, subset, x):
710 721 """``extra(label, [value])``
711 722 Changesets with the given label in the extra metadata, with the given
712 723 optional value.
713 724
714 725 If `value` starts with `re:`, the remainder of the value is treated as
715 726 a regular expression. To match a value that actually starts with `re:`,
716 727 use the prefix `literal:`.
717 728 """
718 729
719 730 # i18n: "extra" is a keyword
720 731 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
721 732 # i18n: "extra" is a keyword
722 733 label = getstring(l[0], _('first argument to extra must be a string'))
723 734 value = None
724 735
725 736 if len(l) > 1:
726 737 # i18n: "extra" is a keyword
727 738 value = getstring(l[1], _('second argument to extra must be a string'))
728 739 kind, value, matcher = _stringmatcher(value)
729 740
730 741 def _matchvalue(r):
731 742 extra = repo[r].extra()
732 743 return label in extra and (value is None or matcher(extra[label]))
733 744
734 745 return subset.filter(lambda r: _matchvalue(r))
735 746
736 747 def filelog(repo, subset, x):
737 748 """``filelog(pattern)``
738 749 Changesets connected to the specified filelog.
739 750
740 751 For performance reasons, ``filelog()`` does not show every changeset
741 752 that affects the requested file(s). See :hg:`help log` for details. For
742 753 a slower, more accurate result, use ``file()``.
743 754
744 755 The pattern without explicit kind like ``glob:`` is expected to be
745 756 relative to the current directory and match against a file exactly
746 757 for efficiency.
747 758 """
748 759
749 760 # i18n: "filelog" is a keyword
750 761 pat = getstring(x, _("filelog requires a pattern"))
751 762 s = set()
752 763
753 764 if not matchmod.patkind(pat):
754 765 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
755 766 fl = repo.file(f)
756 767 for fr in fl:
757 768 s.add(fl.linkrev(fr))
758 769 else:
759 770 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
760 771 for f in repo[None]:
761 772 if m(f):
762 773 fl = repo.file(f)
763 774 for fr in fl:
764 775 s.add(fl.linkrev(fr))
765 776
766 777 return subset.filter(lambda r: r in s)
767 778
768 779 def first(repo, subset, x):
769 780 """``first(set, [n])``
770 781 An alias for limit().
771 782 """
772 783 return limit(repo, subset, x)
773 784
774 785 def _follow(repo, subset, x, name, followfirst=False):
775 786 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
776 787 c = repo['.']
777 788 if l:
778 789 x = getstring(l[0], _("%s expected a filename") % name)
779 790 if x in c:
780 791 cx = c[x]
781 792 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
782 793 # include the revision responsible for the most recent version
783 794 s.add(cx.linkrev())
784 795 else:
785 796 return baseset([])
786 797 else:
787 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
798 s = _revancestors(repo, baseset([c.rev()]), followfirst)
788 799
789 800 return subset.filter(lambda r: r in s)
790 801
791 802 def follow(repo, subset, x):
792 803 """``follow([file])``
793 804 An alias for ``::.`` (ancestors of the working copy's first parent).
794 805 If a filename is specified, the history of the given file is followed,
795 806 including copies.
796 807 """
797 808 return _follow(repo, subset, x, 'follow')
798 809
799 810 def _followfirst(repo, subset, x):
800 811 # ``followfirst([file])``
801 812 # Like ``follow([file])`` but follows only the first parent of
802 813 # every revision or file revision.
803 814 return _follow(repo, subset, x, '_followfirst', followfirst=True)
804 815
805 816 def getall(repo, subset, x):
806 817 """``all()``
807 818 All changesets, the same as ``0:tip``.
808 819 """
809 820 # i18n: "all" is a keyword
810 821 getargs(x, 0, 0, _("all takes no arguments"))
811 822 return subset
812 823
813 824 def grep(repo, subset, x):
814 825 """``grep(regex)``
815 826 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
816 827 to ensure special escape characters are handled correctly. Unlike
817 828 ``keyword(string)``, the match is case-sensitive.
818 829 """
819 830 try:
820 831 # i18n: "grep" is a keyword
821 832 gr = re.compile(getstring(x, _("grep requires a string")))
822 833 except re.error, e:
823 834 raise error.ParseError(_('invalid match pattern: %s') % e)
824 835
825 836 def matches(x):
826 837 c = repo[x]
827 838 for e in c.files() + [c.user(), c.description()]:
828 839 if gr.search(e):
829 840 return True
830 841 return False
831 842
832 843 return subset.filter(matches)
833 844
834 845 def _matchfiles(repo, subset, x):
835 846 # _matchfiles takes a revset list of prefixed arguments:
836 847 #
837 848 # [p:foo, i:bar, x:baz]
838 849 #
839 850 # builds a match object from them and filters subset. Allowed
840 851 # prefixes are 'p:' for regular patterns, 'i:' for include
841 852 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
842 853 # a revision identifier, or the empty string to reference the
843 854 # working directory, from which the match object is
844 855 # initialized. Use 'd:' to set the default matching mode, default
845 856 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
846 857
847 858 # i18n: "_matchfiles" is a keyword
848 859 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
849 860 pats, inc, exc = [], [], []
850 861 hasset = False
851 862 rev, default = None, None
852 863 for arg in l:
853 864 # i18n: "_matchfiles" is a keyword
854 865 s = getstring(arg, _("_matchfiles requires string arguments"))
855 866 prefix, value = s[:2], s[2:]
856 867 if prefix == 'p:':
857 868 pats.append(value)
858 869 elif prefix == 'i:':
859 870 inc.append(value)
860 871 elif prefix == 'x:':
861 872 exc.append(value)
862 873 elif prefix == 'r:':
863 874 if rev is not None:
864 875 # i18n: "_matchfiles" is a keyword
865 876 raise error.ParseError(_('_matchfiles expected at most one '
866 877 'revision'))
867 878 rev = value
868 879 elif prefix == 'd:':
869 880 if default is not None:
870 881 # i18n: "_matchfiles" is a keyword
871 882 raise error.ParseError(_('_matchfiles expected at most one '
872 883 'default mode'))
873 884 default = value
874 885 else:
875 886 # i18n: "_matchfiles" is a keyword
876 887 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
877 888 if not hasset and matchmod.patkind(value) == 'set':
878 889 hasset = True
879 890 if not default:
880 891 default = 'glob'
881 892
882 893 def matches(x):
883 894 m = None
884 895 c = repo[x]
885 896 if not m or (hasset and rev is None):
886 897 ctx = c
887 898 if rev is not None:
888 899 ctx = repo[rev or None]
889 900 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
890 901 exclude=exc, ctx=ctx, default=default)
891 902 for f in c.files():
892 903 if m(f):
893 904 return True
894 905 return False
895 906
896 907 return subset.filter(matches)
897 908
898 909 def hasfile(repo, subset, x):
899 910 """``file(pattern)``
900 911 Changesets affecting files matched by pattern.
901 912
902 913 For a faster but less accurate result, consider using ``filelog()``
903 914 instead.
904 915
905 916 This predicate uses ``glob:`` as the default kind of pattern.
906 917 """
907 918 # i18n: "file" is a keyword
908 919 pat = getstring(x, _("file requires a pattern"))
909 920 return _matchfiles(repo, subset, ('string', 'p:' + pat))
910 921
911 922 def head(repo, subset, x):
912 923 """``head()``
913 924 Changeset is a named branch head.
914 925 """
915 926 # i18n: "head" is a keyword
916 927 getargs(x, 0, 0, _("head takes no arguments"))
917 928 hs = set()
918 929 for b, ls in repo.branchmap().iteritems():
919 930 hs.update(repo[h].rev() for h in ls)
920 931 return subset.filter(lambda r: r in hs)
921 932
922 933 def heads(repo, subset, x):
923 934 """``heads(set)``
924 935 Members of set with no children in set.
925 936 """
926 937 s = getset(repo, subset, x)
927 938 ps = parents(repo, subset, x)
928 939 return s - ps
929 940
930 941 def hidden(repo, subset, x):
931 942 """``hidden()``
932 943 Hidden changesets.
933 944 """
934 945 # i18n: "hidden" is a keyword
935 946 getargs(x, 0, 0, _("hidden takes no arguments"))
936 947 hiddenrevs = repoview.filterrevs(repo, 'visible')
937 948 return subset & hiddenrevs
938 949
939 950 def keyword(repo, subset, x):
940 951 """``keyword(string)``
941 952 Search commit message, user name, and names of changed files for
942 953 string. The match is case-insensitive.
943 954 """
944 955 # i18n: "keyword" is a keyword
945 956 kw = encoding.lower(getstring(x, _("keyword requires a string")))
946 957
947 958 def matches(r):
948 959 c = repo[r]
949 960 return util.any(kw in encoding.lower(t) for t in c.files() + [c.user(),
950 961 c.description()])
951 962
952 963 return subset.filter(matches)
953 964
954 965 def limit(repo, subset, x):
955 966 """``limit(set, [n])``
956 967 First n members of set, defaulting to 1.
957 968 """
958 969 # i18n: "limit" is a keyword
959 970 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
960 971 try:
961 972 lim = 1
962 973 if len(l) == 2:
963 974 # i18n: "limit" is a keyword
964 975 lim = int(getstring(l[1], _("limit requires a number")))
965 976 except (TypeError, ValueError):
966 977 # i18n: "limit" is a keyword
967 978 raise error.ParseError(_("limit expects a number"))
968 979 ss = subset.set()
969 980 os = getset(repo, spanset(repo), l[0])
970 981 bs = baseset([])
971 982 it = iter(os)
972 983 for x in xrange(lim):
973 984 try:
974 985 y = it.next()
975 986 if y in ss:
976 987 bs.append(y)
977 988 except (StopIteration):
978 989 break
979 990 return bs
980 991
981 992 def last(repo, subset, x):
982 993 """``last(set, [n])``
983 994 Last n members of set, defaulting to 1.
984 995 """
985 996 # i18n: "last" is a keyword
986 997 l = getargs(x, 1, 2, _("last requires one or two arguments"))
987 998 try:
988 999 lim = 1
989 1000 if len(l) == 2:
990 1001 # i18n: "last" is a keyword
991 1002 lim = int(getstring(l[1], _("last requires a number")))
992 1003 except (TypeError, ValueError):
993 1004 # i18n: "last" is a keyword
994 1005 raise error.ParseError(_("last expects a number"))
995 1006 ss = subset.set()
996 1007 os = getset(repo, spanset(repo), l[0])
997 1008 os.reverse()
998 1009 bs = baseset([])
999 1010 it = iter(os)
1000 1011 for x in xrange(lim):
1001 1012 try:
1002 1013 y = it.next()
1003 1014 if y in ss:
1004 1015 bs.append(y)
1005 1016 except (StopIteration):
1006 1017 break
1007 1018 return bs
1008 1019
1009 1020 def maxrev(repo, subset, x):
1010 1021 """``max(set)``
1011 1022 Changeset with highest revision number in set.
1012 1023 """
1013 1024 os = getset(repo, spanset(repo), x)
1014 1025 if os:
1015 1026 m = max(os)
1016 1027 if m in subset:
1017 1028 return baseset([m])
1018 1029 return baseset([])
1019 1030
1020 1031 def merge(repo, subset, x):
1021 1032 """``merge()``
1022 1033 Changeset is a merge changeset.
1023 1034 """
1024 1035 # i18n: "merge" is a keyword
1025 1036 getargs(x, 0, 0, _("merge takes no arguments"))
1026 1037 cl = repo.changelog
1027 1038 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1028 1039
1029 1040 def branchpoint(repo, subset, x):
1030 1041 """``branchpoint()``
1031 1042 Changesets with more than one child.
1032 1043 """
1033 1044 # i18n: "branchpoint" is a keyword
1034 1045 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1035 1046 cl = repo.changelog
1036 1047 if not subset:
1037 1048 return baseset([])
1038 1049 baserev = min(subset)
1039 1050 parentscount = [0]*(len(repo) - baserev)
1040 1051 for r in cl.revs(start=baserev + 1):
1041 1052 for p in cl.parentrevs(r):
1042 1053 if p >= baserev:
1043 1054 parentscount[p - baserev] += 1
1044 1055 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1045 1056
1046 1057 def minrev(repo, subset, x):
1047 1058 """``min(set)``
1048 1059 Changeset with lowest revision number in set.
1049 1060 """
1050 1061 os = getset(repo, spanset(repo), x)
1051 1062 if os:
1052 1063 m = min(os)
1053 1064 if m in subset:
1054 1065 return baseset([m])
1055 1066 return baseset([])
1056 1067
1057 1068 def _missingancestors(repo, subset, x):
1058 1069 # i18n: "_missingancestors" is a keyword
1059 1070 revs, bases = getargs(x, 2, 2,
1060 1071 _("_missingancestors requires two arguments"))
1061 1072 rs = baseset(repo)
1062 1073 revs = getset(repo, rs, revs)
1063 1074 bases = getset(repo, rs, bases)
1064 1075 missing = set(repo.changelog.findmissingrevs(bases, revs))
1065 1076 return baseset([r for r in subset if r in missing])
1066 1077
1067 1078 def modifies(repo, subset, x):
1068 1079 """``modifies(pattern)``
1069 1080 Changesets modifying files matched by pattern.
1070 1081
1071 1082 The pattern without explicit kind like ``glob:`` is expected to be
1072 1083 relative to the current directory and match against a file or a
1073 1084 directory.
1074 1085 """
1075 1086 # i18n: "modifies" is a keyword
1076 1087 pat = getstring(x, _("modifies requires a pattern"))
1077 1088 return checkstatus(repo, subset, pat, 0)
1078 1089
1079 1090 def node_(repo, subset, x):
1080 1091 """``id(string)``
1081 1092 Revision non-ambiguously specified by the given hex string prefix.
1082 1093 """
1083 1094 # i18n: "id" is a keyword
1084 1095 l = getargs(x, 1, 1, _("id requires one argument"))
1085 1096 # i18n: "id" is a keyword
1086 1097 n = getstring(l[0], _("id requires a string"))
1087 1098 if len(n) == 40:
1088 1099 rn = repo[n].rev()
1089 1100 else:
1090 1101 rn = None
1091 1102 pm = repo.changelog._partialmatch(n)
1092 1103 if pm is not None:
1093 1104 rn = repo.changelog.rev(pm)
1094 1105
1095 1106 return subset.filter(lambda r: r == rn)
1096 1107
1097 1108 def obsolete(repo, subset, x):
1098 1109 """``obsolete()``
1099 1110 Mutable changeset with a newer version."""
1100 1111 # i18n: "obsolete" is a keyword
1101 1112 getargs(x, 0, 0, _("obsolete takes no arguments"))
1102 1113 obsoletes = obsmod.getrevs(repo, 'obsolete')
1103 1114 return subset & obsoletes
1104 1115
1105 1116 def origin(repo, subset, x):
1106 1117 """``origin([set])``
1107 1118 Changesets that were specified as a source for the grafts, transplants or
1108 1119 rebases that created the given revisions. Omitting the optional set is the
1109 1120 same as passing all(). If a changeset created by these operations is itself
1110 1121 specified as a source for one of these operations, only the source changeset
1111 1122 for the first operation is selected.
1112 1123 """
1113 1124 if x is not None:
1114 1125 args = getset(repo, spanset(repo), x).set()
1115 1126 else:
1116 1127 args = getall(repo, spanset(repo), x).set()
1117 1128
1118 1129 def _firstsrc(rev):
1119 1130 src = _getrevsource(repo, rev)
1120 1131 if src is None:
1121 1132 return None
1122 1133
1123 1134 while True:
1124 1135 prev = _getrevsource(repo, src)
1125 1136
1126 1137 if prev is None:
1127 1138 return src
1128 1139 src = prev
1129 1140
1130 1141 o = set([_firstsrc(r) for r in args])
1131 1142 return subset.filter(lambda r: r in o)
1132 1143
1133 1144 def outgoing(repo, subset, x):
1134 1145 """``outgoing([path])``
1135 1146 Changesets not found in the specified destination repository, or the
1136 1147 default push location.
1137 1148 """
1138 1149 import hg # avoid start-up nasties
1139 1150 # i18n: "outgoing" is a keyword
1140 1151 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1141 1152 # i18n: "outgoing" is a keyword
1142 1153 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1143 1154 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1144 1155 dest, branches = hg.parseurl(dest)
1145 1156 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1146 1157 if revs:
1147 1158 revs = [repo.lookup(rev) for rev in revs]
1148 1159 other = hg.peer(repo, {}, dest)
1149 1160 repo.ui.pushbuffer()
1150 1161 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1151 1162 repo.ui.popbuffer()
1152 1163 cl = repo.changelog
1153 1164 o = set([cl.rev(r) for r in outgoing.missing])
1154 1165 return subset.filter(lambda r: r in o)
1155 1166
1156 1167 def p1(repo, subset, x):
1157 1168 """``p1([set])``
1158 1169 First parent of changesets in set, or the working directory.
1159 1170 """
1160 1171 if x is None:
1161 1172 p = repo[x].p1().rev()
1162 1173 return subset.filter(lambda r: r == p)
1163 1174
1164 1175 ps = set()
1165 1176 cl = repo.changelog
1166 1177 for r in getset(repo, spanset(repo), x):
1167 1178 ps.add(cl.parentrevs(r)[0])
1168 1179 return subset & ps
1169 1180
1170 1181 def p2(repo, subset, x):
1171 1182 """``p2([set])``
1172 1183 Second parent of changesets in set, or the working directory.
1173 1184 """
1174 1185 if x is None:
1175 1186 ps = repo[x].parents()
1176 1187 try:
1177 1188 p = ps[1].rev()
1178 1189 return subset.filter(lambda r: r == p)
1179 1190 except IndexError:
1180 1191 return baseset([])
1181 1192
1182 1193 ps = set()
1183 1194 cl = repo.changelog
1184 1195 for r in getset(repo, spanset(repo), x):
1185 1196 ps.add(cl.parentrevs(r)[1])
1186 1197 return subset & ps
1187 1198
1188 1199 def parents(repo, subset, x):
1189 1200 """``parents([set])``
1190 1201 The set of all parents for all changesets in set, or the working directory.
1191 1202 """
1192 1203 if x is None:
1193 1204 ps = tuple(p.rev() for p in repo[x].parents())
1194 1205 return subset & ps
1195 1206
1196 1207 ps = set()
1197 1208 cl = repo.changelog
1198 1209 for r in getset(repo, spanset(repo), x):
1199 1210 ps.update(cl.parentrevs(r))
1200 1211 return subset & ps
1201 1212
1202 1213 def parentspec(repo, subset, x, n):
1203 1214 """``set^0``
1204 1215 The set.
1205 1216 ``set^1`` (or ``set^``), ``set^2``
1206 1217 First or second parent, respectively, of all changesets in set.
1207 1218 """
1208 1219 try:
1209 1220 n = int(n[1])
1210 1221 if n not in (0, 1, 2):
1211 1222 raise ValueError
1212 1223 except (TypeError, ValueError):
1213 1224 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1214 1225 ps = set()
1215 1226 cl = repo.changelog
1216 1227 for r in getset(repo, baseset(cl), x):
1217 1228 if n == 0:
1218 1229 ps.add(r)
1219 1230 elif n == 1:
1220 1231 ps.add(cl.parentrevs(r)[0])
1221 1232 elif n == 2:
1222 1233 parents = cl.parentrevs(r)
1223 1234 if len(parents) > 1:
1224 1235 ps.add(parents[1])
1225 1236 return subset & ps
1226 1237
1227 1238 def present(repo, subset, x):
1228 1239 """``present(set)``
1229 1240 An empty set, if any revision in set isn't found; otherwise,
1230 1241 all revisions in set.
1231 1242
1232 1243 If any of specified revisions is not present in the local repository,
1233 1244 the query is normally aborted. But this predicate allows the query
1234 1245 to continue even in such cases.
1235 1246 """
1236 1247 try:
1237 1248 return getset(repo, subset, x)
1238 1249 except error.RepoLookupError:
1239 1250 return baseset([])
1240 1251
1241 1252 def public(repo, subset, x):
1242 1253 """``public()``
1243 1254 Changeset in public phase."""
1244 1255 # i18n: "public" is a keyword
1245 1256 getargs(x, 0, 0, _("public takes no arguments"))
1246 1257 pc = repo._phasecache
1247 1258 return subset.filter(lambda r: pc.phase(repo, r) == phases.public)
1248 1259
1249 1260 def remote(repo, subset, x):
1250 1261 """``remote([id [,path]])``
1251 1262 Local revision that corresponds to the given identifier in a
1252 1263 remote repository, if present. Here, the '.' identifier is a
1253 1264 synonym for the current local branch.
1254 1265 """
1255 1266
1256 1267 import hg # avoid start-up nasties
1257 1268 # i18n: "remote" is a keyword
1258 1269 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1259 1270
1260 1271 q = '.'
1261 1272 if len(l) > 0:
1262 1273 # i18n: "remote" is a keyword
1263 1274 q = getstring(l[0], _("remote requires a string id"))
1264 1275 if q == '.':
1265 1276 q = repo['.'].branch()
1266 1277
1267 1278 dest = ''
1268 1279 if len(l) > 1:
1269 1280 # i18n: "remote" is a keyword
1270 1281 dest = getstring(l[1], _("remote requires a repository path"))
1271 1282 dest = repo.ui.expandpath(dest or 'default')
1272 1283 dest, branches = hg.parseurl(dest)
1273 1284 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1274 1285 if revs:
1275 1286 revs = [repo.lookup(rev) for rev in revs]
1276 1287 other = hg.peer(repo, {}, dest)
1277 1288 n = other.lookup(q)
1278 1289 if n in repo:
1279 1290 r = repo[n].rev()
1280 1291 if r in subset:
1281 1292 return baseset([r])
1282 1293 return baseset([])
1283 1294
1284 1295 def removes(repo, subset, x):
1285 1296 """``removes(pattern)``
1286 1297 Changesets which remove files matching pattern.
1287 1298
1288 1299 The pattern without explicit kind like ``glob:`` is expected to be
1289 1300 relative to the current directory and match against a file or a
1290 1301 directory.
1291 1302 """
1292 1303 # i18n: "removes" is a keyword
1293 1304 pat = getstring(x, _("removes requires a pattern"))
1294 1305 return checkstatus(repo, subset, pat, 2)
1295 1306
1296 1307 def rev(repo, subset, x):
1297 1308 """``rev(number)``
1298 1309 Revision with the given numeric identifier.
1299 1310 """
1300 1311 # i18n: "rev" is a keyword
1301 1312 l = getargs(x, 1, 1, _("rev requires one argument"))
1302 1313 try:
1303 1314 # i18n: "rev" is a keyword
1304 1315 l = int(getstring(l[0], _("rev requires a number")))
1305 1316 except (TypeError, ValueError):
1306 1317 # i18n: "rev" is a keyword
1307 1318 raise error.ParseError(_("rev expects a number"))
1308 1319 return subset.filter(lambda r: r == l)
1309 1320
1310 1321 def matching(repo, subset, x):
1311 1322 """``matching(revision [, field])``
1312 1323 Changesets in which a given set of fields match the set of fields in the
1313 1324 selected revision or set.
1314 1325
1315 1326 To match more than one field pass the list of fields to match separated
1316 1327 by spaces (e.g. ``author description``).
1317 1328
1318 1329 Valid fields are most regular revision fields and some special fields.
1319 1330
1320 1331 Regular revision fields are ``description``, ``author``, ``branch``,
1321 1332 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1322 1333 and ``diff``.
1323 1334 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1324 1335 contents of the revision. Two revisions matching their ``diff`` will
1325 1336 also match their ``files``.
1326 1337
1327 1338 Special fields are ``summary`` and ``metadata``:
1328 1339 ``summary`` matches the first line of the description.
1329 1340 ``metadata`` is equivalent to matching ``description user date``
1330 1341 (i.e. it matches the main metadata fields).
1331 1342
1332 1343 ``metadata`` is the default field which is used when no fields are
1333 1344 specified. You can match more than one field at a time.
1334 1345 """
1335 1346 # i18n: "matching" is a keyword
1336 1347 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1337 1348
1338 1349 revs = getset(repo, baseset(repo.changelog), l[0])
1339 1350
1340 1351 fieldlist = ['metadata']
1341 1352 if len(l) > 1:
1342 1353 fieldlist = getstring(l[1],
1343 1354 # i18n: "matching" is a keyword
1344 1355 _("matching requires a string "
1345 1356 "as its second argument")).split()
1346 1357
1347 1358 # Make sure that there are no repeated fields,
1348 1359 # expand the 'special' 'metadata' field type
1349 1360 # and check the 'files' whenever we check the 'diff'
1350 1361 fields = []
1351 1362 for field in fieldlist:
1352 1363 if field == 'metadata':
1353 1364 fields += ['user', 'description', 'date']
1354 1365 elif field == 'diff':
1355 1366 # a revision matching the diff must also match the files
1356 1367 # since matching the diff is very costly, make sure to
1357 1368 # also match the files first
1358 1369 fields += ['files', 'diff']
1359 1370 else:
1360 1371 if field == 'author':
1361 1372 field = 'user'
1362 1373 fields.append(field)
1363 1374 fields = set(fields)
1364 1375 if 'summary' in fields and 'description' in fields:
1365 1376 # If a revision matches its description it also matches its summary
1366 1377 fields.discard('summary')
1367 1378
1368 1379 # We may want to match more than one field
1369 1380 # Not all fields take the same amount of time to be matched
1370 1381 # Sort the selected fields in order of increasing matching cost
1371 1382 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1372 1383 'files', 'description', 'substate', 'diff']
1373 1384 def fieldkeyfunc(f):
1374 1385 try:
1375 1386 return fieldorder.index(f)
1376 1387 except ValueError:
1377 1388 # assume an unknown field is very costly
1378 1389 return len(fieldorder)
1379 1390 fields = list(fields)
1380 1391 fields.sort(key=fieldkeyfunc)
1381 1392
1382 1393 # Each field will be matched with its own "getfield" function
1383 1394 # which will be added to the getfieldfuncs array of functions
1384 1395 getfieldfuncs = []
1385 1396 _funcs = {
1386 1397 'user': lambda r: repo[r].user(),
1387 1398 'branch': lambda r: repo[r].branch(),
1388 1399 'date': lambda r: repo[r].date(),
1389 1400 'description': lambda r: repo[r].description(),
1390 1401 'files': lambda r: repo[r].files(),
1391 1402 'parents': lambda r: repo[r].parents(),
1392 1403 'phase': lambda r: repo[r].phase(),
1393 1404 'substate': lambda r: repo[r].substate,
1394 1405 'summary': lambda r: repo[r].description().splitlines()[0],
1395 1406 'diff': lambda r: list(repo[r].diff(git=True),)
1396 1407 }
1397 1408 for info in fields:
1398 1409 getfield = _funcs.get(info, None)
1399 1410 if getfield is None:
1400 1411 raise error.ParseError(
1401 1412 # i18n: "matching" is a keyword
1402 1413 _("unexpected field name passed to matching: %s") % info)
1403 1414 getfieldfuncs.append(getfield)
1404 1415 # convert the getfield array of functions into a "getinfo" function
1405 1416 # which returns an array of field values (or a single value if there
1406 1417 # is only one field to match)
1407 1418 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1408 1419
1409 1420 def matches(x):
1410 1421 for rev in revs:
1411 1422 target = getinfo(rev)
1412 1423 match = True
1413 1424 for n, f in enumerate(getfieldfuncs):
1414 1425 if target[n] != f(x):
1415 1426 match = False
1416 1427 if match:
1417 1428 return True
1418 1429 return False
1419 1430
1420 1431 return subset.filter(matches)
1421 1432
1422 1433 def reverse(repo, subset, x):
1423 1434 """``reverse(set)``
1424 1435 Reverse order of set.
1425 1436 """
1426 1437 l = getset(repo, subset, x)
1427 1438 l.reverse()
1428 1439 return l
1429 1440
1430 1441 def roots(repo, subset, x):
1431 1442 """``roots(set)``
1432 1443 Changesets in set with no parent changeset in set.
1433 1444 """
1434 1445 s = getset(repo, baseset(repo.changelog), x).set()
1435 1446 subset = baseset([r for r in subset if r in s])
1436 1447 cs = _children(repo, subset, s)
1437 1448 return subset - cs
1438 1449
1439 1450 def secret(repo, subset, x):
1440 1451 """``secret()``
1441 1452 Changeset in secret phase."""
1442 1453 # i18n: "secret" is a keyword
1443 1454 getargs(x, 0, 0, _("secret takes no arguments"))
1444 1455 pc = repo._phasecache
1445 1456 return subset.filter(lambda x: pc.phase(repo, x) == phases.secret)
1446 1457
1447 1458 def sort(repo, subset, x):
1448 1459 """``sort(set[, [-]key...])``
1449 1460 Sort set by keys. The default sort order is ascending, specify a key
1450 1461 as ``-key`` to sort in descending order.
1451 1462
1452 1463 The keys can be:
1453 1464
1454 1465 - ``rev`` for the revision number,
1455 1466 - ``branch`` for the branch name,
1456 1467 - ``desc`` for the commit message (description),
1457 1468 - ``user`` for user name (``author`` can be used as an alias),
1458 1469 - ``date`` for the commit date
1459 1470 """
1460 1471 # i18n: "sort" is a keyword
1461 1472 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1462 1473 keys = "rev"
1463 1474 if len(l) == 2:
1464 1475 # i18n: "sort" is a keyword
1465 1476 keys = getstring(l[1], _("sort spec must be a string"))
1466 1477
1467 1478 s = l[0]
1468 1479 keys = keys.split()
1469 1480 l = []
1470 1481 def invert(s):
1471 1482 return "".join(chr(255 - ord(c)) for c in s)
1472 1483 for r in getset(repo, subset, s):
1473 1484 c = repo[r]
1474 1485 e = []
1475 1486 for k in keys:
1476 1487 if k == 'rev':
1477 1488 e.append(r)
1478 1489 elif k == '-rev':
1479 1490 e.append(-r)
1480 1491 elif k == 'branch':
1481 1492 e.append(c.branch())
1482 1493 elif k == '-branch':
1483 1494 e.append(invert(c.branch()))
1484 1495 elif k == 'desc':
1485 1496 e.append(c.description())
1486 1497 elif k == '-desc':
1487 1498 e.append(invert(c.description()))
1488 1499 elif k in 'user author':
1489 1500 e.append(c.user())
1490 1501 elif k in '-user -author':
1491 1502 e.append(invert(c.user()))
1492 1503 elif k == 'date':
1493 1504 e.append(c.date()[0])
1494 1505 elif k == '-date':
1495 1506 e.append(-c.date()[0])
1496 1507 else:
1497 1508 raise error.ParseError(_("unknown sort key %r") % k)
1498 1509 e.append(r)
1499 1510 l.append(e)
1500 1511 l.sort()
1501 1512 return baseset([e[-1] for e in l])
1502 1513
1503 1514 def _stringmatcher(pattern):
1504 1515 """
1505 1516 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1506 1517 returns the matcher name, pattern, and matcher function.
1507 1518 missing or unknown prefixes are treated as literal matches.
1508 1519
1509 1520 helper for tests:
1510 1521 >>> def test(pattern, *tests):
1511 1522 ... kind, pattern, matcher = _stringmatcher(pattern)
1512 1523 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1513 1524
1514 1525 exact matching (no prefix):
1515 1526 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1516 1527 ('literal', 'abcdefg', [False, False, True])
1517 1528
1518 1529 regex matching ('re:' prefix)
1519 1530 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1520 1531 ('re', 'a.+b', [False, False, True])
1521 1532
1522 1533 force exact matches ('literal:' prefix)
1523 1534 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1524 1535 ('literal', 're:foobar', [False, True])
1525 1536
1526 1537 unknown prefixes are ignored and treated as literals
1527 1538 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1528 1539 ('literal', 'foo:bar', [False, False, True])
1529 1540 """
1530 1541 if pattern.startswith('re:'):
1531 1542 pattern = pattern[3:]
1532 1543 try:
1533 1544 regex = re.compile(pattern)
1534 1545 except re.error, e:
1535 1546 raise error.ParseError(_('invalid regular expression: %s')
1536 1547 % e)
1537 1548 return 're', pattern, regex.search
1538 1549 elif pattern.startswith('literal:'):
1539 1550 pattern = pattern[8:]
1540 1551 return 'literal', pattern, pattern.__eq__
1541 1552
1542 1553 def _substringmatcher(pattern):
1543 1554 kind, pattern, matcher = _stringmatcher(pattern)
1544 1555 if kind == 'literal':
1545 1556 matcher = lambda s: pattern in s
1546 1557 return kind, pattern, matcher
1547 1558
1548 1559 def tag(repo, subset, x):
1549 1560 """``tag([name])``
1550 1561 The specified tag by name, or all tagged revisions if no name is given.
1551 1562 """
1552 1563 # i18n: "tag" is a keyword
1553 1564 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1554 1565 cl = repo.changelog
1555 1566 if args:
1556 1567 pattern = getstring(args[0],
1557 1568 # i18n: "tag" is a keyword
1558 1569 _('the argument to tag must be a string'))
1559 1570 kind, pattern, matcher = _stringmatcher(pattern)
1560 1571 if kind == 'literal':
1561 1572 # avoid resolving all tags
1562 1573 tn = repo._tagscache.tags.get(pattern, None)
1563 1574 if tn is None:
1564 1575 raise util.Abort(_("tag '%s' does not exist") % pattern)
1565 1576 s = set([repo[tn].rev()])
1566 1577 else:
1567 1578 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1568 1579 else:
1569 1580 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1570 1581 return subset & s
1571 1582
1572 1583 def tagged(repo, subset, x):
1573 1584 return tag(repo, subset, x)
1574 1585
1575 1586 def unstable(repo, subset, x):
1576 1587 """``unstable()``
1577 1588 Non-obsolete changesets with obsolete ancestors.
1578 1589 """
1579 1590 # i18n: "unstable" is a keyword
1580 1591 getargs(x, 0, 0, _("unstable takes no arguments"))
1581 1592 unstables = obsmod.getrevs(repo, 'unstable')
1582 1593 return subset & unstables
1583 1594
1584 1595
1585 1596 def user(repo, subset, x):
1586 1597 """``user(string)``
1587 1598 User name contains string. The match is case-insensitive.
1588 1599
1589 1600 If `string` starts with `re:`, the remainder of the string is treated as
1590 1601 a regular expression. To match a user that actually contains `re:`, use
1591 1602 the prefix `literal:`.
1592 1603 """
1593 1604 return author(repo, subset, x)
1594 1605
1595 1606 # for internal use
1596 1607 def _list(repo, subset, x):
1597 1608 s = getstring(x, "internal error")
1598 1609 if not s:
1599 1610 return baseset([])
1600 1611 ls = [repo[r].rev() for r in s.split('\0')]
1601 1612 s = subset.set()
1602 1613 return baseset([r for r in ls if r in s])
1603 1614
1604 1615 # for internal use
1605 1616 def _intlist(repo, subset, x):
1606 1617 s = getstring(x, "internal error")
1607 1618 if not s:
1608 1619 return baseset([])
1609 1620 ls = [int(r) for r in s.split('\0')]
1610 1621 s = subset.set()
1611 1622 return baseset([r for r in ls if r in s])
1612 1623
1613 1624 # for internal use
1614 1625 def _hexlist(repo, subset, x):
1615 1626 s = getstring(x, "internal error")
1616 1627 if not s:
1617 1628 return baseset([])
1618 1629 cl = repo.changelog
1619 1630 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
1620 1631 s = subset.set()
1621 1632 return baseset([r for r in ls if r in s])
1622 1633
1623 1634 symbols = {
1624 1635 "adds": adds,
1625 1636 "all": getall,
1626 1637 "ancestor": ancestor,
1627 1638 "ancestors": ancestors,
1628 1639 "_firstancestors": _firstancestors,
1629 1640 "author": author,
1630 1641 "only": only,
1631 1642 "bisect": bisect,
1632 1643 "bisected": bisected,
1633 1644 "bookmark": bookmark,
1634 1645 "branch": branch,
1635 1646 "branchpoint": branchpoint,
1636 1647 "bumped": bumped,
1637 1648 "bundle": bundle,
1638 1649 "children": children,
1639 1650 "closed": closed,
1640 1651 "contains": contains,
1641 1652 "converted": converted,
1642 1653 "date": date,
1643 1654 "desc": desc,
1644 1655 "descendants": descendants,
1645 1656 "_firstdescendants": _firstdescendants,
1646 1657 "destination": destination,
1647 1658 "divergent": divergent,
1648 1659 "draft": draft,
1649 1660 "extinct": extinct,
1650 1661 "extra": extra,
1651 1662 "file": hasfile,
1652 1663 "filelog": filelog,
1653 1664 "first": first,
1654 1665 "follow": follow,
1655 1666 "_followfirst": _followfirst,
1656 1667 "grep": grep,
1657 1668 "head": head,
1658 1669 "heads": heads,
1659 1670 "hidden": hidden,
1660 1671 "id": node_,
1661 1672 "keyword": keyword,
1662 1673 "last": last,
1663 1674 "limit": limit,
1664 1675 "_matchfiles": _matchfiles,
1665 1676 "max": maxrev,
1666 1677 "merge": merge,
1667 1678 "min": minrev,
1668 1679 "_missingancestors": _missingancestors,
1669 1680 "modifies": modifies,
1670 1681 "obsolete": obsolete,
1671 1682 "origin": origin,
1672 1683 "outgoing": outgoing,
1673 1684 "p1": p1,
1674 1685 "p2": p2,
1675 1686 "parents": parents,
1676 1687 "present": present,
1677 1688 "public": public,
1678 1689 "remote": remote,
1679 1690 "removes": removes,
1680 1691 "rev": rev,
1681 1692 "reverse": reverse,
1682 1693 "roots": roots,
1683 1694 "sort": sort,
1684 1695 "secret": secret,
1685 1696 "matching": matching,
1686 1697 "tag": tag,
1687 1698 "tagged": tagged,
1688 1699 "user": user,
1689 1700 "unstable": unstable,
1690 1701 "_list": _list,
1691 1702 "_intlist": _intlist,
1692 1703 "_hexlist": _hexlist,
1693 1704 }
1694 1705
1695 1706 # symbols which can't be used for a DoS attack for any given input
1696 1707 # (e.g. those which accept regexes as plain strings shouldn't be included)
1697 1708 # functions that just return a lot of changesets (like all) don't count here
1698 1709 safesymbols = set([
1699 1710 "adds",
1700 1711 "all",
1701 1712 "ancestor",
1702 1713 "ancestors",
1703 1714 "_firstancestors",
1704 1715 "author",
1705 1716 "bisect",
1706 1717 "bisected",
1707 1718 "bookmark",
1708 1719 "branch",
1709 1720 "branchpoint",
1710 1721 "bumped",
1711 1722 "bundle",
1712 1723 "children",
1713 1724 "closed",
1714 1725 "converted",
1715 1726 "date",
1716 1727 "desc",
1717 1728 "descendants",
1718 1729 "_firstdescendants",
1719 1730 "destination",
1720 1731 "divergent",
1721 1732 "draft",
1722 1733 "extinct",
1723 1734 "extra",
1724 1735 "file",
1725 1736 "filelog",
1726 1737 "first",
1727 1738 "follow",
1728 1739 "_followfirst",
1729 1740 "head",
1730 1741 "heads",
1731 1742 "hidden",
1732 1743 "id",
1733 1744 "keyword",
1734 1745 "last",
1735 1746 "limit",
1736 1747 "_matchfiles",
1737 1748 "max",
1738 1749 "merge",
1739 1750 "min",
1740 1751 "_missingancestors",
1741 1752 "modifies",
1742 1753 "obsolete",
1743 1754 "origin",
1744 1755 "outgoing",
1745 1756 "p1",
1746 1757 "p2",
1747 1758 "parents",
1748 1759 "present",
1749 1760 "public",
1750 1761 "remote",
1751 1762 "removes",
1752 1763 "rev",
1753 1764 "reverse",
1754 1765 "roots",
1755 1766 "sort",
1756 1767 "secret",
1757 1768 "matching",
1758 1769 "tag",
1759 1770 "tagged",
1760 1771 "user",
1761 1772 "unstable",
1762 1773 "_list",
1763 1774 "_intlist",
1764 1775 "_hexlist",
1765 1776 ])
1766 1777
1767 1778 methods = {
1768 1779 "range": rangeset,
1769 1780 "dagrange": dagrange,
1770 1781 "string": stringset,
1771 1782 "symbol": symbolset,
1772 1783 "and": andset,
1773 1784 "or": orset,
1774 1785 "not": notset,
1775 1786 "list": listset,
1776 1787 "func": func,
1777 1788 "ancestor": ancestorspec,
1778 1789 "parent": parentspec,
1779 1790 "parentpost": p1,
1780 1791 }
1781 1792
1782 1793 def optimize(x, small):
1783 1794 if x is None:
1784 1795 return 0, x
1785 1796
1786 1797 smallbonus = 1
1787 1798 if small:
1788 1799 smallbonus = .5
1789 1800
1790 1801 op = x[0]
1791 1802 if op == 'minus':
1792 1803 return optimize(('and', x[1], ('not', x[2])), small)
1793 1804 elif op == 'dagrangepre':
1794 1805 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1795 1806 elif op == 'dagrangepost':
1796 1807 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1797 1808 elif op == 'rangepre':
1798 1809 return optimize(('range', ('string', '0'), x[1]), small)
1799 1810 elif op == 'rangepost':
1800 1811 return optimize(('range', x[1], ('string', 'tip')), small)
1801 1812 elif op == 'negate':
1802 1813 return optimize(('string',
1803 1814 '-' + getstring(x[1], _("can't negate that"))), small)
1804 1815 elif op in 'string symbol negate':
1805 1816 return smallbonus, x # single revisions are small
1806 1817 elif op == 'and':
1807 1818 wa, ta = optimize(x[1], True)
1808 1819 wb, tb = optimize(x[2], True)
1809 1820
1810 1821 # (::x and not ::y)/(not ::y and ::x) have a fast path
1811 1822 def ismissingancestors(revs, bases):
1812 1823 return (
1813 1824 revs[0] == 'func'
1814 1825 and getstring(revs[1], _('not a symbol')) == 'ancestors'
1815 1826 and bases[0] == 'not'
1816 1827 and bases[1][0] == 'func'
1817 1828 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
1818 1829
1819 1830 w = min(wa, wb)
1820 1831 if ismissingancestors(ta, tb):
1821 1832 return w, ('func', ('symbol', '_missingancestors'),
1822 1833 ('list', ta[2], tb[1][2]))
1823 1834 if ismissingancestors(tb, ta):
1824 1835 return w, ('func', ('symbol', '_missingancestors'),
1825 1836 ('list', tb[2], ta[1][2]))
1826 1837
1827 1838 if wa > wb:
1828 1839 return w, (op, tb, ta)
1829 1840 return w, (op, ta, tb)
1830 1841 elif op == 'or':
1831 1842 wa, ta = optimize(x[1], False)
1832 1843 wb, tb = optimize(x[2], False)
1833 1844 if wb < wa:
1834 1845 wb, wa = wa, wb
1835 1846 return max(wa, wb), (op, ta, tb)
1836 1847 elif op == 'not':
1837 1848 o = optimize(x[1], not small)
1838 1849 return o[0], (op, o[1])
1839 1850 elif op == 'parentpost':
1840 1851 o = optimize(x[1], small)
1841 1852 return o[0], (op, o[1])
1842 1853 elif op == 'group':
1843 1854 return optimize(x[1], small)
1844 1855 elif op in 'dagrange range list parent ancestorspec':
1845 1856 if op == 'parent':
1846 1857 # x^:y means (x^) : y, not x ^ (:y)
1847 1858 post = ('parentpost', x[1])
1848 1859 if x[2][0] == 'dagrangepre':
1849 1860 return optimize(('dagrange', post, x[2][1]), small)
1850 1861 elif x[2][0] == 'rangepre':
1851 1862 return optimize(('range', post, x[2][1]), small)
1852 1863
1853 1864 wa, ta = optimize(x[1], small)
1854 1865 wb, tb = optimize(x[2], small)
1855 1866 return wa + wb, (op, ta, tb)
1856 1867 elif op == 'func':
1857 1868 f = getstring(x[1], _("not a symbol"))
1858 1869 wa, ta = optimize(x[2], small)
1859 1870 if f in ("author branch closed date desc file grep keyword "
1860 1871 "outgoing user"):
1861 1872 w = 10 # slow
1862 1873 elif f in "modifies adds removes":
1863 1874 w = 30 # slower
1864 1875 elif f == "contains":
1865 1876 w = 100 # very slow
1866 1877 elif f == "ancestor":
1867 1878 w = 1 * smallbonus
1868 1879 elif f in "reverse limit first":
1869 1880 w = 0
1870 1881 elif f in "sort":
1871 1882 w = 10 # assume most sorts look at changelog
1872 1883 else:
1873 1884 w = 1
1874 1885 return w + wa, (op, x[1], ta)
1875 1886 return 1, x
1876 1887
1877 1888 _aliasarg = ('func', ('symbol', '_aliasarg'))
1878 1889 def _getaliasarg(tree):
1879 1890 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1880 1891 return X, None otherwise.
1881 1892 """
1882 1893 if (len(tree) == 3 and tree[:2] == _aliasarg
1883 1894 and tree[2][0] == 'string'):
1884 1895 return tree[2][1]
1885 1896 return None
1886 1897
1887 1898 def _checkaliasarg(tree, known=None):
1888 1899 """Check tree contains no _aliasarg construct or only ones which
1889 1900 value is in known. Used to avoid alias placeholders injection.
1890 1901 """
1891 1902 if isinstance(tree, tuple):
1892 1903 arg = _getaliasarg(tree)
1893 1904 if arg is not None and (not known or arg not in known):
1894 1905 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1895 1906 for t in tree:
1896 1907 _checkaliasarg(t, known)
1897 1908
1898 1909 class revsetalias(object):
1899 1910 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1900 1911 args = None
1901 1912
1902 1913 def __init__(self, name, value):
1903 1914 '''Aliases like:
1904 1915
1905 1916 h = heads(default)
1906 1917 b($1) = ancestors($1) - ancestors(default)
1907 1918 '''
1908 1919 m = self.funcre.search(name)
1909 1920 if m:
1910 1921 self.name = m.group(1)
1911 1922 self.tree = ('func', ('symbol', m.group(1)))
1912 1923 self.args = [x.strip() for x in m.group(2).split(',')]
1913 1924 for arg in self.args:
1914 1925 # _aliasarg() is an unknown symbol only used separate
1915 1926 # alias argument placeholders from regular strings.
1916 1927 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1917 1928 else:
1918 1929 self.name = name
1919 1930 self.tree = ('symbol', name)
1920 1931
1921 1932 self.replacement, pos = parse(value)
1922 1933 if pos != len(value):
1923 1934 raise error.ParseError(_('invalid token'), pos)
1924 1935 # Check for placeholder injection
1925 1936 _checkaliasarg(self.replacement, self.args)
1926 1937
1927 1938 def _getalias(aliases, tree):
1928 1939 """If tree looks like an unexpanded alias, return it. Return None
1929 1940 otherwise.
1930 1941 """
1931 1942 if isinstance(tree, tuple) and tree:
1932 1943 if tree[0] == 'symbol' and len(tree) == 2:
1933 1944 name = tree[1]
1934 1945 alias = aliases.get(name)
1935 1946 if alias and alias.args is None and alias.tree == tree:
1936 1947 return alias
1937 1948 if tree[0] == 'func' and len(tree) > 1:
1938 1949 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1939 1950 name = tree[1][1]
1940 1951 alias = aliases.get(name)
1941 1952 if alias and alias.args is not None and alias.tree == tree[:2]:
1942 1953 return alias
1943 1954 return None
1944 1955
1945 1956 def _expandargs(tree, args):
1946 1957 """Replace _aliasarg instances with the substitution value of the
1947 1958 same name in args, recursively.
1948 1959 """
1949 1960 if not tree or not isinstance(tree, tuple):
1950 1961 return tree
1951 1962 arg = _getaliasarg(tree)
1952 1963 if arg is not None:
1953 1964 return args[arg]
1954 1965 return tuple(_expandargs(t, args) for t in tree)
1955 1966
1956 1967 def _expandaliases(aliases, tree, expanding, cache):
1957 1968 """Expand aliases in tree, recursively.
1958 1969
1959 1970 'aliases' is a dictionary mapping user defined aliases to
1960 1971 revsetalias objects.
1961 1972 """
1962 1973 if not isinstance(tree, tuple):
1963 1974 # Do not expand raw strings
1964 1975 return tree
1965 1976 alias = _getalias(aliases, tree)
1966 1977 if alias is not None:
1967 1978 if alias in expanding:
1968 1979 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1969 1980 'detected') % alias.name)
1970 1981 expanding.append(alias)
1971 1982 if alias.name not in cache:
1972 1983 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1973 1984 expanding, cache)
1974 1985 result = cache[alias.name]
1975 1986 expanding.pop()
1976 1987 if alias.args is not None:
1977 1988 l = getlist(tree[2])
1978 1989 if len(l) != len(alias.args):
1979 1990 raise error.ParseError(
1980 1991 _('invalid number of arguments: %s') % len(l))
1981 1992 l = [_expandaliases(aliases, a, [], cache) for a in l]
1982 1993 result = _expandargs(result, dict(zip(alias.args, l)))
1983 1994 else:
1984 1995 result = tuple(_expandaliases(aliases, t, expanding, cache)
1985 1996 for t in tree)
1986 1997 return result
1987 1998
1988 1999 def findaliases(ui, tree):
1989 2000 _checkaliasarg(tree)
1990 2001 aliases = {}
1991 2002 for k, v in ui.configitems('revsetalias'):
1992 2003 alias = revsetalias(k, v)
1993 2004 aliases[alias.name] = alias
1994 2005 return _expandaliases(aliases, tree, [], {})
1995 2006
1996 2007 def parse(spec):
1997 2008 p = parser.parser(tokenize, elements)
1998 2009 return p.parse(spec)
1999 2010
2000 2011 def match(ui, spec):
2001 2012 if not spec:
2002 2013 raise error.ParseError(_("empty query"))
2003 2014 tree, pos = parse(spec)
2004 2015 if (pos != len(spec)):
2005 2016 raise error.ParseError(_("invalid token"), pos)
2006 2017 if ui:
2007 2018 tree = findaliases(ui, tree)
2008 2019 weight, tree = optimize(tree, True)
2009 2020 def mfunc(repo, subset):
2010 2021 if util.safehasattr(subset, 'set'):
2011 2022 return getset(repo, subset, tree)
2012 2023 return getset(repo, baseset(subset), tree)
2013 2024 return mfunc
2014 2025
2015 2026 def formatspec(expr, *args):
2016 2027 '''
2017 2028 This is a convenience function for using revsets internally, and
2018 2029 escapes arguments appropriately. Aliases are intentionally ignored
2019 2030 so that intended expression behavior isn't accidentally subverted.
2020 2031
2021 2032 Supported arguments:
2022 2033
2023 2034 %r = revset expression, parenthesized
2024 2035 %d = int(arg), no quoting
2025 2036 %s = string(arg), escaped and single-quoted
2026 2037 %b = arg.branch(), escaped and single-quoted
2027 2038 %n = hex(arg), single-quoted
2028 2039 %% = a literal '%'
2029 2040
2030 2041 Prefixing the type with 'l' specifies a parenthesized list of that type.
2031 2042
2032 2043 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2033 2044 '(10 or 11):: and ((this()) or (that()))'
2034 2045 >>> formatspec('%d:: and not %d::', 10, 20)
2035 2046 '10:: and not 20::'
2036 2047 >>> formatspec('%ld or %ld', [], [1])
2037 2048 "_list('') or 1"
2038 2049 >>> formatspec('keyword(%s)', 'foo\\xe9')
2039 2050 "keyword('foo\\\\xe9')"
2040 2051 >>> b = lambda: 'default'
2041 2052 >>> b.branch = b
2042 2053 >>> formatspec('branch(%b)', b)
2043 2054 "branch('default')"
2044 2055 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2045 2056 "root(_list('a\\x00b\\x00c\\x00d'))"
2046 2057 '''
2047 2058
2048 2059 def quote(s):
2049 2060 return repr(str(s))
2050 2061
2051 2062 def argtype(c, arg):
2052 2063 if c == 'd':
2053 2064 return str(int(arg))
2054 2065 elif c == 's':
2055 2066 return quote(arg)
2056 2067 elif c == 'r':
2057 2068 parse(arg) # make sure syntax errors are confined
2058 2069 return '(%s)' % arg
2059 2070 elif c == 'n':
2060 2071 return quote(node.hex(arg))
2061 2072 elif c == 'b':
2062 2073 return quote(arg.branch())
2063 2074
2064 2075 def listexp(s, t):
2065 2076 l = len(s)
2066 2077 if l == 0:
2067 2078 return "_list('')"
2068 2079 elif l == 1:
2069 2080 return argtype(t, s[0])
2070 2081 elif t == 'd':
2071 2082 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2072 2083 elif t == 's':
2073 2084 return "_list('%s')" % "\0".join(s)
2074 2085 elif t == 'n':
2075 2086 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2076 2087 elif t == 'b':
2077 2088 return "_list('%s')" % "\0".join(a.branch() for a in s)
2078 2089
2079 2090 m = l // 2
2080 2091 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2081 2092
2082 2093 ret = ''
2083 2094 pos = 0
2084 2095 arg = 0
2085 2096 while pos < len(expr):
2086 2097 c = expr[pos]
2087 2098 if c == '%':
2088 2099 pos += 1
2089 2100 d = expr[pos]
2090 2101 if d == '%':
2091 2102 ret += d
2092 2103 elif d in 'dsnbr':
2093 2104 ret += argtype(d, args[arg])
2094 2105 arg += 1
2095 2106 elif d == 'l':
2096 2107 # a list of some type
2097 2108 pos += 1
2098 2109 d = expr[pos]
2099 2110 ret += listexp(list(args[arg]), d)
2100 2111 arg += 1
2101 2112 else:
2102 2113 raise util.Abort('unexpected revspec format character %s' % d)
2103 2114 else:
2104 2115 ret += c
2105 2116 pos += 1
2106 2117
2107 2118 return ret
2108 2119
2109 2120 def prettyformat(tree):
2110 2121 def _prettyformat(tree, level, lines):
2111 2122 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2112 2123 lines.append((level, str(tree)))
2113 2124 else:
2114 2125 lines.append((level, '(%s' % tree[0]))
2115 2126 for s in tree[1:]:
2116 2127 _prettyformat(s, level + 1, lines)
2117 2128 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
2118 2129
2119 2130 lines = []
2120 2131 _prettyformat(tree, 0, lines)
2121 2132 output = '\n'.join((' '*l + s) for l, s in lines)
2122 2133 return output
2123 2134
2124 2135 def depth(tree):
2125 2136 if isinstance(tree, tuple):
2126 2137 return max(map(depth, tree)) + 1
2127 2138 else:
2128 2139 return 0
2129 2140
2130 2141 def funcsused(tree):
2131 2142 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2132 2143 return set()
2133 2144 else:
2134 2145 funcs = set()
2135 2146 for s in tree[1:]:
2136 2147 funcs |= funcsused(s)
2137 2148 if tree[0] == 'func':
2138 2149 funcs.add(tree[1][1])
2139 2150 return funcs
2140 2151
2141 2152 class baseset(list):
2142 2153 """Basic data structure that represents a revset and contains the basic
2143 2154 operation that it should be able to perform.
2144 2155 """
2145 2156 def __init__(self, data):
2146 2157 super(baseset, self).__init__(data)
2147 2158 self._set = None
2148 2159
2149 2160 def ascending(self):
2150 2161 self.sort()
2151 2162
2152 2163 def descending(self):
2153 2164 self.sort(reverse=True)
2154 2165
2155 2166 def set(self):
2156 2167 if not self._set:
2157 2168 self._set = set(self)
2158 2169 return self._set
2159 2170
2160 2171 def __sub__(self, x):
2161 2172 if isinstance(x, baseset):
2162 2173 s = x.set()
2163 2174 else:
2164 2175 s = set(x)
2165 2176 return baseset(self.set() - s)
2166 2177
2167 2178 def __and__(self, x):
2168 2179 if isinstance(x, baseset):
2169 2180 x = x.set()
2170 2181 return baseset([y for y in self if y in x])
2171 2182
2172 2183 def __add__(self, x):
2173 2184 s = self.set()
2174 2185 l = [r for r in x if r not in s]
2175 2186 return baseset(list(self) + l)
2176 2187
2177 2188 def filter(self, l):
2178 2189 return lazyset(self, l)
2179 2190
2180 2191 class lazyset(object):
2181 2192 """Duck type for baseset class which iterates lazily over the revisions in
2182 2193 the subset and contains a function which tests for membership in the
2183 2194 revset
2184 2195 """
2185 2196 def __init__(self, subset, condition=lambda x: True):
2186 2197 self._subset = subset
2187 2198 self._condition = condition
2188 2199 self._cache = {}
2189 2200
2190 2201 def ascending(self):
2191 2202 self._subset.sort()
2192 2203
2193 2204 def descending(self):
2194 2205 self._subset.sort(reverse=True)
2195 2206
2196 2207 def __contains__(self, x):
2197 2208 c = self._cache
2198 2209 if x not in c:
2199 2210 c[x] = x in self._subset and self._condition(x)
2200 2211 return c[x]
2201 2212
2202 2213 def __iter__(self):
2203 2214 cond = self._condition
2204 2215 for x in self._subset:
2205 2216 if cond(x):
2206 2217 yield x
2207 2218
2208 2219 def __and__(self, x):
2209 2220 return lazyset(self, lambda r: r in x)
2210 2221
2211 2222 def __sub__(self, x):
2212 2223 return lazyset(self, lambda r: r not in x)
2213 2224
2214 2225 def __add__(self, x):
2215 2226 def iterates():
2216 2227 for r in self:
2217 2228 yield r
2218 2229 for r in x:
2219 2230 if r not in self:
2220 2231 yield r
2221 2232
2222 2233 return lazyset(generatorset(iterates()))
2223 2234
2224 2235 def __nonzero__(self):
2225 2236 for r in self:
2226 2237 return True
2227 2238 return False
2228 2239
2229 2240 def __len__(self):
2230 2241 # Basic implementation to be changed in future patches.
2231 2242 l = baseset([r for r in self])
2232 2243 return len(l)
2233 2244
2234 2245 def __getitem__(self, x):
2235 2246 # Basic implementation to be changed in future patches.
2236 2247 l = baseset([r for r in self])
2237 2248 return l[x]
2238 2249
2239 2250 def sort(self, reverse=False):
2240 2251 # Basic implementation to be changed in future patches.
2241 2252 self._subset = baseset(self._subset)
2242 2253 self._subset.sort(reverse=reverse)
2243 2254
2244 2255 def reverse(self):
2245 2256 self._subset.reverse()
2246 2257
2247 2258 def set(self):
2248 2259 return set([r for r in self])
2249 2260
2250 2261 def filter(self, l):
2251 2262 return lazyset(self, l)
2252 2263
2253 2264 class orderedlazyset(lazyset):
2254 2265 """Subclass of lazyset which subset can be ordered either ascending or
2255 2266 descendingly
2256 2267 """
2257 2268 def __init__(self, subset, condition, ascending=True):
2258 2269 super(orderedlazyset, self).__init__(subset, condition)
2259 2270 self._ascending = ascending
2260 2271
2261 2272 def filter(self, l):
2262 2273 return orderedlazyset(self, l, ascending=self._ascending)
2263 2274
2264 2275 def ascending(self):
2265 2276 if not self._ascending:
2266 2277 self.reverse()
2267 2278
2268 2279 def descending(self):
2269 2280 if self._ascending:
2270 2281 self.reverse()
2271 2282
2272 2283 def __and__(self, x):
2273 2284 return orderedlazyset(self, lambda r: r in x,
2274 2285 ascending=self._ascending)
2275 2286
2276 2287 def __sub__(self, x):
2277 2288 return orderedlazyset(self, lambda r: r not in x,
2278 2289 ascending=self._ascending)
2279 2290
2280 2291 def sort(self, reverse=False):
2281 2292 if reverse:
2282 2293 if self._ascending:
2283 2294 self._subset.sort(reverse=reverse)
2284 2295 else:
2285 2296 if not self._ascending:
2286 2297 self._subset.sort(reverse=reverse)
2287 2298 self._ascending = not reverse
2288 2299
2289 2300 def reverse(self):
2290 2301 self._subset.reverse()
2291 2302 self._ascending = not self._ascending
2292 2303
2293 2304 class generatorset(object):
2294 2305 """Wrapper structure for generators that provides lazy membership and can
2295 2306 be iterated more than once.
2296 2307 When asked for membership it generates values until either it finds the
2297 2308 requested one or has gone through all the elements in the generator
2298 2309 """
2299 2310 def __init__(self, gen):
2300 2311 self._gen = gen
2301 2312 self._iter = iter(gen)
2302 2313 self._cache = {}
2303 2314 self._genlist = baseset([])
2304 2315 self._iterated = False
2305 2316
2306 2317 def __contains__(self, x):
2307 2318 if x in self._cache:
2308 2319 return self._cache[x]
2309 2320
2310 2321 for l in self:
2311 2322 if l == x:
2312 2323 return True
2313 2324
2314 2325 self._cache[x] = False
2315 2326 return False
2316 2327
2317 2328 def __iter__(self):
2318 2329 if self._iterated:
2319 2330 for l in self._genlist:
2320 2331 yield l
2321 2332 else:
2322 2333 self._iterated = True
2323 2334
2324 2335 for item in self._gen:
2325 2336 self._cache[item] = True
2326 2337 self._genlist.append(item)
2327 2338 yield item
2328 2339
2329 2340 def set(self):
2330 2341 return self
2331 2342
2332 2343 class ascgeneratorset(generatorset):
2333 2344 """ Same structure as generatorset but stops iterating after it goes past
2334 2345 the value when asked for membership and the element is not contained
2335 2346 """
2336 2347 def __contains__(self, x):
2337 2348 if x in self._cache:
2338 2349 return self._cache[x]
2339 2350
2340 2351 for l in self:
2341 2352 if l == x:
2342 2353 return True
2343 2354 if l > x:
2344 2355 break
2345 2356
2346 2357 self._cache[x] = False
2347 2358 return False
2348 2359
2349 2360 class descgeneratorset(generatorset):
2350 2361 """ Same structure as generatorset but stops iterating after it goes past
2351 2362 the value when asked for membership and the element is not contained
2352 2363 """
2353 2364 def __contains__(self, x):
2354 2365 if x in self._cache:
2355 2366 return self._cache[x]
2356 2367
2357 2368 for l in self:
2358 2369 if l == x:
2359 2370 return True
2360 2371 if l < x:
2361 2372 break
2362 2373
2363 2374 self._cache[x] = False
2364 2375 return False
2365 2376
2366 2377 class spanset(object):
2367 2378 """Duck type for baseset class which represents a range of revisions and
2368 2379 can work lazily and without having all the range in memory
2369 2380 """
2370 2381 def __init__(self, repo, start=0, end=None):
2371 2382 self._start = start
2372 2383 if end is not None:
2373 2384 self._end = end
2374 2385 else:
2375 2386 self._end = len(repo)
2376 2387 self._hiddenrevs = repo.changelog.filteredrevs
2377 2388
2378 2389 def ascending(self):
2379 2390 if self._start > self._end:
2380 2391 self.reverse()
2381 2392
2382 2393 def descending(self):
2383 2394 if self._start < self._end:
2384 2395 self.reverse()
2385 2396
2386 2397 def _contained(self, rev):
2387 2398 return (rev <= self._start and rev > self._end) or (rev >= self._start
2388 2399 and rev < self._end)
2389 2400
2390 2401 def __iter__(self):
2391 2402 if self._start <= self._end:
2392 2403 iterrange = xrange(self._start, self._end)
2393 2404 else:
2394 2405 iterrange = xrange(self._start, self._end, -1)
2395 2406
2396 2407 if self._hiddenrevs:
2397 2408 s = self._hiddenrevs
2398 2409 for r in iterrange:
2399 2410 if r not in s:
2400 2411 yield r
2401 2412 else:
2402 2413 for r in iterrange:
2403 2414 yield r
2404 2415
2405 2416 def __contains__(self, x):
2406 2417 return self._contained(x) and not (self._hiddenrevs and rev in
2407 2418 self._hiddenrevs)
2408 2419
2409 2420 def __and__(self, x):
2410 2421 if isinstance(x, baseset):
2411 2422 x = x.set()
2412 2423 if self._start <= self._end:
2413 2424 return orderedlazyset(self, lambda r: r in x)
2414 2425 else:
2415 2426 return orderedlazyset(self, lambda r: r in x, ascending=False)
2416 2427
2417 2428 def __sub__(self, x):
2418 2429 if isinstance(x, baseset):
2419 2430 x = x.set()
2420 2431 if self._start <= self._end:
2421 2432 return orderedlazyset(self, lambda r: r not in x)
2422 2433 else:
2423 2434 return orderedlazyset(self, lambda r: r not in x, ascending=False)
2424 2435
2425 2436 def __add__(self, x):
2426 2437 def iterates():
2427 2438 for r in self:
2428 2439 yield r
2429 2440 for r in x:
2430 2441 if r not in self:
2431 2442 yield r
2432 2443
2433 2444 return lazyset(generatorset(iterates()))
2434 2445
2435 2446 def __len__(self):
2436 2447 if not self._hiddenrevs:
2437 2448 return abs(self._end - self._start)
2438 2449 else:
2439 2450 count = 0
2440 2451 for rev in self._hiddenrevs:
2441 2452 if self._contained(rev):
2442 2453 count += 1
2443 2454 return abs(self._end - self._start) - count
2444 2455
2445 2456 def __getitem__(self, x):
2446 2457 # Basic implementation to be changed in future patches.
2447 2458 l = baseset([r for r in self])
2448 2459 return l[x]
2449 2460
2450 2461 def sort(self, reverse=False):
2451 2462 # Basic implementation to be changed in future patches.
2452 2463 if reverse:
2453 2464 self.reverse()
2454 2465
2455 2466 def reverse(self):
2456 2467 if self._start <= self._end:
2457 2468 self._start, self._end = self._end - 1, self._start - 1
2458 2469 else:
2459 2470 self._start, self._end = self._end + 1, self._start + 1
2460 2471
2461 2472 def set(self):
2462 2473 return self
2463 2474
2464 2475 def filter(self, l):
2465 2476 if self._start <= self._end:
2466 2477 return orderedlazyset(self, l)
2467 2478 else:
2468 2479 return orderedlazyset(self, l, ascending=False)
2469 2480
2470 2481 # tell hggettext to extract docstrings from these functions:
2471 2482 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now