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