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