##// END OF EJS Templates
revset: fix aliases with 0 or more than 2 parameters...
Mads Kiilerich -
r14723:b9faf94e stable
parent child Browse files
Show More
@@ -1,1045 +1,1021
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
10 10 import bookmarks as bookmarksmod
11 11 import match as matchmod
12 12 from i18n import _
13 13
14 14 elements = {
15 15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 16 "~": (18, None, ("ancestor", 18)),
17 17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
18 18 "-": (5, ("negate", 19), ("minus", 5)),
19 19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
20 20 ("dagrangepost", 17)),
21 21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
22 22 ("dagrangepost", 17)),
23 23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
24 24 "not": (10, ("not", 10)),
25 25 "!": (10, ("not", 10)),
26 26 "and": (5, None, ("and", 5)),
27 27 "&": (5, None, ("and", 5)),
28 28 "or": (4, None, ("or", 4)),
29 29 "|": (4, None, ("or", 4)),
30 30 "+": (4, None, ("or", 4)),
31 31 ",": (2, None, ("list", 2)),
32 32 ")": (0, None, None),
33 33 "symbol": (0, ("symbol",), None),
34 34 "string": (0, ("string",), None),
35 35 "end": (0, None, None),
36 36 }
37 37
38 38 keywords = set(['and', 'or', 'not'])
39 39
40 40 def tokenize(program):
41 41 pos, l = 0, len(program)
42 42 while pos < l:
43 43 c = program[pos]
44 44 if c.isspace(): # skip inter-token whitespace
45 45 pass
46 46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
47 47 yield ('::', None, pos)
48 48 pos += 1 # skip ahead
49 49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
50 50 yield ('..', None, pos)
51 51 pos += 1 # skip ahead
52 52 elif c in "():,-|&+!~^": # handle simple operators
53 53 yield (c, None, pos)
54 54 elif (c in '"\'' or c == 'r' and
55 55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
56 56 if c == 'r':
57 57 pos += 1
58 58 c = program[pos]
59 59 decode = lambda x: x
60 60 else:
61 61 decode = lambda x: x.decode('string-escape')
62 62 pos += 1
63 63 s = pos
64 64 while pos < l: # find closing quote
65 65 d = program[pos]
66 66 if d == '\\': # skip over escaped characters
67 67 pos += 2
68 68 continue
69 69 if d == c:
70 70 yield ('string', decode(program[s:pos]), s)
71 71 break
72 72 pos += 1
73 73 else:
74 74 raise error.ParseError(_("unterminated string"), s)
75 75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
76 76 s = pos
77 77 pos += 1
78 78 while pos < l: # find end of symbol
79 79 d = program[pos]
80 80 if not (d.isalnum() or d in "._" or ord(d) > 127):
81 81 break
82 82 if d == '.' and program[pos - 1] == '.': # special case for ..
83 83 pos -= 1
84 84 break
85 85 pos += 1
86 86 sym = program[s:pos]
87 87 if sym in keywords: # operator keywords
88 88 yield (sym, None, s)
89 89 else:
90 90 yield ('symbol', sym, s)
91 91 pos -= 1
92 92 else:
93 93 raise error.ParseError(_("syntax error"), pos)
94 94 pos += 1
95 95 yield ('end', None, pos)
96 96
97 97 # helpers
98 98
99 99 def getstring(x, err):
100 100 if x and (x[0] == 'string' or x[0] == 'symbol'):
101 101 return x[1]
102 102 raise error.ParseError(err)
103 103
104 104 def getlist(x):
105 105 if not x:
106 106 return []
107 107 if x[0] == 'list':
108 108 return getlist(x[1]) + [x[2]]
109 109 return [x]
110 110
111 111 def getargs(x, min, max, err):
112 112 l = getlist(x)
113 113 if len(l) < min or len(l) > max:
114 114 raise error.ParseError(err)
115 115 return l
116 116
117 117 def getset(repo, subset, x):
118 118 if not x:
119 119 raise error.ParseError(_("missing argument"))
120 120 return methods[x[0]](repo, subset, *x[1:])
121 121
122 122 # operator methods
123 123
124 124 def stringset(repo, subset, x):
125 125 x = repo[x].rev()
126 126 if x == -1 and len(subset) == len(repo):
127 127 return [-1]
128 128 if len(subset) == len(repo) or x in subset:
129 129 return [x]
130 130 return []
131 131
132 132 def symbolset(repo, subset, x):
133 133 if x in symbols:
134 134 raise error.ParseError(_("can't use %s here") % x)
135 135 return stringset(repo, subset, x)
136 136
137 137 def rangeset(repo, subset, x, y):
138 138 m = getset(repo, subset, x)
139 139 if not m:
140 140 m = getset(repo, range(len(repo)), x)
141 141
142 142 n = getset(repo, subset, y)
143 143 if not n:
144 144 n = getset(repo, range(len(repo)), y)
145 145
146 146 if not m or not n:
147 147 return []
148 148 m, n = m[0], n[-1]
149 149
150 150 if m < n:
151 151 r = range(m, n + 1)
152 152 else:
153 153 r = range(m, n - 1, -1)
154 154 s = set(subset)
155 155 return [x for x in r if x in s]
156 156
157 157 def andset(repo, subset, x, y):
158 158 return getset(repo, getset(repo, subset, x), y)
159 159
160 160 def orset(repo, subset, x, y):
161 161 xl = getset(repo, subset, x)
162 162 s = set(xl)
163 163 yl = getset(repo, [r for r in subset if r not in s], y)
164 164 return xl + yl
165 165
166 166 def notset(repo, subset, x):
167 167 s = set(getset(repo, subset, x))
168 168 return [r for r in subset if r not in s]
169 169
170 170 def listset(repo, subset, a, b):
171 171 raise error.ParseError(_("can't use a list in this context"))
172 172
173 173 def func(repo, subset, a, b):
174 174 if a[0] == 'symbol' and a[1] in symbols:
175 175 return symbols[a[1]](repo, subset, b)
176 176 raise error.ParseError(_("not a function: %s") % a[1])
177 177
178 178 # functions
179 179
180 180 def adds(repo, subset, x):
181 181 """``adds(pattern)``
182 182 Changesets that add a file matching pattern.
183 183 """
184 184 # i18n: "adds" is a keyword
185 185 pat = getstring(x, _("adds requires a pattern"))
186 186 return checkstatus(repo, subset, pat, 1)
187 187
188 188 def ancestor(repo, subset, x):
189 189 """``ancestor(single, single)``
190 190 Greatest common ancestor of the two changesets.
191 191 """
192 192 # i18n: "ancestor" is a keyword
193 193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
194 194 r = range(len(repo))
195 195 a = getset(repo, r, l[0])
196 196 b = getset(repo, r, l[1])
197 197 if len(a) != 1 or len(b) != 1:
198 198 # i18n: "ancestor" is a keyword
199 199 raise error.ParseError(_("ancestor arguments must be single revisions"))
200 200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
201 201
202 202 return [r for r in an if r in subset]
203 203
204 204 def ancestors(repo, subset, x):
205 205 """``ancestors(set)``
206 206 Changesets that are ancestors of a changeset in set.
207 207 """
208 208 args = getset(repo, range(len(repo)), x)
209 209 if not args:
210 210 return []
211 211 s = set(repo.changelog.ancestors(*args)) | set(args)
212 212 return [r for r in subset if r in s]
213 213
214 214 def ancestorspec(repo, subset, x, n):
215 215 """``set~n``
216 216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
217 217 """
218 218 try:
219 219 n = int(n[1])
220 220 except ValueError:
221 221 raise error.ParseError(_("~ expects a number"))
222 222 ps = set()
223 223 cl = repo.changelog
224 224 for r in getset(repo, subset, x):
225 225 for i in range(n):
226 226 r = cl.parentrevs(r)[0]
227 227 ps.add(r)
228 228 return [r for r in subset if r in ps]
229 229
230 230 def author(repo, subset, x):
231 231 """``author(string)``
232 232 Alias for ``user(string)``.
233 233 """
234 234 # i18n: "author" is a keyword
235 235 n = getstring(x, _("author requires a string")).lower()
236 236 return [r for r in subset if n in repo[r].user().lower()]
237 237
238 238 def bisected(repo, subset, x):
239 239 """``bisected(string)``
240 240 Changesets marked in the specified bisect state (good, bad, skip).
241 241 """
242 242 state = getstring(x, _("bisect requires a string")).lower()
243 243 if state not in ('good', 'bad', 'skip', 'unknown'):
244 244 raise error.ParseError(_('invalid bisect state'))
245 245 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
246 246 return [r for r in subset if r in marked]
247 247
248 248 def bookmark(repo, subset, x):
249 249 """``bookmark([name])``
250 250 The named bookmark or all bookmarks.
251 251 """
252 252 # i18n: "bookmark" is a keyword
253 253 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
254 254 if args:
255 255 bm = getstring(args[0],
256 256 # i18n: "bookmark" is a keyword
257 257 _('the argument to bookmark must be a string'))
258 258 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
259 259 if not bmrev:
260 260 raise util.Abort(_("bookmark '%s' does not exist") % bm)
261 261 bmrev = repo[bmrev].rev()
262 262 return [r for r in subset if r == bmrev]
263 263 bms = set([repo[r].rev()
264 264 for r in bookmarksmod.listbookmarks(repo).values()])
265 265 return [r for r in subset if r in bms]
266 266
267 267 def branch(repo, subset, x):
268 268 """``branch(string or set)``
269 269 All changesets belonging to the given branch or the branches of the given
270 270 changesets.
271 271 """
272 272 try:
273 273 b = getstring(x, '')
274 274 if b in repo.branchmap():
275 275 return [r for r in subset if repo[r].branch() == b]
276 276 except error.ParseError:
277 277 # not a string, but another revspec, e.g. tip()
278 278 pass
279 279
280 280 s = getset(repo, range(len(repo)), x)
281 281 b = set()
282 282 for r in s:
283 283 b.add(repo[r].branch())
284 284 s = set(s)
285 285 return [r for r in subset if r in s or repo[r].branch() in b]
286 286
287 287 def checkstatus(repo, subset, pat, field):
288 288 m = matchmod.match(repo.root, repo.getcwd(), [pat])
289 289 s = []
290 290 fast = (m.files() == [pat])
291 291 for r in subset:
292 292 c = repo[r]
293 293 if fast:
294 294 if pat not in c.files():
295 295 continue
296 296 else:
297 297 for f in c.files():
298 298 if m(f):
299 299 break
300 300 else:
301 301 continue
302 302 files = repo.status(c.p1().node(), c.node())[field]
303 303 if fast:
304 304 if pat in files:
305 305 s.append(r)
306 306 else:
307 307 for f in files:
308 308 if m(f):
309 309 s.append(r)
310 310 break
311 311 return s
312 312
313 313 def children(repo, subset, x):
314 314 """``children(set)``
315 315 Child changesets of changesets in set.
316 316 """
317 317 cs = set()
318 318 cl = repo.changelog
319 319 s = set(getset(repo, range(len(repo)), x))
320 320 for r in xrange(0, len(repo)):
321 321 for p in cl.parentrevs(r):
322 322 if p in s:
323 323 cs.add(r)
324 324 return [r for r in subset if r in cs]
325 325
326 326 def closed(repo, subset, x):
327 327 """``closed()``
328 328 Changeset is closed.
329 329 """
330 330 # i18n: "closed" is a keyword
331 331 getargs(x, 0, 0, _("closed takes no arguments"))
332 332 return [r for r in subset if repo[r].extra().get('close')]
333 333
334 334 def contains(repo, subset, x):
335 335 """``contains(pattern)``
336 336 Revision contains a file matching pattern. See :hg:`help patterns`
337 337 for information about file patterns.
338 338 """
339 339 # i18n: "contains" is a keyword
340 340 pat = getstring(x, _("contains requires a pattern"))
341 341 m = matchmod.match(repo.root, repo.getcwd(), [pat])
342 342 s = []
343 343 if m.files() == [pat]:
344 344 for r in subset:
345 345 if pat in repo[r]:
346 346 s.append(r)
347 347 else:
348 348 for r in subset:
349 349 for f in repo[r].manifest():
350 350 if m(f):
351 351 s.append(r)
352 352 break
353 353 return s
354 354
355 355 def date(repo, subset, x):
356 356 """``date(interval)``
357 357 Changesets within the interval, see :hg:`help dates`.
358 358 """
359 359 # i18n: "date" is a keyword
360 360 ds = getstring(x, _("date requires a string"))
361 361 dm = util.matchdate(ds)
362 362 return [r for r in subset if dm(repo[r].date()[0])]
363 363
364 364 def desc(repo, subset, x):
365 365 """``desc(string)``
366 366 Search commit message for string. The match is case-insensitive.
367 367 """
368 368 # i18n: "desc" is a keyword
369 369 ds = getstring(x, _("desc requires a string")).lower()
370 370 l = []
371 371 for r in subset:
372 372 c = repo[r]
373 373 if ds in c.description().lower():
374 374 l.append(r)
375 375 return l
376 376
377 377 def descendants(repo, subset, x):
378 378 """``descendants(set)``
379 379 Changesets which are descendants of changesets in set.
380 380 """
381 381 args = getset(repo, range(len(repo)), x)
382 382 if not args:
383 383 return []
384 384 s = set(repo.changelog.descendants(*args)) | set(args)
385 385 return [r for r in subset if r in s]
386 386
387 387 def filelog(repo, subset, x):
388 388 """``filelog(pattern)``
389 389 Changesets connected to the specified filelog.
390 390 """
391 391
392 392 pat = getstring(x, _("filelog requires a pattern"))
393 393 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
394 394 s = set()
395 395
396 396 if not m.anypats():
397 397 for f in m.files():
398 398 fl = repo.file(f)
399 399 for fr in fl:
400 400 s.add(fl.linkrev(fr))
401 401 else:
402 402 for f in repo[None]:
403 403 if m(f):
404 404 fl = repo.file(f)
405 405 for fr in fl:
406 406 s.add(fl.linkrev(fr))
407 407
408 408 return [r for r in subset if r in s]
409 409
410 410 def follow(repo, subset, x):
411 411 """``follow([file])``
412 412 An alias for ``::.`` (ancestors of the working copy's first parent).
413 413 If a filename is specified, the history of the given file is followed,
414 414 including copies.
415 415 """
416 416 # i18n: "follow" is a keyword
417 417 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
418 418 p = repo['.'].rev()
419 419 if l:
420 420 x = getstring(l[0], _("follow expected a filename"))
421 421 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
422 422 else:
423 423 s = set(repo.changelog.ancestors(p))
424 424
425 425 s |= set([p])
426 426 return [r for r in subset if r in s]
427 427
428 428 def followfile(repo, subset, x):
429 429 """``follow()``
430 430 An alias for ``::.`` (ancestors of the working copy's first parent).
431 431 """
432 432 # i18n: "follow" is a keyword
433 433 getargs(x, 0, 0, _("follow takes no arguments"))
434 434 p = repo['.'].rev()
435 435 s = set(repo.changelog.ancestors(p)) | set([p])
436 436 return [r for r in subset if r in s]
437 437
438 438 def getall(repo, subset, x):
439 439 """``all()``
440 440 All changesets, the same as ``0:tip``.
441 441 """
442 442 # i18n: "all" is a keyword
443 443 getargs(x, 0, 0, _("all takes no arguments"))
444 444 return subset
445 445
446 446 def grep(repo, subset, x):
447 447 """``grep(regex)``
448 448 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
449 449 to ensure special escape characters are handled correctly. Unlike
450 450 ``keyword(string)``, the match is case-sensitive.
451 451 """
452 452 try:
453 453 # i18n: "grep" is a keyword
454 454 gr = re.compile(getstring(x, _("grep requires a string")))
455 455 except re.error, e:
456 456 raise error.ParseError(_('invalid match pattern: %s') % e)
457 457 l = []
458 458 for r in subset:
459 459 c = repo[r]
460 460 for e in c.files() + [c.user(), c.description()]:
461 461 if gr.search(e):
462 462 l.append(r)
463 463 break
464 464 return l
465 465
466 466 def hasfile(repo, subset, x):
467 467 """``file(pattern)``
468 468 Changesets affecting files matched by pattern.
469 469 """
470 470 # i18n: "file" is a keyword
471 471 pat = getstring(x, _("file requires a pattern"))
472 472 m = matchmod.match(repo.root, repo.getcwd(), [pat])
473 473 s = []
474 474 for r in subset:
475 475 for f in repo[r].files():
476 476 if m(f):
477 477 s.append(r)
478 478 break
479 479 return s
480 480
481 481 def head(repo, subset, x):
482 482 """``head()``
483 483 Changeset is a named branch head.
484 484 """
485 485 # i18n: "head" is a keyword
486 486 getargs(x, 0, 0, _("head takes no arguments"))
487 487 hs = set()
488 488 for b, ls in repo.branchmap().iteritems():
489 489 hs.update(repo[h].rev() for h in ls)
490 490 return [r for r in subset if r in hs]
491 491
492 492 def heads(repo, subset, x):
493 493 """``heads(set)``
494 494 Members of set with no children in set.
495 495 """
496 496 s = getset(repo, subset, x)
497 497 ps = set(parents(repo, subset, x))
498 498 return [r for r in s if r not in ps]
499 499
500 500 def keyword(repo, subset, x):
501 501 """``keyword(string)``
502 502 Search commit message, user name, and names of changed files for
503 503 string. The match is case-insensitive.
504 504 """
505 505 # i18n: "keyword" is a keyword
506 506 kw = getstring(x, _("keyword requires a string")).lower()
507 507 l = []
508 508 for r in subset:
509 509 c = repo[r]
510 510 t = " ".join(c.files() + [c.user(), c.description()])
511 511 if kw in t.lower():
512 512 l.append(r)
513 513 return l
514 514
515 515 def limit(repo, subset, x):
516 516 """``limit(set, n)``
517 517 First n members of set.
518 518 """
519 519 # i18n: "limit" is a keyword
520 520 l = getargs(x, 2, 2, _("limit requires two arguments"))
521 521 try:
522 522 # i18n: "limit" is a keyword
523 523 lim = int(getstring(l[1], _("limit requires a number")))
524 524 except ValueError:
525 525 # i18n: "limit" is a keyword
526 526 raise error.ParseError(_("limit expects a number"))
527 527 ss = set(subset)
528 528 os = getset(repo, range(len(repo)), l[0])[:lim]
529 529 return [r for r in os if r in ss]
530 530
531 531 def last(repo, subset, x):
532 532 """``last(set, n)``
533 533 Last n members of set.
534 534 """
535 535 # i18n: "last" is a keyword
536 536 l = getargs(x, 2, 2, _("last requires two arguments"))
537 537 try:
538 538 # i18n: "last" is a keyword
539 539 lim = int(getstring(l[1], _("last requires a number")))
540 540 except ValueError:
541 541 # i18n: "last" is a keyword
542 542 raise error.ParseError(_("last expects a number"))
543 543 ss = set(subset)
544 544 os = getset(repo, range(len(repo)), l[0])[-lim:]
545 545 return [r for r in os if r in ss]
546 546
547 547 def maxrev(repo, subset, x):
548 548 """``max(set)``
549 549 Changeset with highest revision number in set.
550 550 """
551 551 os = getset(repo, range(len(repo)), x)
552 552 if os:
553 553 m = max(os)
554 554 if m in subset:
555 555 return [m]
556 556 return []
557 557
558 558 def merge(repo, subset, x):
559 559 """``merge()``
560 560 Changeset is a merge changeset.
561 561 """
562 562 # i18n: "merge" is a keyword
563 563 getargs(x, 0, 0, _("merge takes no arguments"))
564 564 cl = repo.changelog
565 565 return [r for r in subset if cl.parentrevs(r)[1] != -1]
566 566
567 567 def minrev(repo, subset, x):
568 568 """``min(set)``
569 569 Changeset with lowest revision number in set.
570 570 """
571 571 os = getset(repo, range(len(repo)), x)
572 572 if os:
573 573 m = min(os)
574 574 if m in subset:
575 575 return [m]
576 576 return []
577 577
578 578 def modifies(repo, subset, x):
579 579 """``modifies(pattern)``
580 580 Changesets modifying files matched by pattern.
581 581 """
582 582 # i18n: "modifies" is a keyword
583 583 pat = getstring(x, _("modifies requires a pattern"))
584 584 return checkstatus(repo, subset, pat, 0)
585 585
586 586 def node(repo, subset, x):
587 587 """``id(string)``
588 588 Revision non-ambiguously specified by the given hex string prefix.
589 589 """
590 590 # i18n: "id" is a keyword
591 591 l = getargs(x, 1, 1, _("id requires one argument"))
592 592 # i18n: "id" is a keyword
593 593 n = getstring(l[0], _("id requires a string"))
594 594 if len(n) == 40:
595 595 rn = repo[n].rev()
596 596 else:
597 597 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
598 598 return [r for r in subset if r == rn]
599 599
600 600 def outgoing(repo, subset, x):
601 601 """``outgoing([path])``
602 602 Changesets not found in the specified destination repository, or the
603 603 default push location.
604 604 """
605 605 import hg # avoid start-up nasties
606 606 # i18n: "outgoing" is a keyword
607 607 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
608 608 # i18n: "outgoing" is a keyword
609 609 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
610 610 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
611 611 dest, branches = hg.parseurl(dest)
612 612 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
613 613 if revs:
614 614 revs = [repo.lookup(rev) for rev in revs]
615 615 other = hg.peer(repo, {}, dest)
616 616 repo.ui.pushbuffer()
617 617 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
618 618 repo.ui.popbuffer()
619 619 cl = repo.changelog
620 620 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
621 621 return [r for r in subset if r in o]
622 622
623 623 def p1(repo, subset, x):
624 624 """``p1([set])``
625 625 First parent of changesets in set, or the working directory.
626 626 """
627 627 if x is None:
628 628 p = repo[x].p1().rev()
629 629 return [r for r in subset if r == p]
630 630
631 631 ps = set()
632 632 cl = repo.changelog
633 633 for r in getset(repo, range(len(repo)), x):
634 634 ps.add(cl.parentrevs(r)[0])
635 635 return [r for r in subset if r in ps]
636 636
637 637 def p2(repo, subset, x):
638 638 """``p2([set])``
639 639 Second parent of changesets in set, or the working directory.
640 640 """
641 641 if x is None:
642 642 ps = repo[x].parents()
643 643 try:
644 644 p = ps[1].rev()
645 645 return [r for r in subset if r == p]
646 646 except IndexError:
647 647 return []
648 648
649 649 ps = set()
650 650 cl = repo.changelog
651 651 for r in getset(repo, range(len(repo)), x):
652 652 ps.add(cl.parentrevs(r)[1])
653 653 return [r for r in subset if r in ps]
654 654
655 655 def parents(repo, subset, x):
656 656 """``parents([set])``
657 657 The set of all parents for all changesets in set, or the working directory.
658 658 """
659 659 if x is None:
660 660 ps = tuple(p.rev() for p in repo[x].parents())
661 661 return [r for r in subset if r in ps]
662 662
663 663 ps = set()
664 664 cl = repo.changelog
665 665 for r in getset(repo, range(len(repo)), x):
666 666 ps.update(cl.parentrevs(r))
667 667 return [r for r in subset if r in ps]
668 668
669 669 def parentspec(repo, subset, x, n):
670 670 """``set^0``
671 671 The set.
672 672 ``set^1`` (or ``set^``), ``set^2``
673 673 First or second parent, respectively, of all changesets in set.
674 674 """
675 675 try:
676 676 n = int(n[1])
677 677 if n not in (0, 1, 2):
678 678 raise ValueError
679 679 except ValueError:
680 680 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
681 681 ps = set()
682 682 cl = repo.changelog
683 683 for r in getset(repo, subset, x):
684 684 if n == 0:
685 685 ps.add(r)
686 686 elif n == 1:
687 687 ps.add(cl.parentrevs(r)[0])
688 688 elif n == 2:
689 689 parents = cl.parentrevs(r)
690 690 if len(parents) > 1:
691 691 ps.add(parents[1])
692 692 return [r for r in subset if r in ps]
693 693
694 694 def present(repo, subset, x):
695 695 """``present(set)``
696 696 An empty set, if any revision in set isn't found; otherwise,
697 697 all revisions in set.
698 698 """
699 699 try:
700 700 return getset(repo, subset, x)
701 701 except error.RepoLookupError:
702 702 return []
703 703
704 704 def removes(repo, subset, x):
705 705 """``removes(pattern)``
706 706 Changesets which remove files matching pattern.
707 707 """
708 708 # i18n: "removes" is a keyword
709 709 pat = getstring(x, _("removes requires a pattern"))
710 710 return checkstatus(repo, subset, pat, 2)
711 711
712 712 def rev(repo, subset, x):
713 713 """``rev(number)``
714 714 Revision with the given numeric identifier.
715 715 """
716 716 # i18n: "rev" is a keyword
717 717 l = getargs(x, 1, 1, _("rev requires one argument"))
718 718 try:
719 719 # i18n: "rev" is a keyword
720 720 l = int(getstring(l[0], _("rev requires a number")))
721 721 except ValueError:
722 722 # i18n: "rev" is a keyword
723 723 raise error.ParseError(_("rev expects a number"))
724 724 return [r for r in subset if r == l]
725 725
726 726 def reverse(repo, subset, x):
727 727 """``reverse(set)``
728 728 Reverse order of set.
729 729 """
730 730 l = getset(repo, subset, x)
731 731 l.reverse()
732 732 return l
733 733
734 734 def roots(repo, subset, x):
735 735 """``roots(set)``
736 736 Changesets with no parent changeset in set.
737 737 """
738 738 s = getset(repo, subset, x)
739 739 cs = set(children(repo, subset, x))
740 740 return [r for r in s if r not in cs]
741 741
742 742 def sort(repo, subset, x):
743 743 """``sort(set[, [-]key...])``
744 744 Sort set by keys. The default sort order is ascending, specify a key
745 745 as ``-key`` to sort in descending order.
746 746
747 747 The keys can be:
748 748
749 749 - ``rev`` for the revision number,
750 750 - ``branch`` for the branch name,
751 751 - ``desc`` for the commit message (description),
752 752 - ``user`` for user name (``author`` can be used as an alias),
753 753 - ``date`` for the commit date
754 754 """
755 755 # i18n: "sort" is a keyword
756 756 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
757 757 keys = "rev"
758 758 if len(l) == 2:
759 759 keys = getstring(l[1], _("sort spec must be a string"))
760 760
761 761 s = l[0]
762 762 keys = keys.split()
763 763 l = []
764 764 def invert(s):
765 765 return "".join(chr(255 - ord(c)) for c in s)
766 766 for r in getset(repo, subset, s):
767 767 c = repo[r]
768 768 e = []
769 769 for k in keys:
770 770 if k == 'rev':
771 771 e.append(r)
772 772 elif k == '-rev':
773 773 e.append(-r)
774 774 elif k == 'branch':
775 775 e.append(c.branch())
776 776 elif k == '-branch':
777 777 e.append(invert(c.branch()))
778 778 elif k == 'desc':
779 779 e.append(c.description())
780 780 elif k == '-desc':
781 781 e.append(invert(c.description()))
782 782 elif k in 'user author':
783 783 e.append(c.user())
784 784 elif k in '-user -author':
785 785 e.append(invert(c.user()))
786 786 elif k == 'date':
787 787 e.append(c.date()[0])
788 788 elif k == '-date':
789 789 e.append(-c.date()[0])
790 790 else:
791 791 raise error.ParseError(_("unknown sort key %r") % k)
792 792 e.append(r)
793 793 l.append(e)
794 794 l.sort()
795 795 return [e[-1] for e in l]
796 796
797 797 def tag(repo, subset, x):
798 798 """``tag([name])``
799 799 The specified tag by name, or all tagged revisions if no name is given.
800 800 """
801 801 # i18n: "tag" is a keyword
802 802 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
803 803 cl = repo.changelog
804 804 if args:
805 805 tn = getstring(args[0],
806 806 # i18n: "tag" is a keyword
807 807 _('the argument to tag must be a string'))
808 808 if not repo.tags().get(tn, None):
809 809 raise util.Abort(_("tag '%s' does not exist") % tn)
810 810 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
811 811 else:
812 812 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
813 813 return [r for r in subset if r in s]
814 814
815 815 def tagged(repo, subset, x):
816 816 return tag(repo, subset, x)
817 817
818 818 def user(repo, subset, x):
819 819 """``user(string)``
820 820 User name contains string. The match is case-insensitive.
821 821 """
822 822 return author(repo, subset, x)
823 823
824 824 symbols = {
825 825 "adds": adds,
826 826 "all": getall,
827 827 "ancestor": ancestor,
828 828 "ancestors": ancestors,
829 829 "author": author,
830 830 "bisected": bisected,
831 831 "bookmark": bookmark,
832 832 "branch": branch,
833 833 "children": children,
834 834 "closed": closed,
835 835 "contains": contains,
836 836 "date": date,
837 837 "desc": desc,
838 838 "descendants": descendants,
839 839 "file": hasfile,
840 840 "filelog": filelog,
841 841 "follow": follow,
842 842 "grep": grep,
843 843 "head": head,
844 844 "heads": heads,
845 845 "id": node,
846 846 "keyword": keyword,
847 847 "last": last,
848 848 "limit": limit,
849 849 "max": maxrev,
850 850 "merge": merge,
851 851 "min": minrev,
852 852 "modifies": modifies,
853 853 "outgoing": outgoing,
854 854 "p1": p1,
855 855 "p2": p2,
856 856 "parents": parents,
857 857 "present": present,
858 858 "removes": removes,
859 859 "rev": rev,
860 860 "reverse": reverse,
861 861 "roots": roots,
862 862 "sort": sort,
863 863 "tag": tag,
864 864 "tagged": tagged,
865 865 "user": user,
866 866 }
867 867
868 868 methods = {
869 869 "range": rangeset,
870 870 "string": stringset,
871 871 "symbol": symbolset,
872 872 "and": andset,
873 873 "or": orset,
874 874 "not": notset,
875 875 "list": listset,
876 876 "func": func,
877 877 "ancestor": ancestorspec,
878 878 "parent": parentspec,
879 879 "parentpost": p1,
880 880 }
881 881
882 882 def optimize(x, small):
883 883 if x is None:
884 884 return 0, x
885 885
886 886 smallbonus = 1
887 887 if small:
888 888 smallbonus = .5
889 889
890 890 op = x[0]
891 891 if op == 'minus':
892 892 return optimize(('and', x[1], ('not', x[2])), small)
893 893 elif op == 'dagrange':
894 894 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
895 895 ('func', ('symbol', 'ancestors'), x[2])), small)
896 896 elif op == 'dagrangepre':
897 897 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
898 898 elif op == 'dagrangepost':
899 899 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
900 900 elif op == 'rangepre':
901 901 return optimize(('range', ('string', '0'), x[1]), small)
902 902 elif op == 'rangepost':
903 903 return optimize(('range', x[1], ('string', 'tip')), small)
904 904 elif op == 'negate':
905 905 return optimize(('string',
906 906 '-' + getstring(x[1], _("can't negate that"))), small)
907 907 elif op in 'string symbol negate':
908 908 return smallbonus, x # single revisions are small
909 909 elif op == 'and' or op == 'dagrange':
910 910 wa, ta = optimize(x[1], True)
911 911 wb, tb = optimize(x[2], True)
912 912 w = min(wa, wb)
913 913 if wa > wb:
914 914 return w, (op, tb, ta)
915 915 return w, (op, ta, tb)
916 916 elif op == 'or':
917 917 wa, ta = optimize(x[1], False)
918 918 wb, tb = optimize(x[2], False)
919 919 if wb < wa:
920 920 wb, wa = wa, wb
921 921 return max(wa, wb), (op, ta, tb)
922 922 elif op == 'not':
923 923 o = optimize(x[1], not small)
924 924 return o[0], (op, o[1])
925 925 elif op == 'parentpost':
926 926 o = optimize(x[1], small)
927 927 return o[0], (op, o[1])
928 928 elif op == 'group':
929 929 return optimize(x[1], small)
930 930 elif op in 'range list parent ancestorspec':
931 931 wa, ta = optimize(x[1], small)
932 932 wb, tb = optimize(x[2], small)
933 933 return wa + wb, (op, ta, tb)
934 934 elif op == 'func':
935 935 f = getstring(x[1], _("not a symbol"))
936 936 wa, ta = optimize(x[2], small)
937 937 if f in ("author branch closed date desc file grep keyword "
938 938 "outgoing user"):
939 939 w = 10 # slow
940 940 elif f in "modifies adds removes":
941 941 w = 30 # slower
942 942 elif f == "contains":
943 943 w = 100 # very slow
944 944 elif f == "ancestor":
945 945 w = 1 * smallbonus
946 946 elif f in "reverse limit":
947 947 w = 0
948 948 elif f in "sort":
949 949 w = 10 # assume most sorts look at changelog
950 950 else:
951 951 w = 1
952 952 return w + wa, (op, x[1], ta)
953 953 return 1, x
954 954
955 955 class revsetalias(object):
956 956 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
957 args = ()
957 args = None
958 958
959 def __init__(self, token, value):
959 def __init__(self, name, value):
960 960 '''Aliases like:
961 961
962 962 h = heads(default)
963 963 b($1) = ancestors($1) - ancestors(default)
964 964 '''
965 if isinstance(token, tuple):
966 self.type, self.name = token
967 else:
968 m = self.funcre.search(token)
965 if isinstance(name, tuple): # parameter substitution
966 self.tree = name
967 self.replacement = value
968 else: # alias definition
969 m = self.funcre.search(name)
969 970 if m:
970 self.type = 'func'
971 self.name = m.group(1)
971 self.tree = ('func', ('symbol', m.group(1)))
972 972 self.args = [x.strip() for x in m.group(2).split(',')]
973 else:
974 self.type = 'symbol'
975 self.name = token
976
977 if isinstance(value, str):
978 973 for arg in self.args:
979 974 value = value.replace(arg, repr(arg))
975 else:
976 self.tree = ('symbol', name)
977
980 978 self.replacement, pos = parse(value)
981 979 if pos != len(value):
982 980 raise error.ParseError(_('invalid token'), pos)
983 else:
984 self.replacement = value
985 981
986 def match(self, tree):
987 if not tree:
988 return False
989 if tree == (self.type, self.name):
990 return True
991 if tree[0] != self.type:
992 return False
993 if len(tree) > 1 and tree[1] != ('symbol', self.name):
994 return False
995 # 'func' + funcname + args
996 if ((self.args and len(tree) != 3) or
997 (len(self.args) == 1 and tree[2][0] == 'list') or
998 (len(self.args) > 1 and (tree[2][0] != 'list' or
999 len(tree[2]) - 1 != len(self.args)))):
1000 raise error.ParseError(_('invalid amount of arguments'),
1001 len(tree) - 2)
1002 return True
1003
1004 def replace(self, tree):
1005 if tree == (self.type, self.name):
982 def process(self, tree):
983 if isinstance(tree, tuple):
984 if self.args is None:
985 if tree == self.tree:
1006 986 return self.replacement
987 elif tree[:2] == self.tree:
988 l = getlist(tree[2])
989 if len(l) != len(self.args):
990 raise error.ParseError(
991 _('invalid number of arguments: %s') % len(l))
1007 992 result = self.replacement
1008 def getsubtree(i):
1009 if tree[2][0] == 'list':
1010 return tree[2][i + 1]
1011 return tree[i + 2]
1012 for i, v in enumerate(self.args):
1013 valalias = revsetalias(('string', v), getsubtree(i))
993 for a, v in zip(self.args, l):
994 valalias = revsetalias(('string', a), v)
1014 995 result = valalias.process(result)
1015 996 return result
1016
1017 def process(self, tree):
1018 if self.match(tree):
1019 return self.replace(tree)
1020 if isinstance(tree, tuple):
1021 997 return tuple(map(self.process, tree))
1022 998 return tree
1023 999
1024 1000 def findaliases(ui, tree):
1025 1001 for k, v in ui.configitems('revsetalias'):
1026 1002 alias = revsetalias(k, v)
1027 1003 tree = alias.process(tree)
1028 1004 return tree
1029 1005
1030 1006 parse = parser.parser(tokenize, elements).parse
1031 1007
1032 1008 def match(ui, spec):
1033 1009 if not spec:
1034 1010 raise error.ParseError(_("empty query"))
1035 1011 tree, pos = parse(spec)
1036 1012 if (pos != len(spec)):
1037 1013 raise error.ParseError(_("invalid token"), pos)
1038 1014 tree = findaliases(ui, tree)
1039 1015 weight, tree = optimize(tree, True)
1040 1016 def mfunc(repo, subset):
1041 1017 return getset(repo, subset, tree)
1042 1018 return mfunc
1043 1019
1044 1020 # tell hggettext to extract docstrings from these functions:
1045 1021 i18nfunctions = symbols.values()
@@ -1,447 +1,465
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 $ hg ci -Aqm0
19 19
20 20 $ echo b > b
21 21 $ hg branch b
22 22 marked working directory as branch b
23 23 $ hg ci -Aqm1
24 24
25 25 $ rm a
26 26 $ hg branch a-b-c-
27 27 marked working directory as branch a-b-c-
28 28 $ hg ci -Aqm2 -u Bob
29 29
30 30 $ hg co 1
31 31 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
32 32 $ hg branch +a+b+c+
33 33 marked working directory as branch +a+b+c+
34 34 $ hg ci -Aqm3
35 35
36 36 $ hg co 2 # interleave
37 37 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
38 38 $ echo bb > b
39 39 $ hg branch -- -a-b-c-
40 40 marked working directory as branch -a-b-c-
41 41 $ hg ci -Aqm4 -d "May 12 2005"
42 42
43 43 $ hg co 3
44 44 2 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 $ hg ci -Aqm"5 bug"
48 48
49 49 $ hg merge 4
50 50 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
51 51 (branch merge, don't forget to commit)
52 52 $ hg branch _a_b_c_
53 53 marked working directory as branch _a_b_c_
54 54 $ hg ci -Aqm"6 issue619"
55 55
56 56 $ hg branch .a.b.c.
57 57 marked working directory as branch .a.b.c.
58 58 $ hg ci -Aqm7
59 59
60 60 $ hg branch all
61 61 marked working directory as branch all
62 62 $ hg ci --close-branch -Aqm8
63 63 abort: can only close branch heads
64 64 [255]
65 65
66 66 $ hg co 4
67 67 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 68 $ hg branch é
69 69 marked working directory as branch \xc3\xa9 (esc)
70 70 $ hg ci -Aqm9
71 71
72 72 $ hg tag -r6 1.0
73 73
74 74 $ hg clone --quiet -U -r 7 . ../remote1
75 75 $ hg clone --quiet -U -r 8 . ../remote2
76 76 $ echo "[paths]" >> .hg/hgrc
77 77 $ echo "default = ../remote1" >> .hg/hgrc
78 78
79 79 names that should work without quoting
80 80
81 81 $ try a
82 82 ('symbol', 'a')
83 83 0
84 84 $ try b-a
85 85 ('minus', ('symbol', 'b'), ('symbol', 'a'))
86 86 1
87 87 $ try _a_b_c_
88 88 ('symbol', '_a_b_c_')
89 89 6
90 90 $ try _a_b_c_-a
91 91 ('minus', ('symbol', '_a_b_c_'), ('symbol', 'a'))
92 92 6
93 93 $ try .a.b.c.
94 94 ('symbol', '.a.b.c.')
95 95 7
96 96 $ try .a.b.c.-a
97 97 ('minus', ('symbol', '.a.b.c.'), ('symbol', 'a'))
98 98 7
99 99 $ try -- '-a-b-c-' # complains
100 100 hg: parse error at 7: not a prefix: end
101 101 [255]
102 102 $ log -a-b-c- # succeeds with fallback
103 103 4
104 104 $ try -- -a-b-c--a # complains
105 105 ('minus', ('minus', ('minus', ('negate', ('symbol', 'a')), ('symbol', 'b')), ('symbol', 'c')), ('negate', ('symbol', 'a')))
106 106 abort: unknown revision '-a'!
107 107 [255]
108 108 $ try é
109 109 ('symbol', '\xc3\xa9')
110 110 9
111 111
112 112 quoting needed
113 113
114 114 $ try '"-a-b-c-"-a'
115 115 ('minus', ('string', '-a-b-c-'), ('symbol', 'a'))
116 116 4
117 117
118 118 $ log '1 or 2'
119 119 1
120 120 2
121 121 $ log '1|2'
122 122 1
123 123 2
124 124 $ log '1 and 2'
125 125 $ log '1&2'
126 126 $ try '1&2|3' # precedence - and is higher
127 127 ('or', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
128 128 3
129 129 $ try '1|2&3'
130 130 ('or', ('symbol', '1'), ('and', ('symbol', '2'), ('symbol', '3')))
131 131 1
132 132 $ try '1&2&3' # associativity
133 133 ('and', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
134 134 $ try '1|(2|3)'
135 135 ('or', ('symbol', '1'), ('group', ('or', ('symbol', '2'), ('symbol', '3'))))
136 136 1
137 137 2
138 138 3
139 139 $ log '1.0' # tag
140 140 6
141 141 $ log 'a' # branch
142 142 0
143 143 $ log '2785f51ee'
144 144 0
145 145 $ log 'date(2005)'
146 146 4
147 147 $ log 'date(this is a test)'
148 148 hg: parse error at 10: unexpected token: symbol
149 149 [255]
150 150 $ log 'date()'
151 151 hg: parse error: date requires a string
152 152 [255]
153 153 $ log 'date'
154 154 hg: parse error: can't use date here
155 155 [255]
156 156 $ log 'date('
157 157 hg: parse error at 5: not a prefix: end
158 158 [255]
159 159 $ log 'date(tip)'
160 160 abort: invalid date: 'tip'
161 161 [255]
162 162 $ log '"date"'
163 163 abort: unknown revision 'date'!
164 164 [255]
165 165 $ log 'date(2005) and 1::'
166 166 4
167 167
168 168 $ log 'ancestor(1)'
169 169 hg: parse error: ancestor requires two arguments
170 170 [255]
171 171 $ log 'ancestor(4,5)'
172 172 1
173 173 $ log 'ancestor(4,5) and 4'
174 174 $ log 'ancestors(5)'
175 175 0
176 176 1
177 177 3
178 178 5
179 179 $ log 'author(bob)'
180 180 2
181 181 $ log 'branch(é)'
182 182 8
183 183 9
184 184 $ log 'children(ancestor(4,5))'
185 185 2
186 186 3
187 187 $ log 'closed()'
188 188 $ log 'contains(a)'
189 189 0
190 190 1
191 191 3
192 192 5
193 193 $ log 'desc(B)'
194 194 5
195 195 $ log 'descendants(2 or 3)'
196 196 2
197 197 3
198 198 4
199 199 5
200 200 6
201 201 7
202 202 8
203 203 9
204 204 $ log 'file(b)'
205 205 1
206 206 4
207 207 $ log 'follow()'
208 208 0
209 209 1
210 210 2
211 211 4
212 212 8
213 213 9
214 214 $ log 'grep("issue\d+")'
215 215 6
216 216 $ try 'grep("(")' # invalid regular expression
217 217 ('func', ('symbol', 'grep'), ('string', '('))
218 218 hg: parse error: invalid match pattern: unbalanced parenthesis
219 219 [255]
220 220 $ try 'grep("\bissue\d+")'
221 221 ('func', ('symbol', 'grep'), ('string', '\x08issue\\d+'))
222 222 $ try 'grep(r"\bissue\d+")'
223 223 ('func', ('symbol', 'grep'), ('string', '\\bissue\\d+'))
224 224 6
225 225 $ try 'grep(r"\")'
226 226 hg: parse error at 7: unterminated string
227 227 [255]
228 228 $ log 'head()'
229 229 0
230 230 1
231 231 2
232 232 3
233 233 4
234 234 5
235 235 6
236 236 7
237 237 9
238 238 $ log 'heads(6::)'
239 239 7
240 240 $ log 'keyword(issue)'
241 241 6
242 242 $ log 'limit(head(), 1)'
243 243 0
244 244 $ log 'max(contains(a))'
245 245 5
246 246 $ log 'min(contains(a))'
247 247 0
248 248 $ log 'merge()'
249 249 6
250 250 $ log 'modifies(b)'
251 251 4
252 252 $ log 'id(5)'
253 253 2
254 254 $ log 'outgoing()'
255 255 8
256 256 9
257 257 $ log 'outgoing("../remote1")'
258 258 8
259 259 9
260 260 $ log 'outgoing("../remote2")'
261 261 3
262 262 5
263 263 6
264 264 7
265 265 9
266 266 $ log 'p1(merge())'
267 267 5
268 268 $ log 'p2(merge())'
269 269 4
270 270 $ log 'parents(merge())'
271 271 4
272 272 5
273 273 $ log 'removes(a)'
274 274 2
275 275 6
276 276 $ log 'roots(all())'
277 277 0
278 278 $ log 'reverse(2 or 3 or 4 or 5)'
279 279 5
280 280 4
281 281 3
282 282 2
283 283 $ log 'rev(5)'
284 284 5
285 285 $ log 'sort(limit(reverse(all()), 3))'
286 286 7
287 287 8
288 288 9
289 289 $ log 'sort(2 or 3 or 4 or 5, date)'
290 290 2
291 291 3
292 292 5
293 293 4
294 294 $ log 'tagged()'
295 295 6
296 296 $ log 'tag()'
297 297 6
298 298 $ log 'tag(1.0)'
299 299 6
300 300 $ log 'tag(tip)'
301 301 9
302 302 $ log 'tag(unknown)'
303 303 abort: tag 'unknown' does not exist
304 304 [255]
305 305 $ log 'branch(unknown)'
306 306 abort: unknown revision 'unknown'!
307 307 [255]
308 308 $ log 'user(bob)'
309 309 2
310 310
311 311 $ log '4::8'
312 312 4
313 313 8
314 314 $ log '4:8'
315 315 4
316 316 5
317 317 6
318 318 7
319 319 8
320 320
321 321 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
322 322 4
323 323 2
324 324 5
325 325
326 326 $ log 'not 0 and 0:2'
327 327 1
328 328 2
329 329 $ log 'not 1 and 0:2'
330 330 0
331 331 2
332 332 $ log 'not 2 and 0:2'
333 333 0
334 334 1
335 335 $ log '(1 and 2)::'
336 336 $ log '(1 and 2):'
337 337 $ log '(1 and 2):3'
338 338 $ log 'sort(head(), -rev)'
339 339 9
340 340 7
341 341 6
342 342 5
343 343 4
344 344 3
345 345 2
346 346 1
347 347 0
348 348 $ log '4::8 - 8'
349 349 4
350 350
351 351 issue2437
352 352
353 353 $ log '3 and p1(5)'
354 354 3
355 355 $ log '4 and p2(6)'
356 356 4
357 357 $ log '1 and parents(:2)'
358 358 1
359 359 $ log '2 and children(1:)'
360 360 2
361 361 $ log 'roots(all()) or roots(all())'
362 362 0
363 363 $ log 'heads(branch(é)) or heads(branch(é))'
364 364 9
365 365 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(é)))'
366 366 4
367 367
368 368 issue2654: report a parse error if the revset was not completely parsed
369 369
370 370 $ log '1 OR 2'
371 371 hg: parse error at 2: invalid token
372 372 [255]
373 373
374 374 or operator should preserve ordering:
375 375 $ log 'reverse(2::4) or tip'
376 376 4
377 377 2
378 378 9
379 379
380 380 parentrevspec
381 381
382 382 $ log 'merge()^0'
383 383 6
384 384 $ log 'merge()^'
385 385 5
386 386 $ log 'merge()^1'
387 387 5
388 388 $ log 'merge()^2'
389 389 4
390 390 $ log 'merge()^^'
391 391 3
392 392 $ log 'merge()^1^'
393 393 3
394 394 $ log 'merge()^^^'
395 395 1
396 396
397 397 $ log 'merge()~0'
398 398 6
399 399 $ log 'merge()~1'
400 400 5
401 401 $ log 'merge()~2'
402 402 3
403 403 $ log 'merge()~2^1'
404 404 1
405 405 $ log 'merge()~3'
406 406 1
407 407
408 408 $ log '(-3:tip)^'
409 409 4
410 410 6
411 411 8
412 412
413 413 $ log 'tip^foo'
414 414 hg: parse error: ^ expects a number 0, 1, or 2
415 415 [255]
416 416
417 417 aliases:
418 418
419 419 $ echo '[revsetalias]' >> .hg/hgrc
420 420 $ echo 'm = merge()' >> .hg/hgrc
421 421 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
422 422 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
423 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
423 424
424 425 $ try m
425 426 ('symbol', 'm')
426 427 ('func', ('symbol', 'merge'), None)
427 428 6
428 429 $ try 'd(2:5)'
429 430 ('func', ('symbol', 'd'), ('range', ('symbol', '2'), ('symbol', '5')))
430 431 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('range', ('symbol', '2'), ('symbol', '5')), ('symbol', 'date'))))
431 432 4
432 433 5
433 434 3
434 435 2
435 436 $ try 'rs(2 or 3, date)'
436 437 ('func', ('symbol', 'rs'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date')))
437 438 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
438 439 3
439 440 2
441 $ try 'rs()'
442 ('func', ('symbol', 'rs'), None)
443 hg: parse error: invalid number of arguments: 0
444 [255]
445 $ try 'rs(2)'
446 ('func', ('symbol', 'rs'), ('symbol', '2'))
447 hg: parse error: invalid number of arguments: 1
448 [255]
449 $ try 'rs(2, data, 7)'
450 ('func', ('symbol', 'rs'), ('list', ('list', ('symbol', '2'), ('symbol', 'data')), ('symbol', '7')))
451 hg: parse error: invalid number of arguments: 3
452 [255]
453 $ try 'rs4(2 or 3, x, x, date)'
454 ('func', ('symbol', 'rs4'), ('list', ('list', ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'x')), ('symbol', 'x')), ('symbol', 'date')))
455 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
456 3
457 2
440 458
441 459 issue2549 - correct optimizations
442 460
443 461 $ log 'limit(1 or 2 or 3, 2) and not 2'
444 462 1
445 463 $ log 'max(1 or 2) and not 2'
446 464 $ log 'min(1 or 2) and not 1'
447 465 $ log 'last(1 or 2, 1) and not 2'
General Comments 0
You need to be logged in to leave comments. Login now