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