##// END OF EJS Templates
revset: fix alias substitution recursion (issue3240)...
Patrick Mezard -
r16096:b8be4506 default
parent child Browse files
Show More
@@ -1,1225 +1,1264 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node as nodemod
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 elements = {
17 17 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
18 18 "~": (18, None, ("ancestor", 18)),
19 19 "^": (18, None, ("parent", 18), ("parentpost", 18)),
20 20 "-": (5, ("negate", 19), ("minus", 5)),
21 21 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
22 22 ("dagrangepost", 17)),
23 23 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
24 24 ("dagrangepost", 17)),
25 25 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
26 26 "not": (10, ("not", 10)),
27 27 "!": (10, ("not", 10)),
28 28 "and": (5, None, ("and", 5)),
29 29 "&": (5, None, ("and", 5)),
30 30 "or": (4, None, ("or", 4)),
31 31 "|": (4, None, ("or", 4)),
32 32 "+": (4, None, ("or", 4)),
33 33 ",": (2, None, ("list", 2)),
34 34 ")": (0, None, None),
35 35 "symbol": (0, ("symbol",), None),
36 36 "string": (0, ("string",), None),
37 37 "end": (0, None, None),
38 38 }
39 39
40 40 keywords = set(['and', 'or', 'not'])
41 41
42 42 def tokenize(program):
43 43 pos, l = 0, len(program)
44 44 while pos < l:
45 45 c = program[pos]
46 46 if c.isspace(): # skip inter-token whitespace
47 47 pass
48 48 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
49 49 yield ('::', None, pos)
50 50 pos += 1 # skip ahead
51 51 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
52 52 yield ('..', None, pos)
53 53 pos += 1 # skip ahead
54 54 elif c in "():,-|&+!~^": # handle simple operators
55 55 yield (c, None, pos)
56 56 elif (c in '"\'' or c == 'r' and
57 57 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
58 58 if c == 'r':
59 59 pos += 1
60 60 c = program[pos]
61 61 decode = lambda x: x
62 62 else:
63 63 decode = lambda x: x.decode('string-escape')
64 64 pos += 1
65 65 s = pos
66 66 while pos < l: # find closing quote
67 67 d = program[pos]
68 68 if d == '\\': # skip over escaped characters
69 69 pos += 2
70 70 continue
71 71 if d == c:
72 72 yield ('string', decode(program[s:pos]), s)
73 73 break
74 74 pos += 1
75 75 else:
76 76 raise error.ParseError(_("unterminated string"), s)
77 77 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
78 78 s = pos
79 79 pos += 1
80 80 while pos < l: # find end of symbol
81 81 d = program[pos]
82 82 if not (d.isalnum() or d in "._/" or ord(d) > 127):
83 83 break
84 84 if d == '.' and program[pos - 1] == '.': # special case for ..
85 85 pos -= 1
86 86 break
87 87 pos += 1
88 88 sym = program[s:pos]
89 89 if sym in keywords: # operator keywords
90 90 yield (sym, None, s)
91 91 else:
92 92 yield ('symbol', sym, s)
93 93 pos -= 1
94 94 else:
95 95 raise error.ParseError(_("syntax error"), pos)
96 96 pos += 1
97 97 yield ('end', None, pos)
98 98
99 99 # helpers
100 100
101 101 def getstring(x, err):
102 102 if x and (x[0] == 'string' or x[0] == 'symbol'):
103 103 return x[1]
104 104 raise error.ParseError(err)
105 105
106 106 def getlist(x):
107 107 if not x:
108 108 return []
109 109 if x[0] == 'list':
110 110 return getlist(x[1]) + [x[2]]
111 111 return [x]
112 112
113 113 def getargs(x, min, max, err):
114 114 l = getlist(x)
115 115 if len(l) < min or len(l) > max:
116 116 raise error.ParseError(err)
117 117 return l
118 118
119 119 def getset(repo, subset, x):
120 120 if not x:
121 121 raise error.ParseError(_("missing argument"))
122 122 return methods[x[0]](repo, subset, *x[1:])
123 123
124 124 # operator methods
125 125
126 126 def stringset(repo, subset, x):
127 127 x = repo[x].rev()
128 128 if x == -1 and len(subset) == len(repo):
129 129 return [-1]
130 130 if len(subset) == len(repo) or x in subset:
131 131 return [x]
132 132 return []
133 133
134 134 def symbolset(repo, subset, x):
135 135 if x in symbols:
136 136 raise error.ParseError(_("can't use %s here") % x)
137 137 return stringset(repo, subset, x)
138 138
139 139 def rangeset(repo, subset, x, y):
140 140 m = getset(repo, subset, x)
141 141 if not m:
142 142 m = getset(repo, range(len(repo)), x)
143 143
144 144 n = getset(repo, subset, y)
145 145 if not n:
146 146 n = getset(repo, range(len(repo)), y)
147 147
148 148 if not m or not n:
149 149 return []
150 150 m, n = m[0], n[-1]
151 151
152 152 if m < n:
153 153 r = range(m, n + 1)
154 154 else:
155 155 r = range(m, n - 1, -1)
156 156 s = set(subset)
157 157 return [x for x in r if x in s]
158 158
159 159 def andset(repo, subset, x, y):
160 160 return getset(repo, getset(repo, subset, x), y)
161 161
162 162 def orset(repo, subset, x, y):
163 163 xl = getset(repo, subset, x)
164 164 s = set(xl)
165 165 yl = getset(repo, [r for r in subset if r not in s], y)
166 166 return xl + yl
167 167
168 168 def notset(repo, subset, x):
169 169 s = set(getset(repo, subset, x))
170 170 return [r for r in subset if r not in s]
171 171
172 172 def listset(repo, subset, a, b):
173 173 raise error.ParseError(_("can't use a list in this context"))
174 174
175 175 def func(repo, subset, a, b):
176 176 if a[0] == 'symbol' and a[1] in symbols:
177 177 return symbols[a[1]](repo, subset, b)
178 178 raise error.ParseError(_("not a function: %s") % a[1])
179 179
180 180 # functions
181 181
182 182 def adds(repo, subset, x):
183 183 """``adds(pattern)``
184 184 Changesets that add a file matching pattern.
185 185 """
186 186 # i18n: "adds" is a keyword
187 187 pat = getstring(x, _("adds requires a pattern"))
188 188 return checkstatus(repo, subset, pat, 1)
189 189
190 190 def ancestor(repo, subset, x):
191 191 """``ancestor(single, single)``
192 192 Greatest common ancestor of the two changesets.
193 193 """
194 194 # i18n: "ancestor" is a keyword
195 195 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
196 196 r = range(len(repo))
197 197 a = getset(repo, r, l[0])
198 198 b = getset(repo, r, l[1])
199 199 if len(a) != 1 or len(b) != 1:
200 200 # i18n: "ancestor" is a keyword
201 201 raise error.ParseError(_("ancestor arguments must be single revisions"))
202 202 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
203 203
204 204 return [r for r in an if r in subset]
205 205
206 206 def ancestors(repo, subset, x):
207 207 """``ancestors(set)``
208 208 Changesets that are ancestors of a changeset in set.
209 209 """
210 210 args = getset(repo, range(len(repo)), x)
211 211 if not args:
212 212 return []
213 213 s = set(repo.changelog.ancestors(*args)) | set(args)
214 214 return [r for r in subset if r in s]
215 215
216 216 def ancestorspec(repo, subset, x, n):
217 217 """``set~n``
218 218 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
219 219 """
220 220 try:
221 221 n = int(n[1])
222 222 except (TypeError, ValueError):
223 223 raise error.ParseError(_("~ expects a number"))
224 224 ps = set()
225 225 cl = repo.changelog
226 226 for r in getset(repo, subset, x):
227 227 for i in range(n):
228 228 r = cl.parentrevs(r)[0]
229 229 ps.add(r)
230 230 return [r for r in subset if r in ps]
231 231
232 232 def author(repo, subset, x):
233 233 """``author(string)``
234 234 Alias for ``user(string)``.
235 235 """
236 236 # i18n: "author" is a keyword
237 237 n = encoding.lower(getstring(x, _("author requires a string")))
238 238 return [r for r in subset if n in encoding.lower(repo[r].user())]
239 239
240 240 def bisect(repo, subset, x):
241 241 """``bisect(string)``
242 242 Changesets marked in the specified bisect status:
243 243
244 244 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
245 245 - ``goods``, ``bads`` : csets topologicaly good/bad
246 246 - ``range`` : csets taking part in the bisection
247 247 - ``pruned`` : csets that are goods, bads or skipped
248 248 - ``untested`` : csets whose fate is yet unknown
249 249 - ``ignored`` : csets ignored due to DAG topology
250 250 """
251 251 status = getstring(x, _("bisect requires a string")).lower()
252 252 return [r for r in subset if r in hbisect.get(repo, status)]
253 253
254 254 # Backward-compatibility
255 255 # - no help entry so that we do not advertise it any more
256 256 def bisected(repo, subset, x):
257 257 return bisect(repo, subset, x)
258 258
259 259 def bookmark(repo, subset, x):
260 260 """``bookmark([name])``
261 261 The named bookmark or all bookmarks.
262 262 """
263 263 # i18n: "bookmark" is a keyword
264 264 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
265 265 if args:
266 266 bm = getstring(args[0],
267 267 # i18n: "bookmark" is a keyword
268 268 _('the argument to bookmark must be a string'))
269 269 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
270 270 if not bmrev:
271 271 raise util.Abort(_("bookmark '%s' does not exist") % bm)
272 272 bmrev = repo[bmrev].rev()
273 273 return [r for r in subset if r == bmrev]
274 274 bms = set([repo[r].rev()
275 275 for r in bookmarksmod.listbookmarks(repo).values()])
276 276 return [r for r in subset if r in bms]
277 277
278 278 def branch(repo, subset, x):
279 279 """``branch(string or set)``
280 280 All changesets belonging to the given branch or the branches of the given
281 281 changesets.
282 282 """
283 283 try:
284 284 b = getstring(x, '')
285 285 if b in repo.branchmap():
286 286 return [r for r in subset if repo[r].branch() == b]
287 287 except error.ParseError:
288 288 # not a string, but another revspec, e.g. tip()
289 289 pass
290 290
291 291 s = getset(repo, range(len(repo)), x)
292 292 b = set()
293 293 for r in s:
294 294 b.add(repo[r].branch())
295 295 s = set(s)
296 296 return [r for r in subset if r in s or repo[r].branch() in b]
297 297
298 298 def checkstatus(repo, subset, pat, field):
299 299 m = None
300 300 s = []
301 301 fast = not matchmod.patkind(pat)
302 302 for r in subset:
303 303 c = repo[r]
304 304 if fast:
305 305 if pat not in c.files():
306 306 continue
307 307 else:
308 308 if not m or matchmod.patkind(pat) == 'set':
309 309 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
310 310 for f in c.files():
311 311 if m(f):
312 312 break
313 313 else:
314 314 continue
315 315 files = repo.status(c.p1().node(), c.node())[field]
316 316 if fast:
317 317 if pat in files:
318 318 s.append(r)
319 319 else:
320 320 for f in files:
321 321 if m(f):
322 322 s.append(r)
323 323 break
324 324 return s
325 325
326 326 def _children(repo, narrow, s):
327 327 cs = set()
328 328 pr = repo.changelog.parentrevs
329 329 s = set(s)
330 330 for r in xrange(len(repo)):
331 331 for p in pr(r):
332 332 if p in s:
333 333 cs.add(r)
334 334 return cs
335 335
336 336 def children(repo, subset, x):
337 337 """``children(set)``
338 338 Child changesets of changesets in set.
339 339 """
340 340 s = getset(repo, range(len(repo)), x)
341 341 cs = _children(repo, subset, s)
342 342 return [r for r in subset if r in cs]
343 343
344 344 def closed(repo, subset, x):
345 345 """``closed()``
346 346 Changeset is closed.
347 347 """
348 348 # i18n: "closed" is a keyword
349 349 getargs(x, 0, 0, _("closed takes no arguments"))
350 350 return [r for r in subset if repo[r].extra().get('close')]
351 351
352 352 def contains(repo, subset, x):
353 353 """``contains(pattern)``
354 354 Revision contains a file matching pattern. See :hg:`help patterns`
355 355 for information about file patterns.
356 356 """
357 357 # i18n: "contains" is a keyword
358 358 pat = getstring(x, _("contains requires a pattern"))
359 359 m = None
360 360 s = []
361 361 if not matchmod.patkind(pat):
362 362 for r in subset:
363 363 if pat in repo[r]:
364 364 s.append(r)
365 365 else:
366 366 for r in subset:
367 367 c = repo[r]
368 368 if not m or matchmod.patkind(pat) == 'set':
369 369 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
370 370 for f in c.manifest():
371 371 if m(f):
372 372 s.append(r)
373 373 break
374 374 return s
375 375
376 376 def date(repo, subset, x):
377 377 """``date(interval)``
378 378 Changesets within the interval, see :hg:`help dates`.
379 379 """
380 380 # i18n: "date" is a keyword
381 381 ds = getstring(x, _("date requires a string"))
382 382 dm = util.matchdate(ds)
383 383 return [r for r in subset if dm(repo[r].date()[0])]
384 384
385 385 def desc(repo, subset, x):
386 386 """``desc(string)``
387 387 Search commit message for string. The match is case-insensitive.
388 388 """
389 389 # i18n: "desc" is a keyword
390 390 ds = encoding.lower(getstring(x, _("desc requires a string")))
391 391 l = []
392 392 for r in subset:
393 393 c = repo[r]
394 394 if ds in encoding.lower(c.description()):
395 395 l.append(r)
396 396 return l
397 397
398 398 def descendants(repo, subset, x):
399 399 """``descendants(set)``
400 400 Changesets which are descendants of changesets in set.
401 401 """
402 402 args = getset(repo, range(len(repo)), x)
403 403 if not args:
404 404 return []
405 405 s = set(repo.changelog.descendants(*args)) | set(args)
406 406 return [r for r in subset if r in s]
407 407
408 408 def draft(repo, subset, x):
409 409 """``draft()``
410 410 Changeset in draft phase."""
411 411 getargs(x, 0, 0, _("draft takes no arguments"))
412 412 return [r for r in subset if repo._phaserev[r] == phases.draft]
413 413
414 414 def filelog(repo, subset, x):
415 415 """``filelog(pattern)``
416 416 Changesets connected to the specified filelog.
417 417 """
418 418
419 419 pat = getstring(x, _("filelog requires a pattern"))
420 420 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
421 421 ctx=repo[None])
422 422 s = set()
423 423
424 424 if not matchmod.patkind(pat):
425 425 for f in m.files():
426 426 fl = repo.file(f)
427 427 for fr in fl:
428 428 s.add(fl.linkrev(fr))
429 429 else:
430 430 for f in repo[None]:
431 431 if m(f):
432 432 fl = repo.file(f)
433 433 for fr in fl:
434 434 s.add(fl.linkrev(fr))
435 435
436 436 return [r for r in subset if r in s]
437 437
438 438 def first(repo, subset, x):
439 439 """``first(set, [n])``
440 440 An alias for limit().
441 441 """
442 442 return limit(repo, subset, x)
443 443
444 444 def follow(repo, subset, x):
445 445 """``follow([file])``
446 446 An alias for ``::.`` (ancestors of the working copy's first parent).
447 447 If a filename is specified, the history of the given file is followed,
448 448 including copies.
449 449 """
450 450 # i18n: "follow" is a keyword
451 451 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
452 452 c = repo['.']
453 453 if l:
454 454 x = getstring(l[0], _("follow expected a filename"))
455 455 if x in c:
456 456 cx = c[x]
457 457 s = set(ctx.rev() for ctx in cx.ancestors())
458 458 # include the revision responsible for the most recent version
459 459 s.add(cx.linkrev())
460 460 else:
461 461 return []
462 462 else:
463 463 s = set(repo.changelog.ancestors(c.rev()))
464 464 s.add(c.rev())
465 465
466 466 return [r for r in subset if r in s]
467 467
468 468 def getall(repo, subset, x):
469 469 """``all()``
470 470 All changesets, the same as ``0:tip``.
471 471 """
472 472 # i18n: "all" is a keyword
473 473 getargs(x, 0, 0, _("all takes no arguments"))
474 474 return subset
475 475
476 476 def grep(repo, subset, x):
477 477 """``grep(regex)``
478 478 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
479 479 to ensure special escape characters are handled correctly. Unlike
480 480 ``keyword(string)``, the match is case-sensitive.
481 481 """
482 482 try:
483 483 # i18n: "grep" is a keyword
484 484 gr = re.compile(getstring(x, _("grep requires a string")))
485 485 except re.error, e:
486 486 raise error.ParseError(_('invalid match pattern: %s') % e)
487 487 l = []
488 488 for r in subset:
489 489 c = repo[r]
490 490 for e in c.files() + [c.user(), c.description()]:
491 491 if gr.search(e):
492 492 l.append(r)
493 493 break
494 494 return l
495 495
496 496 def hasfile(repo, subset, x):
497 497 """``file(pattern)``
498 498 Changesets affecting files matched by pattern.
499 499 """
500 500 # i18n: "file" is a keyword
501 501 pat = getstring(x, _("file requires a pattern"))
502 502 m = None
503 503 s = []
504 504 for r in subset:
505 505 c = repo[r]
506 506 if not m or matchmod.patkind(pat) == 'set':
507 507 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
508 508 for f in c.files():
509 509 if m(f):
510 510 s.append(r)
511 511 break
512 512 return s
513 513
514 514 def head(repo, subset, x):
515 515 """``head()``
516 516 Changeset is a named branch head.
517 517 """
518 518 # i18n: "head" is a keyword
519 519 getargs(x, 0, 0, _("head takes no arguments"))
520 520 hs = set()
521 521 for b, ls in repo.branchmap().iteritems():
522 522 hs.update(repo[h].rev() for h in ls)
523 523 return [r for r in subset if r in hs]
524 524
525 525 def heads(repo, subset, x):
526 526 """``heads(set)``
527 527 Members of set with no children in set.
528 528 """
529 529 s = getset(repo, subset, x)
530 530 ps = set(parents(repo, subset, x))
531 531 return [r for r in s if r not in ps]
532 532
533 533 def keyword(repo, subset, x):
534 534 """``keyword(string)``
535 535 Search commit message, user name, and names of changed files for
536 536 string. The match is case-insensitive.
537 537 """
538 538 # i18n: "keyword" is a keyword
539 539 kw = encoding.lower(getstring(x, _("keyword requires a string")))
540 540 l = []
541 541 for r in subset:
542 542 c = repo[r]
543 543 t = " ".join(c.files() + [c.user(), c.description()])
544 544 if kw in encoding.lower(t):
545 545 l.append(r)
546 546 return l
547 547
548 548 def limit(repo, subset, x):
549 549 """``limit(set, [n])``
550 550 First n members of set, defaulting to 1.
551 551 """
552 552 # i18n: "limit" is a keyword
553 553 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
554 554 try:
555 555 lim = 1
556 556 if len(l) == 2:
557 557 # i18n: "limit" is a keyword
558 558 lim = int(getstring(l[1], _("limit requires a number")))
559 559 except (TypeError, ValueError):
560 560 # i18n: "limit" is a keyword
561 561 raise error.ParseError(_("limit expects a number"))
562 562 ss = set(subset)
563 563 os = getset(repo, range(len(repo)), l[0])[:lim]
564 564 return [r for r in os if r in ss]
565 565
566 566 def last(repo, subset, x):
567 567 """``last(set, [n])``
568 568 Last n members of set, defaulting to 1.
569 569 """
570 570 # i18n: "last" is a keyword
571 571 l = getargs(x, 1, 2, _("last requires one or two arguments"))
572 572 try:
573 573 lim = 1
574 574 if len(l) == 2:
575 575 # i18n: "last" is a keyword
576 576 lim = int(getstring(l[1], _("last requires a number")))
577 577 except (TypeError, ValueError):
578 578 # i18n: "last" is a keyword
579 579 raise error.ParseError(_("last expects a number"))
580 580 ss = set(subset)
581 581 os = getset(repo, range(len(repo)), l[0])[-lim:]
582 582 return [r for r in os if r in ss]
583 583
584 584 def maxrev(repo, subset, x):
585 585 """``max(set)``
586 586 Changeset with highest revision number in set.
587 587 """
588 588 os = getset(repo, range(len(repo)), x)
589 589 if os:
590 590 m = max(os)
591 591 if m in subset:
592 592 return [m]
593 593 return []
594 594
595 595 def merge(repo, subset, x):
596 596 """``merge()``
597 597 Changeset is a merge changeset.
598 598 """
599 599 # i18n: "merge" is a keyword
600 600 getargs(x, 0, 0, _("merge takes no arguments"))
601 601 cl = repo.changelog
602 602 return [r for r in subset if cl.parentrevs(r)[1] != -1]
603 603
604 604 def minrev(repo, subset, x):
605 605 """``min(set)``
606 606 Changeset with lowest revision number in set.
607 607 """
608 608 os = getset(repo, range(len(repo)), x)
609 609 if os:
610 610 m = min(os)
611 611 if m in subset:
612 612 return [m]
613 613 return []
614 614
615 615 def modifies(repo, subset, x):
616 616 """``modifies(pattern)``
617 617 Changesets modifying files matched by pattern.
618 618 """
619 619 # i18n: "modifies" is a keyword
620 620 pat = getstring(x, _("modifies requires a pattern"))
621 621 return checkstatus(repo, subset, pat, 0)
622 622
623 623 def node(repo, subset, x):
624 624 """``id(string)``
625 625 Revision non-ambiguously specified by the given hex string prefix.
626 626 """
627 627 # i18n: "id" is a keyword
628 628 l = getargs(x, 1, 1, _("id requires one argument"))
629 629 # i18n: "id" is a keyword
630 630 n = getstring(l[0], _("id requires a string"))
631 631 if len(n) == 40:
632 632 rn = repo[n].rev()
633 633 else:
634 634 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
635 635 return [r for r in subset if r == rn]
636 636
637 637 def outgoing(repo, subset, x):
638 638 """``outgoing([path])``
639 639 Changesets not found in the specified destination repository, or the
640 640 default push location.
641 641 """
642 642 import hg # avoid start-up nasties
643 643 # i18n: "outgoing" is a keyword
644 644 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
645 645 # i18n: "outgoing" is a keyword
646 646 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
647 647 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
648 648 dest, branches = hg.parseurl(dest)
649 649 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
650 650 if revs:
651 651 revs = [repo.lookup(rev) for rev in revs]
652 652 other = hg.peer(repo, {}, dest)
653 653 repo.ui.pushbuffer()
654 654 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
655 655 repo.ui.popbuffer()
656 656 cl = repo.changelog
657 657 o = set([cl.rev(r) for r in outgoing.missing])
658 658 return [r for r in subset if r in o]
659 659
660 660 def p1(repo, subset, x):
661 661 """``p1([set])``
662 662 First parent of changesets in set, or the working directory.
663 663 """
664 664 if x is None:
665 665 p = repo[x].p1().rev()
666 666 return [r for r in subset if r == p]
667 667
668 668 ps = set()
669 669 cl = repo.changelog
670 670 for r in getset(repo, range(len(repo)), x):
671 671 ps.add(cl.parentrevs(r)[0])
672 672 return [r for r in subset if r in ps]
673 673
674 674 def p2(repo, subset, x):
675 675 """``p2([set])``
676 676 Second parent of changesets in set, or the working directory.
677 677 """
678 678 if x is None:
679 679 ps = repo[x].parents()
680 680 try:
681 681 p = ps[1].rev()
682 682 return [r for r in subset if r == p]
683 683 except IndexError:
684 684 return []
685 685
686 686 ps = set()
687 687 cl = repo.changelog
688 688 for r in getset(repo, range(len(repo)), x):
689 689 ps.add(cl.parentrevs(r)[1])
690 690 return [r for r in subset if r in ps]
691 691
692 692 def parents(repo, subset, x):
693 693 """``parents([set])``
694 694 The set of all parents for all changesets in set, or the working directory.
695 695 """
696 696 if x is None:
697 697 ps = tuple(p.rev() for p in repo[x].parents())
698 698 return [r for r in subset if r in ps]
699 699
700 700 ps = set()
701 701 cl = repo.changelog
702 702 for r in getset(repo, range(len(repo)), x):
703 703 ps.update(cl.parentrevs(r))
704 704 return [r for r in subset if r in ps]
705 705
706 706 def parentspec(repo, subset, x, n):
707 707 """``set^0``
708 708 The set.
709 709 ``set^1`` (or ``set^``), ``set^2``
710 710 First or second parent, respectively, of all changesets in set.
711 711 """
712 712 try:
713 713 n = int(n[1])
714 714 if n not in (0, 1, 2):
715 715 raise ValueError
716 716 except (TypeError, ValueError):
717 717 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
718 718 ps = set()
719 719 cl = repo.changelog
720 720 for r in getset(repo, subset, x):
721 721 if n == 0:
722 722 ps.add(r)
723 723 elif n == 1:
724 724 ps.add(cl.parentrevs(r)[0])
725 725 elif n == 2:
726 726 parents = cl.parentrevs(r)
727 727 if len(parents) > 1:
728 728 ps.add(parents[1])
729 729 return [r for r in subset if r in ps]
730 730
731 731 def present(repo, subset, x):
732 732 """``present(set)``
733 733 An empty set, if any revision in set isn't found; otherwise,
734 734 all revisions in set.
735 735 """
736 736 try:
737 737 return getset(repo, subset, x)
738 738 except error.RepoLookupError:
739 739 return []
740 740
741 741 def public(repo, subset, x):
742 742 """``public()``
743 743 Changeset in public phase."""
744 744 getargs(x, 0, 0, _("public takes no arguments"))
745 745 return [r for r in subset if repo._phaserev[r] == phases.public]
746 746
747 747 def remote(repo, subset, x):
748 748 """``remote([id [,path]])``
749 749 Local revision that corresponds to the given identifier in a
750 750 remote repository, if present. Here, the '.' identifier is a
751 751 synonym for the current local branch.
752 752 """
753 753
754 754 import hg # avoid start-up nasties
755 755 # i18n: "remote" is a keyword
756 756 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
757 757
758 758 q = '.'
759 759 if len(l) > 0:
760 760 # i18n: "remote" is a keyword
761 761 q = getstring(l[0], _("remote requires a string id"))
762 762 if q == '.':
763 763 q = repo['.'].branch()
764 764
765 765 dest = ''
766 766 if len(l) > 1:
767 767 # i18n: "remote" is a keyword
768 768 dest = getstring(l[1], _("remote requires a repository path"))
769 769 dest = repo.ui.expandpath(dest or 'default')
770 770 dest, branches = hg.parseurl(dest)
771 771 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
772 772 if revs:
773 773 revs = [repo.lookup(rev) for rev in revs]
774 774 other = hg.peer(repo, {}, dest)
775 775 n = other.lookup(q)
776 776 if n in repo:
777 777 r = repo[n].rev()
778 778 if r in subset:
779 779 return [r]
780 780 return []
781 781
782 782 def removes(repo, subset, x):
783 783 """``removes(pattern)``
784 784 Changesets which remove files matching pattern.
785 785 """
786 786 # i18n: "removes" is a keyword
787 787 pat = getstring(x, _("removes requires a pattern"))
788 788 return checkstatus(repo, subset, pat, 2)
789 789
790 790 def rev(repo, subset, x):
791 791 """``rev(number)``
792 792 Revision with the given numeric identifier.
793 793 """
794 794 # i18n: "rev" is a keyword
795 795 l = getargs(x, 1, 1, _("rev requires one argument"))
796 796 try:
797 797 # i18n: "rev" is a keyword
798 798 l = int(getstring(l[0], _("rev requires a number")))
799 799 except (TypeError, ValueError):
800 800 # i18n: "rev" is a keyword
801 801 raise error.ParseError(_("rev expects a number"))
802 802 return [r for r in subset if r == l]
803 803
804 804 def reverse(repo, subset, x):
805 805 """``reverse(set)``
806 806 Reverse order of set.
807 807 """
808 808 l = getset(repo, subset, x)
809 809 l.reverse()
810 810 return l
811 811
812 812 def roots(repo, subset, x):
813 813 """``roots(set)``
814 814 Changesets with no parent changeset in set.
815 815 """
816 816 s = getset(repo, xrange(len(repo)), x)
817 817 cs = _children(repo, s, s)
818 818 return [r for r in s if r not in cs]
819 819
820 820 def secret(repo, subset, x):
821 821 """``secret()``
822 822 Changeset in secret phase."""
823 823 getargs(x, 0, 0, _("secret takes no arguments"))
824 824 return [r for r in subset if repo._phaserev[r] == phases.secret]
825 825
826 826 def sort(repo, subset, x):
827 827 """``sort(set[, [-]key...])``
828 828 Sort set by keys. The default sort order is ascending, specify a key
829 829 as ``-key`` to sort in descending order.
830 830
831 831 The keys can be:
832 832
833 833 - ``rev`` for the revision number,
834 834 - ``branch`` for the branch name,
835 835 - ``desc`` for the commit message (description),
836 836 - ``user`` for user name (``author`` can be used as an alias),
837 837 - ``date`` for the commit date
838 838 """
839 839 # i18n: "sort" is a keyword
840 840 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
841 841 keys = "rev"
842 842 if len(l) == 2:
843 843 keys = getstring(l[1], _("sort spec must be a string"))
844 844
845 845 s = l[0]
846 846 keys = keys.split()
847 847 l = []
848 848 def invert(s):
849 849 return "".join(chr(255 - ord(c)) for c in s)
850 850 for r in getset(repo, subset, s):
851 851 c = repo[r]
852 852 e = []
853 853 for k in keys:
854 854 if k == 'rev':
855 855 e.append(r)
856 856 elif k == '-rev':
857 857 e.append(-r)
858 858 elif k == 'branch':
859 859 e.append(c.branch())
860 860 elif k == '-branch':
861 861 e.append(invert(c.branch()))
862 862 elif k == 'desc':
863 863 e.append(c.description())
864 864 elif k == '-desc':
865 865 e.append(invert(c.description()))
866 866 elif k in 'user author':
867 867 e.append(c.user())
868 868 elif k in '-user -author':
869 869 e.append(invert(c.user()))
870 870 elif k == 'date':
871 871 e.append(c.date()[0])
872 872 elif k == '-date':
873 873 e.append(-c.date()[0])
874 874 else:
875 875 raise error.ParseError(_("unknown sort key %r") % k)
876 876 e.append(r)
877 877 l.append(e)
878 878 l.sort()
879 879 return [e[-1] for e in l]
880 880
881 881 def tag(repo, subset, x):
882 882 """``tag([name])``
883 883 The specified tag by name, or all tagged revisions if no name is given.
884 884 """
885 885 # i18n: "tag" is a keyword
886 886 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
887 887 cl = repo.changelog
888 888 if args:
889 889 tn = getstring(args[0],
890 890 # i18n: "tag" is a keyword
891 891 _('the argument to tag must be a string'))
892 892 if not repo.tags().get(tn, None):
893 893 raise util.Abort(_("tag '%s' does not exist") % tn)
894 894 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
895 895 else:
896 896 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
897 897 return [r for r in subset if r in s]
898 898
899 899 def tagged(repo, subset, x):
900 900 return tag(repo, subset, x)
901 901
902 902 def user(repo, subset, x):
903 903 """``user(string)``
904 904 User name contains string. The match is case-insensitive.
905 905 """
906 906 return author(repo, subset, x)
907 907
908 908 # for internal use
909 909 def _list(repo, subset, x):
910 910 s = getstring(x, "internal error")
911 911 if not s:
912 912 return []
913 913 if not isinstance(subset, set):
914 914 subset = set(subset)
915 915 ls = [repo[r].rev() for r in s.split('\0')]
916 916 return [r for r in ls if r in subset]
917 917
918 918 symbols = {
919 919 "adds": adds,
920 920 "all": getall,
921 921 "ancestor": ancestor,
922 922 "ancestors": ancestors,
923 923 "author": author,
924 924 "bisect": bisect,
925 925 "bisected": bisected,
926 926 "bookmark": bookmark,
927 927 "branch": branch,
928 928 "children": children,
929 929 "closed": closed,
930 930 "contains": contains,
931 931 "date": date,
932 932 "desc": desc,
933 933 "descendants": descendants,
934 934 "draft": draft,
935 935 "file": hasfile,
936 936 "filelog": filelog,
937 937 "first": first,
938 938 "follow": follow,
939 939 "grep": grep,
940 940 "head": head,
941 941 "heads": heads,
942 942 "id": node,
943 943 "keyword": keyword,
944 944 "last": last,
945 945 "limit": limit,
946 946 "max": maxrev,
947 947 "merge": merge,
948 948 "min": minrev,
949 949 "modifies": modifies,
950 950 "outgoing": outgoing,
951 951 "p1": p1,
952 952 "p2": p2,
953 953 "parents": parents,
954 954 "present": present,
955 955 "public": public,
956 956 "remote": remote,
957 957 "removes": removes,
958 958 "rev": rev,
959 959 "reverse": reverse,
960 960 "roots": roots,
961 961 "sort": sort,
962 962 "secret": secret,
963 963 "tag": tag,
964 964 "tagged": tagged,
965 965 "user": user,
966 966 "_list": _list,
967 967 }
968 968
969 969 methods = {
970 970 "range": rangeset,
971 971 "string": stringset,
972 972 "symbol": symbolset,
973 973 "and": andset,
974 974 "or": orset,
975 975 "not": notset,
976 976 "list": listset,
977 977 "func": func,
978 978 "ancestor": ancestorspec,
979 979 "parent": parentspec,
980 980 "parentpost": p1,
981 981 }
982 982
983 983 def optimize(x, small):
984 984 if x is None:
985 985 return 0, x
986 986
987 987 smallbonus = 1
988 988 if small:
989 989 smallbonus = .5
990 990
991 991 op = x[0]
992 992 if op == 'minus':
993 993 return optimize(('and', x[1], ('not', x[2])), small)
994 994 elif op == 'dagrange':
995 995 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
996 996 ('func', ('symbol', 'ancestors'), x[2])), small)
997 997 elif op == 'dagrangepre':
998 998 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
999 999 elif op == 'dagrangepost':
1000 1000 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1001 1001 elif op == 'rangepre':
1002 1002 return optimize(('range', ('string', '0'), x[1]), small)
1003 1003 elif op == 'rangepost':
1004 1004 return optimize(('range', x[1], ('string', 'tip')), small)
1005 1005 elif op == 'negate':
1006 1006 return optimize(('string',
1007 1007 '-' + getstring(x[1], _("can't negate that"))), small)
1008 1008 elif op in 'string symbol negate':
1009 1009 return smallbonus, x # single revisions are small
1010 1010 elif op == 'and' or op == 'dagrange':
1011 1011 wa, ta = optimize(x[1], True)
1012 1012 wb, tb = optimize(x[2], True)
1013 1013 w = min(wa, wb)
1014 1014 if wa > wb:
1015 1015 return w, (op, tb, ta)
1016 1016 return w, (op, ta, tb)
1017 1017 elif op == 'or':
1018 1018 wa, ta = optimize(x[1], False)
1019 1019 wb, tb = optimize(x[2], False)
1020 1020 if wb < wa:
1021 1021 wb, wa = wa, wb
1022 1022 return max(wa, wb), (op, ta, tb)
1023 1023 elif op == 'not':
1024 1024 o = optimize(x[1], not small)
1025 1025 return o[0], (op, o[1])
1026 1026 elif op == 'parentpost':
1027 1027 o = optimize(x[1], small)
1028 1028 return o[0], (op, o[1])
1029 1029 elif op == 'group':
1030 1030 return optimize(x[1], small)
1031 1031 elif op in 'range list parent ancestorspec':
1032 1032 if op == 'parent':
1033 1033 # x^:y means (x^) : y, not x ^ (:y)
1034 1034 post = ('parentpost', x[1])
1035 1035 if x[2][0] == 'dagrangepre':
1036 1036 return optimize(('dagrange', post, x[2][1]), small)
1037 1037 elif x[2][0] == 'rangepre':
1038 1038 return optimize(('range', post, x[2][1]), small)
1039 1039
1040 1040 wa, ta = optimize(x[1], small)
1041 1041 wb, tb = optimize(x[2], small)
1042 1042 return wa + wb, (op, ta, tb)
1043 1043 elif op == 'func':
1044 1044 f = getstring(x[1], _("not a symbol"))
1045 1045 wa, ta = optimize(x[2], small)
1046 1046 if f in ("author branch closed date desc file grep keyword "
1047 1047 "outgoing user"):
1048 1048 w = 10 # slow
1049 1049 elif f in "modifies adds removes":
1050 1050 w = 30 # slower
1051 1051 elif f == "contains":
1052 1052 w = 100 # very slow
1053 1053 elif f == "ancestor":
1054 1054 w = 1 * smallbonus
1055 1055 elif f in "reverse limit first":
1056 1056 w = 0
1057 1057 elif f in "sort":
1058 1058 w = 10 # assume most sorts look at changelog
1059 1059 else:
1060 1060 w = 1
1061 1061 return w + wa, (op, x[1], ta)
1062 1062 return 1, x
1063 1063
1064 1064 class revsetalias(object):
1065 1065 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1066 1066 args = None
1067 1067
1068 1068 def __init__(self, name, value):
1069 1069 '''Aliases like:
1070 1070
1071 1071 h = heads(default)
1072 1072 b($1) = ancestors($1) - ancestors(default)
1073 1073 '''
1074 if isinstance(name, tuple): # parameter substitution
1075 self.tree = name
1076 self.replacement = value
1077 else: # alias definition
1078 1074 m = self.funcre.search(name)
1079 1075 if m:
1076 self.name = m.group(1)
1080 1077 self.tree = ('func', ('symbol', m.group(1)))
1081 1078 self.args = [x.strip() for x in m.group(2).split(',')]
1082 1079 for arg in self.args:
1083 1080 value = value.replace(arg, repr(arg))
1084 1081 else:
1082 self.name = name
1085 1083 self.tree = ('symbol', name)
1086 1084
1087 1085 self.replacement, pos = parse(value)
1088 1086 if pos != len(value):
1089 1087 raise error.ParseError(_('invalid token'), pos)
1090 1088
1091 def process(self, tree):
1092 if isinstance(tree, tuple):
1093 if self.args is None:
1094 if tree == self.tree:
1095 return self.replacement
1096 elif tree[:2] == self.tree:
1089 def _getalias(aliases, tree):
1090 """If tree looks like an unexpanded alias, return it. Return None
1091 otherwise.
1092 """
1093 if isinstance(tree, tuple) and tree:
1094 if tree[0] == 'symbol' and len(tree) == 2:
1095 name = tree[1]
1096 alias = aliases.get(name)
1097 if alias and alias.args is None and alias.tree == tree:
1098 return alias
1099 if tree[0] == 'func' and len(tree) > 1:
1100 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1101 name = tree[1][1]
1102 alias = aliases.get(name)
1103 if alias and alias.args is not None and alias.tree == tree[:2]:
1104 return alias
1105 return None
1106
1107 def _expandargs(tree, args):
1108 """Replace all occurences of ('string', name) with the
1109 substitution value of the same name in args, recursively.
1110 """
1111 if not isinstance(tree, tuple):
1112 return tree
1113 if len(tree) == 2 and tree[0] == 'string':
1114 return args.get(tree[1], tree)
1115 return tuple(_expandargs(t, args) for t in tree)
1116
1117 def _expandaliases(aliases, tree, expanding):
1118 """Expand aliases in tree, recursively.
1119
1120 'aliases' is a dictionary mapping user defined aliases to
1121 revsetalias objects.
1122 """
1123 if not isinstance(tree, tuple):
1124 # Do not expand raw strings
1125 return tree
1126 alias = _getalias(aliases, tree)
1127 if alias is not None:
1128 if alias in expanding:
1129 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1130 'detected') % alias.name)
1131 expanding.append(alias)
1132 result = alias.replacement
1133 if alias.args is not None:
1097 1134 l = getlist(tree[2])
1098 if len(l) != len(self.args):
1135 if len(l) != len(alias.args):
1099 1136 raise error.ParseError(
1100 1137 _('invalid number of arguments: %s') % len(l))
1101 result = self.replacement
1102 for a, v in zip(self.args, l):
1103 valalias = revsetalias(('string', a), v)
1104 result = valalias.process(result)
1138 result = _expandargs(result, dict(zip(alias.args, l)))
1139 # Recurse in place, the base expression may have been rewritten
1140 result = _expandaliases(aliases, result, expanding)
1141 expanding.pop()
1142 else:
1143 result = tuple(_expandaliases(aliases, t, expanding)
1144 for t in tree)
1105 1145 return result
1106 return tuple(map(self.process, tree))
1107 return tree
1108 1146
1109 1147 def findaliases(ui, tree):
1148 aliases = {}
1110 1149 for k, v in ui.configitems('revsetalias'):
1111 1150 alias = revsetalias(k, v)
1112 tree = alias.process(tree)
1113 return tree
1151 aliases[alias.name] = alias
1152 return _expandaliases(aliases, tree, [])
1114 1153
1115 1154 parse = parser.parser(tokenize, elements).parse
1116 1155
1117 1156 def match(ui, spec):
1118 1157 if not spec:
1119 1158 raise error.ParseError(_("empty query"))
1120 1159 tree, pos = parse(spec)
1121 1160 if (pos != len(spec)):
1122 1161 raise error.ParseError(_("invalid token"), pos)
1123 1162 if ui:
1124 1163 tree = findaliases(ui, tree)
1125 1164 weight, tree = optimize(tree, True)
1126 1165 def mfunc(repo, subset):
1127 1166 return getset(repo, subset, tree)
1128 1167 return mfunc
1129 1168
1130 1169 def formatspec(expr, *args):
1131 1170 '''
1132 1171 This is a convenience function for using revsets internally, and
1133 1172 escapes arguments appropriately. Aliases are intentionally ignored
1134 1173 so that intended expression behavior isn't accidentally subverted.
1135 1174
1136 1175 Supported arguments:
1137 1176
1138 1177 %r = revset expression, parenthesized
1139 1178 %d = int(arg), no quoting
1140 1179 %s = string(arg), escaped and single-quoted
1141 1180 %b = arg.branch(), escaped and single-quoted
1142 1181 %n = hex(arg), single-quoted
1143 1182 %% = a literal '%'
1144 1183
1145 1184 Prefixing the type with 'l' specifies a parenthesized list of that type.
1146 1185
1147 1186 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1148 1187 '(10 or 11):: and ((this()) or (that()))'
1149 1188 >>> formatspec('%d:: and not %d::', 10, 20)
1150 1189 '10:: and not 20::'
1151 1190 >>> formatspec('%ld or %ld', [], [1])
1152 1191 "_list('') or 1"
1153 1192 >>> formatspec('keyword(%s)', 'foo\\xe9')
1154 1193 "keyword('foo\\\\xe9')"
1155 1194 >>> b = lambda: 'default'
1156 1195 >>> b.branch = b
1157 1196 >>> formatspec('branch(%b)', b)
1158 1197 "branch('default')"
1159 1198 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1160 1199 "root(_list('a\\x00b\\x00c\\x00d'))"
1161 1200 '''
1162 1201
1163 1202 def quote(s):
1164 1203 return repr(str(s))
1165 1204
1166 1205 def argtype(c, arg):
1167 1206 if c == 'd':
1168 1207 return str(int(arg))
1169 1208 elif c == 's':
1170 1209 return quote(arg)
1171 1210 elif c == 'r':
1172 1211 parse(arg) # make sure syntax errors are confined
1173 1212 return '(%s)' % arg
1174 1213 elif c == 'n':
1175 1214 return quote(nodemod.hex(arg))
1176 1215 elif c == 'b':
1177 1216 return quote(arg.branch())
1178 1217
1179 1218 def listexp(s, t):
1180 1219 l = len(s)
1181 1220 if l == 0:
1182 1221 return "_list('')"
1183 1222 elif l == 1:
1184 1223 return argtype(t, s[0])
1185 1224 elif t == 'd':
1186 1225 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1187 1226 elif t == 's':
1188 1227 return "_list('%s')" % "\0".join(s)
1189 1228 elif t == 'n':
1190 1229 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1191 1230 elif t == 'b':
1192 1231 return "_list('%s')" % "\0".join(a.branch() for a in s)
1193 1232
1194 1233 m = l // 2
1195 1234 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1196 1235
1197 1236 ret = ''
1198 1237 pos = 0
1199 1238 arg = 0
1200 1239 while pos < len(expr):
1201 1240 c = expr[pos]
1202 1241 if c == '%':
1203 1242 pos += 1
1204 1243 d = expr[pos]
1205 1244 if d == '%':
1206 1245 ret += d
1207 1246 elif d in 'dsnbr':
1208 1247 ret += argtype(d, args[arg])
1209 1248 arg += 1
1210 1249 elif d == 'l':
1211 1250 # a list of some type
1212 1251 pos += 1
1213 1252 d = expr[pos]
1214 1253 ret += listexp(list(args[arg]), d)
1215 1254 arg += 1
1216 1255 else:
1217 1256 raise util.Abort('unexpected revspec format character %s' % d)
1218 1257 else:
1219 1258 ret += c
1220 1259 pos += 1
1221 1260
1222 1261 return ret
1223 1262
1224 1263 # tell hggettext to extract docstrings from these functions:
1225 1264 i18nfunctions = symbols.values()
@@ -1,553 +1,594 b''
1 1 $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate /a/b/c/ as if it was a real file path
2 2
3 3 $ HGENCODING=utf-8
4 4 $ export HGENCODING
5 5
6 6 $ try() {
7 7 > hg debugrevspec --debug "$@"
8 8 > }
9 9
10 10 $ log() {
11 11 > hg log --template '{rev}\n' -r "$1"
12 12 > }
13 13
14 14 $ hg init repo
15 15 $ cd repo
16 16
17 17 $ echo a > a
18 18 $ hg branch a
19 19 marked working directory as branch a
20 20 (branches are permanent and global, did you want a bookmark?)
21 21 $ hg ci -Aqm0
22 22
23 23 $ echo b > b
24 24 $ hg branch b
25 25 marked working directory as branch b
26 26 (branches are permanent and global, did you want a bookmark?)
27 27 $ hg ci -Aqm1
28 28
29 29 $ rm a
30 30 $ hg branch a-b-c-
31 31 marked working directory as branch a-b-c-
32 32 (branches are permanent and global, did you want a bookmark?)
33 33 $ hg ci -Aqm2 -u Bob
34 34
35 35 $ hg co 1
36 36 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 37 $ hg branch +a+b+c+
38 38 marked working directory as branch +a+b+c+
39 39 (branches are permanent and global, did you want a bookmark?)
40 40 $ hg ci -Aqm3
41 41
42 42 $ hg co 2 # interleave
43 43 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
44 44 $ echo bb > b
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 -Aqm4 -d "May 12 2005"
49 49
50 50 $ hg co 3
51 51 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
52 52 $ hg branch /a/b/c/
53 53 marked working directory as branch /a/b/c/
54 54 (branches are permanent and global, did you want a bookmark?)
55 55 $ hg ci -Aqm"5 bug"
56 56
57 57 $ hg merge 4
58 58 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
59 59 (branch merge, don't forget to commit)
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"6 issue619"
64 64
65 65 $ hg branch .a.b.c.
66 66 marked working directory as branch .a.b.c.
67 67 (branches are permanent and global, did you want a bookmark?)
68 68 $ hg ci -Aqm7
69 69
70 70 $ hg branch all
71 71 marked working directory as branch all
72 72 (branches are permanent and global, did you want a bookmark?)
73 73 $ hg ci --close-branch -Aqm8
74 74 abort: can only close branch heads
75 75 [255]
76 76
77 77 $ hg co 4
78 78 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 79 $ hg branch Γ©
80 80 marked working directory as branch \xc3\xa9 (esc)
81 81 (branches are permanent and global, did you want a bookmark?)
82 82 $ hg ci -Aqm9
83 83
84 84 $ hg tag -r6 1.0
85 85
86 86 $ hg clone --quiet -U -r 7 . ../remote1
87 87 $ hg clone --quiet -U -r 8 . ../remote2
88 88 $ echo "[paths]" >> .hg/hgrc
89 89 $ echo "default = ../remote1" >> .hg/hgrc
90 90
91 91 names that should work without quoting
92 92
93 93 $ try a
94 94 ('symbol', 'a')
95 95 0
96 96 $ try b-a
97 97 ('minus', ('symbol', 'b'), ('symbol', 'a'))
98 98 1
99 99 $ try _a_b_c_
100 100 ('symbol', '_a_b_c_')
101 101 6
102 102 $ try _a_b_c_-a
103 103 ('minus', ('symbol', '_a_b_c_'), ('symbol', 'a'))
104 104 6
105 105 $ try .a.b.c.
106 106 ('symbol', '.a.b.c.')
107 107 7
108 108 $ try .a.b.c.-a
109 109 ('minus', ('symbol', '.a.b.c.'), ('symbol', 'a'))
110 110 7
111 111 $ try -- '-a-b-c-' # complains
112 112 hg: parse error at 7: not a prefix: end
113 113 [255]
114 114 $ log -a-b-c- # succeeds with fallback
115 115 4
116 116 $ try -- -a-b-c--a # complains
117 117 ('minus', ('minus', ('minus', ('negate', ('symbol', 'a')), ('symbol', 'b')), ('symbol', 'c')), ('negate', ('symbol', 'a')))
118 118 abort: unknown revision '-a'!
119 119 [255]
120 120 $ try Γ©
121 121 ('symbol', '\xc3\xa9')
122 122 9
123 123
124 124 quoting needed
125 125
126 126 $ try '"-a-b-c-"-a'
127 127 ('minus', ('string', '-a-b-c-'), ('symbol', 'a'))
128 128 4
129 129
130 130 $ log '1 or 2'
131 131 1
132 132 2
133 133 $ log '1|2'
134 134 1
135 135 2
136 136 $ log '1 and 2'
137 137 $ log '1&2'
138 138 $ try '1&2|3' # precedence - and is higher
139 139 ('or', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
140 140 3
141 141 $ try '1|2&3'
142 142 ('or', ('symbol', '1'), ('and', ('symbol', '2'), ('symbol', '3')))
143 143 1
144 144 $ try '1&2&3' # associativity
145 145 ('and', ('and', ('symbol', '1'), ('symbol', '2')), ('symbol', '3'))
146 146 $ try '1|(2|3)'
147 147 ('or', ('symbol', '1'), ('group', ('or', ('symbol', '2'), ('symbol', '3'))))
148 148 1
149 149 2
150 150 3
151 151 $ log '1.0' # tag
152 152 6
153 153 $ log 'a' # branch
154 154 0
155 155 $ log '2785f51ee'
156 156 0
157 157 $ log 'date(2005)'
158 158 4
159 159 $ log 'date(this is a test)'
160 160 hg: parse error at 10: unexpected token: symbol
161 161 [255]
162 162 $ log 'date()'
163 163 hg: parse error: date requires a string
164 164 [255]
165 165 $ log 'date'
166 166 hg: parse error: can't use date here
167 167 [255]
168 168 $ log 'date('
169 169 hg: parse error at 5: not a prefix: end
170 170 [255]
171 171 $ log 'date(tip)'
172 172 abort: invalid date: 'tip'
173 173 [255]
174 174 $ log '"date"'
175 175 abort: unknown revision 'date'!
176 176 [255]
177 177 $ log 'date(2005) and 1::'
178 178 4
179 179
180 180 $ log 'ancestor(1)'
181 181 hg: parse error: ancestor requires two arguments
182 182 [255]
183 183 $ log 'ancestor(4,5)'
184 184 1
185 185 $ log 'ancestor(4,5) and 4'
186 186 $ log 'ancestors(5)'
187 187 0
188 188 1
189 189 3
190 190 5
191 191 $ log 'author(bob)'
192 192 2
193 193 $ log 'branch(Γ©)'
194 194 8
195 195 9
196 196 $ log 'children(ancestor(4,5))'
197 197 2
198 198 3
199 199 $ log 'closed()'
200 200 $ log 'contains(a)'
201 201 0
202 202 1
203 203 3
204 204 5
205 205 $ log 'desc(B)'
206 206 5
207 207 $ log 'descendants(2 or 3)'
208 208 2
209 209 3
210 210 4
211 211 5
212 212 6
213 213 7
214 214 8
215 215 9
216 216 $ log 'file(b)'
217 217 1
218 218 4
219 219 $ log 'follow()'
220 220 0
221 221 1
222 222 2
223 223 4
224 224 8
225 225 9
226 226 $ log 'grep("issue\d+")'
227 227 6
228 228 $ try 'grep("(")' # invalid regular expression
229 229 ('func', ('symbol', 'grep'), ('string', '('))
230 230 hg: parse error: invalid match pattern: unbalanced parenthesis
231 231 [255]
232 232 $ try 'grep("\bissue\d+")'
233 233 ('func', ('symbol', 'grep'), ('string', '\x08issue\\d+'))
234 234 $ try 'grep(r"\bissue\d+")'
235 235 ('func', ('symbol', 'grep'), ('string', '\\bissue\\d+'))
236 236 6
237 237 $ try 'grep(r"\")'
238 238 hg: parse error at 7: unterminated string
239 239 [255]
240 240 $ log 'head()'
241 241 0
242 242 1
243 243 2
244 244 3
245 245 4
246 246 5
247 247 6
248 248 7
249 249 9
250 250 $ log 'heads(6::)'
251 251 7
252 252 $ log 'keyword(issue)'
253 253 6
254 254 $ log 'limit(head(), 1)'
255 255 0
256 256 $ log 'max(contains(a))'
257 257 5
258 258 $ log 'min(contains(a))'
259 259 0
260 260 $ log 'merge()'
261 261 6
262 262 $ log 'modifies(b)'
263 263 4
264 264 $ log 'id(5)'
265 265 2
266 266 $ log 'outgoing()'
267 267 8
268 268 9
269 269 $ log 'outgoing("../remote1")'
270 270 8
271 271 9
272 272 $ log 'outgoing("../remote2")'
273 273 3
274 274 5
275 275 6
276 276 7
277 277 9
278 278 $ log 'p1(merge())'
279 279 5
280 280 $ log 'p2(merge())'
281 281 4
282 282 $ log 'parents(merge())'
283 283 4
284 284 5
285 285 $ log 'removes(a)'
286 286 2
287 287 6
288 288 $ log 'roots(all())'
289 289 0
290 290 $ log 'reverse(2 or 3 or 4 or 5)'
291 291 5
292 292 4
293 293 3
294 294 2
295 295 $ log 'rev(5)'
296 296 5
297 297 $ log 'sort(limit(reverse(all()), 3))'
298 298 7
299 299 8
300 300 9
301 301 $ log 'sort(2 or 3 or 4 or 5, date)'
302 302 2
303 303 3
304 304 5
305 305 4
306 306 $ log 'tagged()'
307 307 6
308 308 $ log 'tag()'
309 309 6
310 310 $ log 'tag(1.0)'
311 311 6
312 312 $ log 'tag(tip)'
313 313 9
314 314 $ log 'tag(unknown)'
315 315 abort: tag 'unknown' does not exist
316 316 [255]
317 317 $ log 'branch(unknown)'
318 318 abort: unknown revision 'unknown'!
319 319 [255]
320 320 $ log 'user(bob)'
321 321 2
322 322
323 323 $ log '4::8'
324 324 4
325 325 8
326 326 $ log '4:8'
327 327 4
328 328 5
329 329 6
330 330 7
331 331 8
332 332
333 333 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
334 334 4
335 335 2
336 336 5
337 337
338 338 $ log 'not 0 and 0:2'
339 339 1
340 340 2
341 341 $ log 'not 1 and 0:2'
342 342 0
343 343 2
344 344 $ log 'not 2 and 0:2'
345 345 0
346 346 1
347 347 $ log '(1 and 2)::'
348 348 $ log '(1 and 2):'
349 349 $ log '(1 and 2):3'
350 350 $ log 'sort(head(), -rev)'
351 351 9
352 352 7
353 353 6
354 354 5
355 355 4
356 356 3
357 357 2
358 358 1
359 359 0
360 360 $ log '4::8 - 8'
361 361 4
362 362
363 363 issue2437
364 364
365 365 $ log '3 and p1(5)'
366 366 3
367 367 $ log '4 and p2(6)'
368 368 4
369 369 $ log '1 and parents(:2)'
370 370 1
371 371 $ log '2 and children(1:)'
372 372 2
373 373 $ log 'roots(all()) or roots(all())'
374 374 0
375 375 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
376 376 9
377 377 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
378 378 4
379 379
380 380 issue2654: report a parse error if the revset was not completely parsed
381 381
382 382 $ log '1 OR 2'
383 383 hg: parse error at 2: invalid token
384 384 [255]
385 385
386 386 or operator should preserve ordering:
387 387 $ log 'reverse(2::4) or tip'
388 388 4
389 389 2
390 390 9
391 391
392 392 parentrevspec
393 393
394 394 $ log 'merge()^0'
395 395 6
396 396 $ log 'merge()^'
397 397 5
398 398 $ log 'merge()^1'
399 399 5
400 400 $ log 'merge()^2'
401 401 4
402 402 $ log 'merge()^^'
403 403 3
404 404 $ log 'merge()^1^'
405 405 3
406 406 $ log 'merge()^^^'
407 407 1
408 408
409 409 $ log 'merge()~0'
410 410 6
411 411 $ log 'merge()~1'
412 412 5
413 413 $ log 'merge()~2'
414 414 3
415 415 $ log 'merge()~2^1'
416 416 1
417 417 $ log 'merge()~3'
418 418 1
419 419
420 420 $ log '(-3:tip)^'
421 421 4
422 422 6
423 423 8
424 424
425 425 $ log 'tip^foo'
426 426 hg: parse error: ^ expects a number 0, 1, or 2
427 427 [255]
428 428
429 429 aliases:
430 430
431 431 $ echo '[revsetalias]' >> .hg/hgrc
432 432 $ echo 'm = merge()' >> .hg/hgrc
433 $ echo 'sincem = descendants(m)' >> .hg/hgrc
433 434 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
434 435 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
435 436 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
436 437
437 438 $ try m
438 439 ('symbol', 'm')
439 440 ('func', ('symbol', 'merge'), None)
440 441 6
442
443 test alias recursion
444
445 $ try sincem
446 ('symbol', 'sincem')
447 ('func', ('symbol', 'descendants'), ('func', ('symbol', 'merge'), None))
448 6
449 7
450
451 test infinite recursion
452
453 $ echo 'recurse1 = recurse2' >> .hg/hgrc
454 $ echo 'recurse2 = recurse1' >> .hg/hgrc
455 $ try recurse1
456 ('symbol', 'recurse1')
457 hg: parse error: infinite expansion of revset alias "recurse1" detected
458 [255]
459
460 test nesting and variable passing
461
462 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
463 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
464 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
465 $ try 'nested(2:5)'
466 ('func', ('symbol', 'nested'), ('range', ('symbol', '2'), ('symbol', '5')))
467 ('func', ('symbol', 'max'), ('range', ('symbol', '2'), ('symbol', '5')))
468 5
469
470 test variable isolation, variable placeholders are rewritten as string
471 then parsed and matched again as string. Check they do not leak too
472 far away.
473
474 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
475 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
476 $ try 'callinjection(2:5)'
477 ('func', ('symbol', 'callinjection'), ('range', ('symbol', '2'), ('symbol', '5')))
478 ('func', ('symbol', 'descendants'), ('func', ('symbol', 'max'), ('string', '$1')))
479 abort: unknown revision '$1'!
480 [255]
481
441 482 $ try 'd(2:5)'
442 483 ('func', ('symbol', 'd'), ('range', ('symbol', '2'), ('symbol', '5')))
443 484 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('range', ('symbol', '2'), ('symbol', '5')), ('symbol', 'date'))))
444 485 4
445 486 5
446 487 3
447 488 2
448 489 $ try 'rs(2 or 3, date)'
449 490 ('func', ('symbol', 'rs'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date')))
450 491 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
451 492 3
452 493 2
453 494 $ try 'rs()'
454 495 ('func', ('symbol', 'rs'), None)
455 496 hg: parse error: invalid number of arguments: 0
456 497 [255]
457 498 $ try 'rs(2)'
458 499 ('func', ('symbol', 'rs'), ('symbol', '2'))
459 500 hg: parse error: invalid number of arguments: 1
460 501 [255]
461 502 $ try 'rs(2, data, 7)'
462 503 ('func', ('symbol', 'rs'), ('list', ('list', ('symbol', '2'), ('symbol', 'data')), ('symbol', '7')))
463 504 hg: parse error: invalid number of arguments: 3
464 505 [255]
465 506 $ try 'rs4(2 or 3, x, x, date)'
466 507 ('func', ('symbol', 'rs4'), ('list', ('list', ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'x')), ('symbol', 'x')), ('symbol', 'date')))
467 508 ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
468 509 3
469 510 2
470 511
471 512 issue2549 - correct optimizations
472 513
473 514 $ log 'limit(1 or 2 or 3, 2) and not 2'
474 515 1
475 516 $ log 'max(1 or 2) and not 2'
476 517 $ log 'min(1 or 2) and not 1'
477 518 $ log 'last(1 or 2, 1) and not 2'
478 519
479 520 tests for 'remote()' predicate:
480 521 #. (csets in remote) (id) (remote)
481 522 1. less than local current branch "default"
482 523 2. same with local specified "default"
483 524 3. more than local specified specified
484 525
485 526 $ hg clone --quiet -U . ../remote3
486 527 $ cd ../remote3
487 528 $ hg update -q 7
488 529 $ echo r > r
489 530 $ hg ci -Aqm 10
490 531 $ log 'remote()'
491 532 7
492 533 $ log 'remote("a-b-c-")'
493 534 2
494 535 $ cd ../repo
495 536 $ log 'remote(".a.b.c.", "../remote3")'
496 537
497 538 $ cd ..
498 539
499 540 test author/desc/keyword in problematic encoding
500 541 # unicode: cp932:
501 542 # u30A2 0x83 0x41(= 'A')
502 543 # u30C2 0x83 0x61(= 'a')
503 544
504 545 $ hg init problematicencoding
505 546 $ cd problematicencoding
506 547
507 548 $ python > setup.sh <<EOF
508 549 > print u'''
509 550 > echo a > text
510 551 > hg add text
511 552 > hg --encoding utf-8 commit -u '\u30A2' -m none
512 553 > echo b > text
513 554 > hg --encoding utf-8 commit -u '\u30C2' -m none
514 555 > echo c > text
515 556 > hg --encoding utf-8 commit -u none -m '\u30A2'
516 557 > echo d > text
517 558 > hg --encoding utf-8 commit -u none -m '\u30C2'
518 559 > '''.encode('utf-8')
519 560 > EOF
520 561 $ sh < setup.sh
521 562
522 563 test in problematic encoding
523 564 $ python > test.sh <<EOF
524 565 > print u'''
525 566 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
526 567 > echo ====
527 568 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
528 569 > echo ====
529 570 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
530 571 > echo ====
531 572 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
532 573 > echo ====
533 574 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
534 575 > echo ====
535 576 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
536 577 > '''.encode('cp932')
537 578 > EOF
538 579 $ sh < test.sh
539 580 0
540 581 ====
541 582 1
542 583 ====
543 584 2
544 585 ====
545 586 3
546 587 ====
547 588 0
548 589 2
549 590 ====
550 591 1
551 592 3
552 593
553 594 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now