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