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