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