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