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