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