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