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