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