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