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