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