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