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