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