PageRenderTime 40ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/mercurial/revset.py

https://bitbucket.org/mirror/mercurial/
Python | 2856 lines | 2652 code | 53 blank | 151 comment | 120 complexity | a7c93e7212c923b73893474afbf61d1b MD5 | raw file
Possible License(s): GPL-2.0
  1. # revset.py - revision set queries for mercurial
  2. #
  3. # Copyright 2010 Matt Mackall <mpm@selenic.com>
  4. #
  5. # This software may be used and distributed according to the terms of the
  6. # GNU General Public License version 2 or any later version.
  7. import re
  8. import parser, util, error, discovery, hbisect, phases
  9. import node
  10. import heapq
  11. import match as matchmod
  12. import ancestor as ancestormod
  13. from i18n import _
  14. import encoding
  15. import obsolete as obsmod
  16. import pathutil
  17. import repoview
  18. def _revancestors(repo, revs, followfirst):
  19. """Like revlog.ancestors(), but supports followfirst."""
  20. cut = followfirst and 1 or None
  21. cl = repo.changelog
  22. def iterate():
  23. revqueue, revsnode = None, None
  24. h = []
  25. revs.descending()
  26. revqueue = util.deque(revs)
  27. if revqueue:
  28. revsnode = revqueue.popleft()
  29. heapq.heappush(h, -revsnode)
  30. seen = set([node.nullrev])
  31. while h:
  32. current = -heapq.heappop(h)
  33. if current not in seen:
  34. if revsnode and current == revsnode:
  35. if revqueue:
  36. revsnode = revqueue.popleft()
  37. heapq.heappush(h, -revsnode)
  38. seen.add(current)
  39. yield current
  40. for parent in cl.parentrevs(current)[:cut]:
  41. if parent != node.nullrev:
  42. heapq.heappush(h, -parent)
  43. return _descgeneratorset(iterate())
  44. def _revdescendants(repo, revs, followfirst):
  45. """Like revlog.descendants() but supports followfirst."""
  46. cut = followfirst and 1 or None
  47. def iterate():
  48. cl = repo.changelog
  49. first = min(revs)
  50. nullrev = node.nullrev
  51. if first == nullrev:
  52. # Are there nodes with a null first parent and a non-null
  53. # second one? Maybe. Do we care? Probably not.
  54. for i in cl:
  55. yield i
  56. else:
  57. seen = set(revs)
  58. for i in cl.revs(first + 1):
  59. for x in cl.parentrevs(i)[:cut]:
  60. if x != nullrev and x in seen:
  61. seen.add(i)
  62. yield i
  63. break
  64. return _ascgeneratorset(iterate())
  65. def _revsbetween(repo, roots, heads):
  66. """Return all paths between roots and heads, inclusive of both endpoint
  67. sets."""
  68. if not roots:
  69. return baseset([])
  70. parentrevs = repo.changelog.parentrevs
  71. visit = baseset(heads)
  72. reachable = set()
  73. seen = {}
  74. minroot = min(roots)
  75. roots = set(roots)
  76. # open-code the post-order traversal due to the tiny size of
  77. # sys.getrecursionlimit()
  78. while visit:
  79. rev = visit.pop()
  80. if rev in roots:
  81. reachable.add(rev)
  82. parents = parentrevs(rev)
  83. seen[rev] = parents
  84. for parent in parents:
  85. if parent >= minroot and parent not in seen:
  86. visit.append(parent)
  87. if not reachable:
  88. return baseset([])
  89. for rev in sorted(seen):
  90. for parent in seen[rev]:
  91. if parent in reachable:
  92. reachable.add(rev)
  93. return baseset(sorted(reachable))
  94. elements = {
  95. "(": (20, ("group", 1, ")"), ("func", 1, ")")),
  96. "~": (18, None, ("ancestor", 18)),
  97. "^": (18, None, ("parent", 18), ("parentpost", 18)),
  98. "-": (5, ("negate", 19), ("minus", 5)),
  99. "::": (17, ("dagrangepre", 17), ("dagrange", 17),
  100. ("dagrangepost", 17)),
  101. "..": (17, ("dagrangepre", 17), ("dagrange", 17),
  102. ("dagrangepost", 17)),
  103. ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
  104. "not": (10, ("not", 10)),
  105. "!": (10, ("not", 10)),
  106. "and": (5, None, ("and", 5)),
  107. "&": (5, None, ("and", 5)),
  108. "or": (4, None, ("or", 4)),
  109. "|": (4, None, ("or", 4)),
  110. "+": (4, None, ("or", 4)),
  111. ",": (2, None, ("list", 2)),
  112. ")": (0, None, None),
  113. "symbol": (0, ("symbol",), None),
  114. "string": (0, ("string",), None),
  115. "end": (0, None, None),
  116. }
  117. keywords = set(['and', 'or', 'not'])
  118. def tokenize(program, lookup=None):
  119. '''
  120. Parse a revset statement into a stream of tokens
  121. Check that @ is a valid unquoted token character (issue3686):
  122. >>> list(tokenize("@::"))
  123. [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
  124. '''
  125. pos, l = 0, len(program)
  126. while pos < l:
  127. c = program[pos]
  128. if c.isspace(): # skip inter-token whitespace
  129. pass
  130. elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
  131. yield ('::', None, pos)
  132. pos += 1 # skip ahead
  133. elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
  134. yield ('..', None, pos)
  135. pos += 1 # skip ahead
  136. elif c in "():,-|&+!~^": # handle simple operators
  137. yield (c, None, pos)
  138. elif (c in '"\'' or c == 'r' and
  139. program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
  140. if c == 'r':
  141. pos += 1
  142. c = program[pos]
  143. decode = lambda x: x
  144. else:
  145. decode = lambda x: x.decode('string-escape')
  146. pos += 1
  147. s = pos
  148. while pos < l: # find closing quote
  149. d = program[pos]
  150. if d == '\\': # skip over escaped characters
  151. pos += 2
  152. continue
  153. if d == c:
  154. yield ('string', decode(program[s:pos]), s)
  155. break
  156. pos += 1
  157. else:
  158. raise error.ParseError(_("unterminated string"), s)
  159. # gather up a symbol/keyword
  160. elif c.isalnum() or c in '._@' or ord(c) > 127:
  161. s = pos
  162. pos += 1
  163. while pos < l: # find end of symbol
  164. d = program[pos]
  165. if not (d.isalnum() or d in "-._/@" or ord(d) > 127):
  166. break
  167. if d == '.' and program[pos - 1] == '.': # special case for ..
  168. pos -= 1
  169. break
  170. pos += 1
  171. sym = program[s:pos]
  172. if sym in keywords: # operator keywords
  173. yield (sym, None, s)
  174. elif '-' in sym:
  175. # some jerk gave us foo-bar-baz, try to check if it's a symbol
  176. if lookup and lookup(sym):
  177. # looks like a real symbol
  178. yield ('symbol', sym, s)
  179. else:
  180. # looks like an expression
  181. parts = sym.split('-')
  182. for p in parts[:-1]:
  183. if p: # possible consecutive -
  184. yield ('symbol', p, s)
  185. s += len(p)
  186. yield ('-', None, pos)
  187. s += 1
  188. if parts[-1]: # possible trailing -
  189. yield ('symbol', parts[-1], s)
  190. else:
  191. yield ('symbol', sym, s)
  192. pos -= 1
  193. else:
  194. raise error.ParseError(_("syntax error"), pos)
  195. pos += 1
  196. yield ('end', None, pos)
  197. # helpers
  198. def getstring(x, err):
  199. if x and (x[0] == 'string' or x[0] == 'symbol'):
  200. return x[1]
  201. raise error.ParseError(err)
  202. def getlist(x):
  203. if not x:
  204. return []
  205. if x[0] == 'list':
  206. return getlist(x[1]) + [x[2]]
  207. return [x]
  208. def getargs(x, min, max, err):
  209. l = getlist(x)
  210. if len(l) < min or (max >= 0 and len(l) > max):
  211. raise error.ParseError(err)
  212. return l
  213. def getset(repo, subset, x):
  214. if not x:
  215. raise error.ParseError(_("missing argument"))
  216. s = methods[x[0]](repo, subset, *x[1:])
  217. if util.safehasattr(s, 'set'):
  218. return s
  219. return baseset(s)
  220. def _getrevsource(repo, r):
  221. extra = repo[r].extra()
  222. for label in ('source', 'transplant_source', 'rebase_source'):
  223. if label in extra:
  224. try:
  225. return repo[extra[label]].rev()
  226. except error.RepoLookupError:
  227. pass
  228. return None
  229. # operator methods
  230. def stringset(repo, subset, x):
  231. x = repo[x].rev()
  232. if x == -1 and len(subset) == len(repo):
  233. return baseset([-1])
  234. if len(subset) == len(repo) or x in subset:
  235. return baseset([x])
  236. return baseset([])
  237. def symbolset(repo, subset, x):
  238. if x in symbols:
  239. raise error.ParseError(_("can't use %s here") % x)
  240. return stringset(repo, subset, x)
  241. def rangeset(repo, subset, x, y):
  242. cl = baseset(repo.changelog)
  243. m = getset(repo, cl, x)
  244. n = getset(repo, cl, y)
  245. if not m or not n:
  246. return baseset([])
  247. m, n = m[0], n[-1]
  248. if m < n:
  249. r = spanset(repo, m, n + 1)
  250. else:
  251. r = spanset(repo, m, n - 1)
  252. return r & subset
  253. def dagrange(repo, subset, x, y):
  254. r = spanset(repo)
  255. xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
  256. s = subset.set()
  257. return xs.filter(s.__contains__)
  258. def andset(repo, subset, x, y):
  259. return getset(repo, getset(repo, subset, x), y)
  260. def orset(repo, subset, x, y):
  261. xl = getset(repo, subset, x)
  262. yl = getset(repo, subset - xl, y)
  263. return xl + yl
  264. def notset(repo, subset, x):
  265. return subset - getset(repo, subset, x)
  266. def listset(repo, subset, a, b):
  267. raise error.ParseError(_("can't use a list in this context"))
  268. def func(repo, subset, a, b):
  269. if a[0] == 'symbol' and a[1] in symbols:
  270. return symbols[a[1]](repo, subset, b)
  271. raise error.ParseError(_("not a function: %s") % a[1])
  272. # functions
  273. def adds(repo, subset, x):
  274. """``adds(pattern)``
  275. Changesets that add a file matching pattern.
  276. The pattern without explicit kind like ``glob:`` is expected to be
  277. relative to the current directory and match against a file or a
  278. directory.
  279. """
  280. # i18n: "adds" is a keyword
  281. pat = getstring(x, _("adds requires a pattern"))
  282. return checkstatus(repo, subset, pat, 1)
  283. def ancestor(repo, subset, x):
  284. """``ancestor(*changeset)``
  285. A greatest common ancestor of the changesets.
  286. Accepts 0 or more changesets.
  287. Will return empty list when passed no args.
  288. Greatest common ancestor of a single changeset is that changeset.
  289. """
  290. # i18n: "ancestor" is a keyword
  291. l = getlist(x)
  292. rl = spanset(repo)
  293. anc = None
  294. # (getset(repo, rl, i) for i in l) generates a list of lists
  295. for revs in (getset(repo, rl, i) for i in l):
  296. for r in revs:
  297. if anc is None:
  298. anc = repo[r]
  299. else:
  300. anc = anc.ancestor(repo[r])
  301. if anc is not None and anc.rev() in subset:
  302. return baseset([anc.rev()])
  303. return baseset([])
  304. def _ancestors(repo, subset, x, followfirst=False):
  305. args = getset(repo, spanset(repo), x)
  306. if not args:
  307. return baseset([])
  308. s = _revancestors(repo, args, followfirst)
  309. return subset.filter(s.__contains__)
  310. def ancestors(repo, subset, x):
  311. """``ancestors(set)``
  312. Changesets that are ancestors of a changeset in set.
  313. """
  314. return _ancestors(repo, subset, x)
  315. def _firstancestors(repo, subset, x):
  316. # ``_firstancestors(set)``
  317. # Like ``ancestors(set)`` but follows only the first parents.
  318. return _ancestors(repo, subset, x, followfirst=True)
  319. def ancestorspec(repo, subset, x, n):
  320. """``set~n``
  321. Changesets that are the Nth ancestor (first parents only) of a changeset
  322. in set.
  323. """
  324. try:
  325. n = int(n[1])
  326. except (TypeError, ValueError):
  327. raise error.ParseError(_("~ expects a number"))
  328. ps = set()
  329. cl = repo.changelog
  330. for r in getset(repo, baseset(cl), x):
  331. for i in range(n):
  332. r = cl.parentrevs(r)[0]
  333. ps.add(r)
  334. return subset.filter(ps.__contains__)
  335. def author(repo, subset, x):
  336. """``author(string)``
  337. Alias for ``user(string)``.
  338. """
  339. # i18n: "author" is a keyword
  340. n = encoding.lower(getstring(x, _("author requires a string")))
  341. kind, pattern, matcher = _substringmatcher(n)
  342. return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
  343. def only(repo, subset, x):
  344. """``only(set, [set])``
  345. Changesets that are ancestors of the first set that are not ancestors
  346. of any other head in the repo. If a second set is specified, the result
  347. is ancestors of the first set that are not ancestors of the second set
  348. (i.e. ::<set1> - ::<set2>).
  349. """
  350. cl = repo.changelog
  351. # i18n: "only" is a keyword
  352. args = getargs(x, 1, 2, _('only takes one or two arguments'))
  353. include = getset(repo, spanset(repo), args[0]).set()
  354. if len(args) == 1:
  355. descendants = set(_revdescendants(repo, include, False))
  356. exclude = [rev for rev in cl.headrevs()
  357. if not rev in descendants and not rev in include]
  358. else:
  359. exclude = getset(repo, spanset(repo), args[1])
  360. results = set(ancestormod.missingancestors(include, exclude, cl.parentrevs))
  361. return lazyset(subset, results.__contains__)
  362. def bisect(repo, subset, x):
  363. """``bisect(string)``
  364. Changesets marked in the specified bisect status:
  365. - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
  366. - ``goods``, ``bads`` : csets topologically good/bad
  367. - ``range`` : csets taking part in the bisection
  368. - ``pruned`` : csets that are goods, bads or skipped
  369. - ``untested`` : csets whose fate is yet unknown
  370. - ``ignored`` : csets ignored due to DAG topology
  371. - ``current`` : the cset currently being bisected
  372. """
  373. # i18n: "bisect" is a keyword
  374. status = getstring(x, _("bisect requires a string")).lower()
  375. state = set(hbisect.get(repo, status))
  376. return subset.filter(state.__contains__)
  377. # Backward-compatibility
  378. # - no help entry so that we do not advertise it any more
  379. def bisected(repo, subset, x):
  380. return bisect(repo, subset, x)
  381. def bookmark(repo, subset, x):
  382. """``bookmark([name])``
  383. The named bookmark or all bookmarks.
  384. If `name` starts with `re:`, the remainder of the name is treated as
  385. a regular expression. To match a bookmark that actually starts with `re:`,
  386. use the prefix `literal:`.
  387. """
  388. # i18n: "bookmark" is a keyword
  389. args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
  390. if args:
  391. bm = getstring(args[0],
  392. # i18n: "bookmark" is a keyword
  393. _('the argument to bookmark must be a string'))
  394. kind, pattern, matcher = _stringmatcher(bm)
  395. if kind == 'literal':
  396. bmrev = repo._bookmarks.get(bm, None)
  397. if not bmrev:
  398. raise util.Abort(_("bookmark '%s' does not exist") % bm)
  399. bmrev = repo[bmrev].rev()
  400. return subset.filter(lambda r: r == bmrev)
  401. else:
  402. matchrevs = set()
  403. for name, bmrev in repo._bookmarks.iteritems():
  404. if matcher(name):
  405. matchrevs.add(bmrev)
  406. if not matchrevs:
  407. raise util.Abort(_("no bookmarks exist that match '%s'")
  408. % pattern)
  409. bmrevs = set()
  410. for bmrev in matchrevs:
  411. bmrevs.add(repo[bmrev].rev())
  412. return subset & bmrevs
  413. bms = set([repo[r].rev()
  414. for r in repo._bookmarks.values()])
  415. return subset.filter(bms.__contains__)
  416. def branch(repo, subset, x):
  417. """``branch(string or set)``
  418. All changesets belonging to the given branch or the branches of the given
  419. changesets.
  420. If `string` starts with `re:`, the remainder of the name is treated as
  421. a regular expression. To match a branch that actually starts with `re:`,
  422. use the prefix `literal:`.
  423. """
  424. try:
  425. b = getstring(x, '')
  426. except error.ParseError:
  427. # not a string, but another revspec, e.g. tip()
  428. pass
  429. else:
  430. kind, pattern, matcher = _stringmatcher(b)
  431. if kind == 'literal':
  432. # note: falls through to the revspec case if no branch with
  433. # this name exists
  434. if pattern in repo.branchmap():
  435. return subset.filter(lambda r: matcher(repo[r].branch()))
  436. else:
  437. return subset.filter(lambda r: matcher(repo[r].branch()))
  438. s = getset(repo, spanset(repo), x)
  439. b = set()
  440. for r in s:
  441. b.add(repo[r].branch())
  442. s = s.set()
  443. return subset.filter(lambda r: r in s or repo[r].branch() in b)
  444. def bumped(repo, subset, x):
  445. """``bumped()``
  446. Mutable changesets marked as successors of public changesets.
  447. Only non-public and non-obsolete changesets can be `bumped`.
  448. """
  449. # i18n: "bumped" is a keyword
  450. getargs(x, 0, 0, _("bumped takes no arguments"))
  451. bumped = obsmod.getrevs(repo, 'bumped')
  452. return subset & bumped
  453. def bundle(repo, subset, x):
  454. """``bundle()``
  455. Changesets in the bundle.
  456. Bundle must be specified by the -R option."""
  457. try:
  458. bundlerevs = repo.changelog.bundlerevs
  459. except AttributeError:
  460. raise util.Abort(_("no bundle provided - specify with -R"))
  461. return subset & bundlerevs
  462. def checkstatus(repo, subset, pat, field):
  463. hasset = matchmod.patkind(pat) == 'set'
  464. def matches(x):
  465. m = None
  466. fname = None
  467. c = repo[x]
  468. if not m or hasset:
  469. m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
  470. if not m.anypats() and len(m.files()) == 1:
  471. fname = m.files()[0]
  472. if fname is not None:
  473. if fname not in c.files():
  474. return False
  475. else:
  476. for f in c.files():
  477. if m(f):
  478. break
  479. else:
  480. return False
  481. files = repo.status(c.p1().node(), c.node())[field]
  482. if fname is not None:
  483. if fname in files:
  484. return True
  485. else:
  486. for f in files:
  487. if m(f):
  488. return True
  489. return subset.filter(matches)
  490. def _children(repo, narrow, parentset):
  491. cs = set()
  492. if not parentset:
  493. return baseset(cs)
  494. pr = repo.changelog.parentrevs
  495. minrev = min(parentset)
  496. for r in narrow:
  497. if r <= minrev:
  498. continue
  499. for p in pr(r):
  500. if p in parentset:
  501. cs.add(r)
  502. return baseset(cs)
  503. def children(repo, subset, x):
  504. """``children(set)``
  505. Child changesets of changesets in set.
  506. """
  507. s = getset(repo, baseset(repo), x).set()
  508. cs = _children(repo, subset, s)
  509. return subset & cs
  510. def closed(repo, subset, x):
  511. """``closed()``
  512. Changeset is closed.
  513. """
  514. # i18n: "closed" is a keyword
  515. getargs(x, 0, 0, _("closed takes no arguments"))
  516. return subset.filter(lambda r: repo[r].closesbranch())
  517. def contains(repo, subset, x):
  518. """``contains(pattern)``
  519. The revision's manifest contains a file matching pattern (but might not
  520. modify it). See :hg:`help patterns` for information about file patterns.
  521. The pattern without explicit kind like ``glob:`` is expected to be
  522. relative to the current directory and match against a file exactly
  523. for efficiency.
  524. """
  525. # i18n: "contains" is a keyword
  526. pat = getstring(x, _("contains requires a pattern"))
  527. def matches(x):
  528. if not matchmod.patkind(pat):
  529. pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
  530. if pats in repo[x]:
  531. return True
  532. else:
  533. c = repo[x]
  534. m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
  535. for f in c.manifest():
  536. if m(f):
  537. return True
  538. return False
  539. return subset.filter(matches)
  540. def converted(repo, subset, x):
  541. """``converted([id])``
  542. Changesets converted from the given identifier in the old repository if
  543. present, or all converted changesets if no identifier is specified.
  544. """
  545. # There is exactly no chance of resolving the revision, so do a simple
  546. # string compare and hope for the best
  547. rev = None
  548. # i18n: "converted" is a keyword
  549. l = getargs(x, 0, 1, _('converted takes one or no arguments'))
  550. if l:
  551. # i18n: "converted" is a keyword
  552. rev = getstring(l[0], _('converted requires a revision'))
  553. def _matchvalue(r):
  554. source = repo[r].extra().get('convert_revision', None)
  555. return source is not None and (rev is None or source.startswith(rev))
  556. return subset.filter(lambda r: _matchvalue(r))
  557. def date(repo, subset, x):
  558. """``date(interval)``
  559. Changesets within the interval, see :hg:`help dates`.
  560. """
  561. # i18n: "date" is a keyword
  562. ds = getstring(x, _("date requires a string"))
  563. dm = util.matchdate(ds)
  564. return subset.filter(lambda x: dm(repo[x].date()[0]))
  565. def desc(repo, subset, x):
  566. """``desc(string)``
  567. Search commit message for string. The match is case-insensitive.
  568. """
  569. # i18n: "desc" is a keyword
  570. ds = encoding.lower(getstring(x, _("desc requires a string")))
  571. def matches(x):
  572. c = repo[x]
  573. return ds in encoding.lower(c.description())
  574. return subset.filter(matches)
  575. def _descendants(repo, subset, x, followfirst=False):
  576. args = getset(repo, spanset(repo), x)
  577. if not args:
  578. return baseset([])
  579. s = _revdescendants(repo, args, followfirst)
  580. # Both sets need to be ascending in order to lazily return the union
  581. # in the correct order.
  582. args.ascending()
  583. subsetset = subset.set()
  584. result = (orderedlazyset(s, subsetset.__contains__, ascending=True) +
  585. orderedlazyset(args, subsetset.__contains__, ascending=True))
  586. # Wrap result in a lazyset since it's an _addset, which doesn't implement
  587. # all the necessary functions to be consumed by callers.
  588. return orderedlazyset(result, lambda r: True, ascending=True)
  589. def descendants(repo, subset, x):
  590. """``descendants(set)``
  591. Changesets which are descendants of changesets in set.
  592. """
  593. return _descendants(repo, subset, x)
  594. def _firstdescendants(repo, subset, x):
  595. # ``_firstdescendants(set)``
  596. # Like ``descendants(set)`` but follows only the first parents.
  597. return _descendants(repo, subset, x, followfirst=True)
  598. def destination(repo, subset, x):
  599. """``destination([set])``
  600. Changesets that were created by a graft, transplant or rebase operation,
  601. with the given revisions specified as the source. Omitting the optional set
  602. is the same as passing all().
  603. """
  604. if x is not None:
  605. args = getset(repo, spanset(repo), x).set()
  606. else:
  607. args = getall(repo, spanset(repo), x).set()
  608. dests = set()
  609. # subset contains all of the possible destinations that can be returned, so
  610. # iterate over them and see if their source(s) were provided in the args.
  611. # Even if the immediate src of r is not in the args, src's source (or
  612. # further back) may be. Scanning back further than the immediate src allows
  613. # transitive transplants and rebases to yield the same results as transitive
  614. # grafts.
  615. for r in subset:
  616. src = _getrevsource(repo, r)
  617. lineage = None
  618. while src is not None:
  619. if lineage is None:
  620. lineage = list()
  621. lineage.append(r)
  622. # The visited lineage is a match if the current source is in the arg
  623. # set. Since every candidate dest is visited by way of iterating
  624. # subset, any dests further back in the lineage will be tested by a
  625. # different iteration over subset. Likewise, if the src was already
  626. # selected, the current lineage can be selected without going back
  627. # further.
  628. if src in args or src in dests:
  629. dests.update(lineage)
  630. break
  631. r = src
  632. src = _getrevsource(repo, r)
  633. return subset.filter(dests.__contains__)
  634. def divergent(repo, subset, x):
  635. """``divergent()``
  636. Final successors of changesets with an alternative set of final successors.
  637. """
  638. # i18n: "divergent" is a keyword
  639. getargs(x, 0, 0, _("divergent takes no arguments"))
  640. divergent = obsmod.getrevs(repo, 'divergent')
  641. return subset.filter(divergent.__contains__)
  642. def draft(repo, subset, x):
  643. """``draft()``
  644. Changeset in draft phase."""
  645. # i18n: "draft" is a keyword
  646. getargs(x, 0, 0, _("draft takes no arguments"))
  647. pc = repo._phasecache
  648. return subset.filter(lambda r: pc.phase(repo, r) == phases.draft)
  649. def extinct(repo, subset, x):
  650. """``extinct()``
  651. Obsolete changesets with obsolete descendants only.
  652. """
  653. # i18n: "extinct" is a keyword
  654. getargs(x, 0, 0, _("extinct takes no arguments"))
  655. extincts = obsmod.getrevs(repo, 'extinct')
  656. return subset & extincts
  657. def extra(repo, subset, x):
  658. """``extra(label, [value])``
  659. Changesets with the given label in the extra metadata, with the given
  660. optional value.
  661. If `value` starts with `re:`, the remainder of the value is treated as
  662. a regular expression. To match a value that actually starts with `re:`,
  663. use the prefix `literal:`.
  664. """
  665. # i18n: "extra" is a keyword
  666. l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
  667. # i18n: "extra" is a keyword
  668. label = getstring(l[0], _('first argument to extra must be a string'))
  669. value = None
  670. if len(l) > 1:
  671. # i18n: "extra" is a keyword
  672. value = getstring(l[1], _('second argument to extra must be a string'))
  673. kind, value, matcher = _stringmatcher(value)
  674. def _matchvalue(r):
  675. extra = repo[r].extra()
  676. return label in extra and (value is None or matcher(extra[label]))
  677. return subset.filter(lambda r: _matchvalue(r))
  678. def filelog(repo, subset, x):
  679. """``filelog(pattern)``
  680. Changesets connected to the specified filelog.
  681. For performance reasons, visits only revisions mentioned in the file-level
  682. filelog, rather than filtering through all changesets (much faster, but
  683. doesn't include deletes or duplicate changes). For a slower, more accurate
  684. result, use ``file()``.
  685. The pattern without explicit kind like ``glob:`` is expected to be
  686. relative to the current directory and match against a file exactly
  687. for efficiency.
  688. """
  689. # i18n: "filelog" is a keyword
  690. pat = getstring(x, _("filelog requires a pattern"))
  691. s = set()
  692. if not matchmod.patkind(pat):
  693. f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
  694. fl = repo.file(f)
  695. for fr in fl:
  696. s.add(fl.linkrev(fr))
  697. else:
  698. m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
  699. for f in repo[None]:
  700. if m(f):
  701. fl = repo.file(f)
  702. for fr in fl:
  703. s.add(fl.linkrev(fr))
  704. return subset.filter(s.__contains__)
  705. def first(repo, subset, x):
  706. """``first(set, [n])``
  707. An alias for limit().
  708. """
  709. return limit(repo, subset, x)
  710. def _follow(repo, subset, x, name, followfirst=False):
  711. l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
  712. c = repo['.']
  713. if l:
  714. x = getstring(l[0], _("%s expected a filename") % name)
  715. if x in c:
  716. cx = c[x]
  717. s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
  718. # include the revision responsible for the most recent version
  719. s.add(cx.linkrev())
  720. else:
  721. return baseset([])
  722. else:
  723. s = _revancestors(repo, baseset([c.rev()]), followfirst)
  724. return subset.filter(s.__contains__)
  725. def follow(repo, subset, x):
  726. """``follow([file])``
  727. An alias for ``::.`` (ancestors of the working copy's first parent).
  728. If a filename is specified, the history of the given file is followed,
  729. including copies.
  730. """
  731. return _follow(repo, subset, x, 'follow')
  732. def _followfirst(repo, subset, x):
  733. # ``followfirst([file])``
  734. # Like ``follow([file])`` but follows only the first parent of
  735. # every revision or file revision.
  736. return _follow(repo, subset, x, '_followfirst', followfirst=True)
  737. def getall(repo, subset, x):
  738. """``all()``
  739. All changesets, the same as ``0:tip``.
  740. """
  741. # i18n: "all" is a keyword
  742. getargs(x, 0, 0, _("all takes no arguments"))
  743. return subset
  744. def grep(repo, subset, x):
  745. """``grep(regex)``
  746. Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
  747. to ensure special escape characters are handled correctly. Unlike
  748. ``keyword(string)``, the match is case-sensitive.
  749. """
  750. try:
  751. # i18n: "grep" is a keyword
  752. gr = re.compile(getstring(x, _("grep requires a string")))
  753. except re.error, e:
  754. raise error.ParseError(_('invalid match pattern: %s') % e)
  755. def matches(x):
  756. c = repo[x]
  757. for e in c.files() + [c.user(), c.description()]:
  758. if gr.search(e):
  759. return True
  760. return False
  761. return subset.filter(matches)
  762. def _matchfiles(repo, subset, x):
  763. # _matchfiles takes a revset list of prefixed arguments:
  764. #
  765. # [p:foo, i:bar, x:baz]
  766. #
  767. # builds a match object from them and filters subset. Allowed
  768. # prefixes are 'p:' for regular patterns, 'i:' for include
  769. # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
  770. # a revision identifier, or the empty string to reference the
  771. # working directory, from which the match object is
  772. # initialized. Use 'd:' to set the default matching mode, default
  773. # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
  774. # i18n: "_matchfiles" is a keyword
  775. l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
  776. pats, inc, exc = [], [], []
  777. hasset = False
  778. rev, default = None, None
  779. for arg in l:
  780. # i18n: "_matchfiles" is a keyword
  781. s = getstring(arg, _("_matchfiles requires string arguments"))
  782. prefix, value = s[:2], s[2:]
  783. if prefix == 'p:':
  784. pats.append(value)
  785. elif prefix == 'i:':
  786. inc.append(value)
  787. elif prefix == 'x:':
  788. exc.append(value)
  789. elif prefix == 'r:':
  790. if rev is not None:
  791. # i18n: "_matchfiles" is a keyword
  792. raise error.ParseError(_('_matchfiles expected at most one '
  793. 'revision'))
  794. rev = value
  795. elif prefix == 'd:':
  796. if default is not None:
  797. # i18n: "_matchfiles" is a keyword
  798. raise error.ParseError(_('_matchfiles expected at most one '
  799. 'default mode'))
  800. default = value
  801. else:
  802. # i18n: "_matchfiles" is a keyword
  803. raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
  804. if not hasset and matchmod.patkind(value) == 'set':
  805. hasset = True
  806. if not default:
  807. default = 'glob'
  808. def matches(x):
  809. m = None
  810. c = repo[x]
  811. if not m or (hasset and rev is None):
  812. ctx = c
  813. if rev is not None:
  814. ctx = repo[rev or None]
  815. m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
  816. exclude=exc, ctx=ctx, default=default)
  817. for f in c.files():
  818. if m(f):
  819. return True
  820. return False
  821. return subset.filter(matches)
  822. def hasfile(repo, subset, x):
  823. """``file(pattern)``
  824. Changesets affecting files matched by pattern.
  825. For a faster but less accurate result, consider using ``filelog()``
  826. instead.
  827. This predicate uses ``glob:`` as the default kind of pattern.
  828. """
  829. # i18n: "file" is a keyword
  830. pat = getstring(x, _("file requires a pattern"))
  831. return _matchfiles(repo, subset, ('string', 'p:' + pat))
  832. def head(repo, subset, x):
  833. """``head()``
  834. Changeset is a named branch head.
  835. """
  836. # i18n: "head" is a keyword
  837. getargs(x, 0, 0, _("head takes no arguments"))
  838. hs = set()
  839. for b, ls in repo.branchmap().iteritems():
  840. hs.update(repo[h].rev() for h in ls)
  841. return baseset(hs).filter(subset.__contains__)
  842. def heads(repo, subset, x):
  843. """``heads(set)``
  844. Members of set with no children in set.
  845. """
  846. s = getset(repo, subset, x)
  847. ps = parents(repo, subset, x)
  848. return s - ps
  849. def hidden(repo, subset, x):
  850. """``hidden()``
  851. Hidden changesets.
  852. """
  853. # i18n: "hidden" is a keyword
  854. getargs(x, 0, 0, _("hidden takes no arguments"))
  855. hiddenrevs = repoview.filterrevs(repo, 'visible')
  856. return subset & hiddenrevs
  857. def keyword(repo, subset, x):
  858. """``keyword(string)``
  859. Search commit message, user name, and names of changed files for
  860. string. The match is case-insensitive.
  861. """
  862. # i18n: "keyword" is a keyword
  863. kw = encoding.lower(getstring(x, _("keyword requires a string")))
  864. def matches(r):
  865. c = repo[r]
  866. return util.any(kw in encoding.lower(t) for t in c.files() + [c.user(),
  867. c.description()])
  868. return subset.filter(matches)
  869. def limit(repo, subset, x):
  870. """``limit(set, [n])``
  871. First n members of set, defaulting to 1.
  872. """
  873. # i18n: "limit" is a keyword
  874. l = getargs(x, 1, 2, _("limit requires one or two arguments"))
  875. try:
  876. lim = 1
  877. if len(l) == 2:
  878. # i18n: "limit" is a keyword
  879. lim = int(getstring(l[1], _("limit requires a number")))
  880. except (TypeError, ValueError):
  881. # i18n: "limit" is a keyword
  882. raise error.ParseError(_("limit expects a number"))
  883. ss = subset.set()
  884. os = getset(repo, spanset(repo), l[0])
  885. bs = baseset([])
  886. it = iter(os)
  887. for x in xrange(lim):
  888. try:
  889. y = it.next()
  890. if y in ss:
  891. bs.append(y)
  892. except (StopIteration):
  893. break
  894. return bs
  895. def last(repo, subset, x):
  896. """``last(set, [n])``
  897. Last n members of set, defaulting to 1.
  898. """
  899. # i18n: "last" is a keyword
  900. l = getargs(x, 1, 2, _("last requires one or two arguments"))
  901. try:
  902. lim = 1
  903. if len(l) == 2:
  904. # i18n: "last" is a keyword
  905. lim = int(getstring(l[1], _("last requires a number")))
  906. except (TypeError, ValueError):
  907. # i18n: "last" is a keyword
  908. raise error.ParseError(_("last expects a number"))
  909. ss = subset.set()
  910. os = getset(repo, spanset(repo), l[0])
  911. os.reverse()
  912. bs = baseset([])
  913. it = iter(os)
  914. for x in xrange(lim):
  915. try:
  916. y = it.next()
  917. if y in ss:
  918. bs.append(y)
  919. except (StopIteration):
  920. break
  921. return bs
  922. def maxrev(repo, subset, x):
  923. """``max(set)``
  924. Changeset with highest revision number in set.
  925. """
  926. os = getset(repo, spanset(repo), x)
  927. if os:
  928. m = os.max()
  929. if m in subset:
  930. return baseset([m])
  931. return baseset([])
  932. def merge(repo, subset, x):
  933. """``merge()``
  934. Changeset is a merge changeset.
  935. """
  936. # i18n: "merge" is a keyword
  937. getargs(x, 0, 0, _("merge takes no arguments"))
  938. cl = repo.changelog
  939. return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
  940. def branchpoint(repo, subset, x):
  941. """``branchpoint()``
  942. Changesets with more than one child.
  943. """
  944. # i18n: "branchpoint" is a keyword
  945. getargs(x, 0, 0, _("branchpoint takes no arguments"))
  946. cl = repo.changelog
  947. if not subset:
  948. return baseset([])
  949. baserev = min(subset)
  950. parentscount = [0]*(len(repo) - baserev)
  951. for r in cl.revs(start=baserev + 1):
  952. for p in cl.parentrevs(r):
  953. if p >= baserev:
  954. parentscount[p - baserev] += 1
  955. return subset.filter(lambda r: parentscount[r - baserev] > 1)
  956. def minrev(repo, subset, x):
  957. """``min(set)``
  958. Changeset with lowest revision number in set.
  959. """
  960. os = getset(repo, spanset(repo), x)
  961. if os:
  962. m = os.min()
  963. if m in subset:
  964. return baseset([m])
  965. return baseset([])
  966. def _missingancestors(repo, subset, x):
  967. # i18n: "_missingancestors" is a keyword
  968. revs, bases = getargs(x, 2, 2,
  969. _("_missingancestors requires two arguments"))
  970. rs = baseset(repo)
  971. revs = getset(repo, rs, revs)
  972. bases = getset(repo, rs, bases)
  973. missing = set(repo.changelog.findmissingrevs(bases, revs))
  974. return baseset([r for r in subset if r in missing])
  975. def modifies(repo, subset, x):
  976. """``modifies(pattern)``
  977. Changesets modifying files matched by pattern.
  978. The pattern without explicit kind like ``glob:`` is expected to be
  979. relative to the current directory and match against a file or a
  980. directory.
  981. """
  982. # i18n: "modifies" is a keyword
  983. pat = getstring(x, _("modifies requires a pattern"))
  984. return checkstatus(repo, subset, pat, 0)
  985. def node_(repo, subset, x):
  986. """``id(string)``
  987. Revision non-ambiguously specified by the given hex string prefix.
  988. """
  989. # i18n: "id" is a keyword
  990. l = getargs(x, 1, 1, _("id requires one argument"))
  991. # i18n: "id" is a keyword
  992. n = getstring(l[0], _("id requires a string"))
  993. if len(n) == 40:
  994. rn = repo[n].rev()
  995. else:
  996. rn = None
  997. pm = repo.changelog._partialmatch(n)
  998. if pm is not None:
  999. rn = repo.changelog.rev(pm)
  1000. return subset.filter(lambda r: r == rn)
  1001. def obsolete(repo, subset, x):
  1002. """``obsolete()``
  1003. Mutable changeset with a newer version."""
  1004. # i18n: "obsolete" is a keyword
  1005. getargs(x, 0, 0, _("obsolete takes no arguments"))
  1006. obsoletes = obsmod.getrevs(repo, 'obsolete')
  1007. return subset & obsoletes
  1008. def origin(repo, subset, x):
  1009. """``origin([set])``
  1010. Changesets that were specified as a source for the grafts, transplants or
  1011. rebases that created the given revisions. Omitting the optional set is the
  1012. same as passing all(). If a changeset created by these operations is itself
  1013. specified as a source for one of these operations, only the source changeset
  1014. for the first operation is selected.
  1015. """
  1016. if x is not None:
  1017. args = getset(repo, spanset(repo), x).set()
  1018. else:
  1019. args = getall(repo, spanset(repo), x).set()
  1020. def _firstsrc(rev):
  1021. src = _getrevsource(repo, rev)
  1022. if src is None:
  1023. return None
  1024. while True:
  1025. prev = _getrevsource(repo, src)
  1026. if prev is None:
  1027. return src
  1028. src = prev
  1029. o = set([_firstsrc(r) for r in args])
  1030. return subset.filter(o.__contains__)
  1031. def outgoing(repo, subset, x):
  1032. """``outgoing([path])``
  1033. Changesets not found in the specified destination repository, or the
  1034. default push location.
  1035. """
  1036. import hg # avoid start-up nasties
  1037. # i18n: "outgoing" is a keyword
  1038. l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
  1039. # i18n: "outgoing" is a keyword
  1040. dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
  1041. dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
  1042. dest, branches = hg.parseurl(dest)
  1043. revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
  1044. if revs:
  1045. revs = [repo.lookup(rev) for rev in revs]
  1046. other = hg.peer(repo, {}, dest)
  1047. repo.ui.pushbuffer()
  1048. outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
  1049. repo.ui.popbuffer()
  1050. cl = repo.changelog
  1051. o = set([cl.rev(r) for r in outgoing.missing])
  1052. return subset.filter(o.__contains__)
  1053. def p1(repo, subset, x):
  1054. """``p1([set])``
  1055. First parent of changesets in set, or the working directory.
  1056. """
  1057. if x is None:
  1058. p = repo[x].p1().rev()
  1059. return subset.filter(lambda r: r == p)
  1060. ps = set()
  1061. cl = repo.changelog
  1062. for r in getset(repo, spanset(repo), x):
  1063. ps.add(cl.parentrevs(r)[0])
  1064. return subset & ps
  1065. def p2(repo, subset, x):
  1066. """``p2([set])``
  1067. Second parent of changesets in set, or the working directory.
  1068. """
  1069. if x is None:
  1070. ps = repo[x].parents()
  1071. try:
  1072. p = ps[1].rev()
  1073. return subset.filter(lambda r: r == p)
  1074. except IndexError:
  1075. return baseset([])
  1076. ps = set()
  1077. cl = repo.changelog
  1078. for r in getset(repo, spanset(repo), x):
  1079. ps.add(cl.parentrevs(r)[1])
  1080. return subset & ps
  1081. def parents(repo, subset, x):
  1082. """``parents([set])``
  1083. The set of all parents for all changesets in set, or the working directory.
  1084. """
  1085. if x is None:
  1086. ps = tuple(p.rev() for p in repo[x].parents())
  1087. return subset & ps
  1088. ps = set()
  1089. cl = repo.changelog
  1090. for r in getset(repo, spanset(repo), x):
  1091. ps.update(cl.parentrevs(r))
  1092. return subset & ps
  1093. def parentspec(repo, subset, x, n):
  1094. """``set^0``
  1095. The set.
  1096. ``set^1`` (or ``set^``), ``set^2``
  1097. First or second parent, respectively, of all changesets in set.
  1098. """
  1099. try:
  1100. n = int(n[1])
  1101. if n not in (0, 1, 2):
  1102. raise ValueError
  1103. except (TypeError, ValueError):
  1104. raise error.ParseError(_("^ expects a number 0, 1, or 2"))
  1105. ps = set()
  1106. cl = repo.changelog
  1107. for r in getset(repo, baseset(cl), x):
  1108. if n == 0:
  1109. ps.add(r)
  1110. elif n == 1:
  1111. ps.add(cl.parentrevs(r)[0])
  1112. elif n == 2:
  1113. parents = cl.parentrevs(r)
  1114. if len(parents) > 1:
  1115. ps.add(parents[1])
  1116. return subset & ps
  1117. def present(repo, subset, x):
  1118. """``present(set)``
  1119. An empty set, if any revision in set isn't found; otherwise,
  1120. all revisions in set.
  1121. If any of specified revisions is not present in the local repository,
  1122. the query is normally aborted. But this predicate allows the query
  1123. to continue even in such cases.
  1124. """
  1125. try:
  1126. return getset(repo, subset, x)
  1127. except error.RepoLookupError:
  1128. return baseset([])
  1129. def public(repo, subset, x):
  1130. """``public()``
  1131. Changeset in public phase."""
  1132. # i18n: "public" is a keyword
  1133. getargs(x, 0, 0, _("public takes no arguments"))
  1134. pc = repo._phasecache
  1135. return subset.filter(lambda r: pc.phase(repo, r) == phases.public)
  1136. def remote(repo, subset, x):
  1137. """``remote([id [,path]])``
  1138. Local revision that corresponds to the given identifier in a
  1139. remote repository, if present. Here, the '.' identifier is a
  1140. synonym for the current local branch.
  1141. """
  1142. import hg # avoid start-up nasties
  1143. # i18n: "remote" is a keyword
  1144. l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
  1145. q = '.'
  1146. if len(l) > 0:
  1147. # i18n: "remote" is a keyword
  1148. q = getstring(l[0], _("remote requires a string id"))
  1149. if q == '.':
  1150. q = repo['.'].branch()
  1151. dest = ''
  1152. if len(l) > 1:
  1153. # i18n: "remote" is a keyword
  1154. dest = getstring(l[1], _("remote requires a repository path"))
  1155. dest = repo.ui.expandpath(dest or 'default')
  1156. dest, branches = hg.parseurl(dest)
  1157. revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
  1158. if revs:
  1159. revs = [repo.lookup(rev) for rev in revs]
  1160. other = hg.peer(repo, {}, dest)
  1161. n = other.lookup(q)
  1162. if n in repo:
  1163. r = repo[n].rev()
  1164. if r in subset:
  1165. return baseset([r])
  1166. return baseset([])
  1167. def removes(repo, subset, x):
  1168. """``removes(pattern)``
  1169. Changesets which remove files matching pattern.
  1170. The pattern without explicit kind like ``glob:`` is expected to be
  1171. relative to the current directory and match against a file or a
  1172. directory.
  1173. """
  1174. # i18n: "removes" is a keyword
  1175. pat = getstring(x, _("removes requires a pattern"))
  1176. return checkstatus(repo, subset, pat, 2)
  1177. def rev(repo, subset, x):
  1178. """``rev(number)``
  1179. Revision with the given numeric identifier.
  1180. """
  1181. # i18n: "rev" is a keyword
  1182. l = getargs(x, 1, 1, _("rev requires one argument"))
  1183. try:
  1184. # i18n: "rev" is a keyword
  1185. l = int(getstring(l[0], _("rev requires a number")))
  1186. except (TypeError, ValueError):
  1187. # i18n: "rev" is a keyword
  1188. raise error.ParseError(_("rev expects a number"))
  1189. return subset.filter(lambda r: r == l)
  1190. def matching(repo, subset, x):
  1191. """``matching(revision [, field])``
  1192. Changesets in which a given set of fields match the set of fields in the
  1193. selected revision or set.
  1194. To match more than one field pass the list of fields to match separated
  1195. by spaces (e.g. ``author description``).
  1196. Valid fields are most regular revision fields and some special fields.
  1197. Regular revision fields are ``description``, ``author``, ``branch``,
  1198. ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
  1199. and ``diff``.
  1200. Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
  1201. contents of the revision. Two revisions matching their ``diff`` will
  1202. also match their ``files``.
  1203. Special fields are ``summary`` and ``metadata``:
  1204. ``summary`` matches the first line of the description.
  1205. ``metadata`` is equivalent to matching ``description user date``
  1206. (i.e. it matches the main metadata fields).
  1207. ``metadata`` is the default field which is used when no fields are
  1208. specified. You can match more than one field at a time.
  1209. """
  1210. # i18n: "matching" is a keyword
  1211. l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
  1212. revs = getset(repo, baseset(repo.changelog), l[0])
  1213. fieldlist = ['metadata']
  1214. if len(l) > 1:
  1215. fieldlist = getstring(l[1],
  1216. # i18n: "matching" is a keyword
  1217. _("matching requires a string "
  1218. "as its second argument")).split()
  1219. # Make sure that there are no repeated fields,
  1220. # expand the 'special' 'metadata' field type
  1221. # and check the 'files' whenever we check the 'diff'
  1222. fields = []
  1223. for field in fieldlist:
  1224. if field == 'metadata':
  1225. fields += ['user', 'description', 'date']
  1226. elif field == 'diff':
  1227. # a revision matching the diff must also match the files
  1228. # since matching the diff is very costly, make sure to
  1229. # also match the files first
  1230. fields += ['files', 'diff']
  1231. else:
  1232. if field == 'author':
  1233. field = 'user'
  1234. fields.append(field)
  1235. fields = set(fields)
  1236. if 'summary' in fields and 'description' in fields:
  1237. # If a revision matches its description it also matches its summary
  1238. fields.discard('summary')
  1239. # We may want to match more than one field
  1240. # Not all fields take the same amount of time to be matched
  1241. # Sort the selected fields in order of increasing matching cost
  1242. fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
  1243. 'files', 'description', 'substate', 'diff']
  1244. def fieldkeyfunc(f):
  1245. try:
  1246. return fieldorder.index(f)
  1247. except ValueError:
  1248. # assume an unknown field is very costly
  1249. return len(fieldorder)
  1250. fields = list(fields)
  1251. fields.sort(key=fieldkeyfunc)
  1252. # Each field will be matched with its own "getfield" function
  1253. # which will be added to the getfieldfuncs array of functions
  1254. getfieldfuncs = []
  1255. _funcs = {
  1256. 'user': lambda r: repo[r].user(),
  1257. 'branch': lambda r: repo[r].branch(),
  1258. 'date': lambda r: repo[r].date(),
  1259. 'description': lambda r: repo[r].description(),
  1260. 'files': lambda r: repo[r].files(),
  1261. 'parents': lambda r: repo[r].parents(),
  1262. 'phase': lambda r: repo[r].phase(),
  1263. 'substate': lambda r: repo[r].substate,
  1264. 'summary': lambda r: repo[r].description().splitlines()[0],
  1265. 'diff': lambda r: list(repo[r].diff(git=True),)
  1266. }
  1267. for info in fields:
  1268. getfield = _funcs.get(info, None)
  1269. if getfield is None:
  1270. raise error.ParseError(
  1271. # i18n: "matching" is a keyword
  1272. _("unexpected field name passed to matching: %s") % info)
  1273. getfieldfuncs.append(getfield)
  1274. # convert the getfield array of functions into a "getinfo" function
  1275. # which returns an array of field values (or a single value if there
  1276. # is only one field to match)
  1277. getinfo = lambda r: [f(r) for f in getfieldfuncs]
  1278. def matches(x):
  1279. for rev in revs:
  1280. target = getinfo(rev)
  1281. match = True
  1282. for n, f in enumerate(getfieldfuncs):
  1283. if target[n] != f(x):
  1284. match = False
  1285. if match:
  1286. return True
  1287. return False
  1288. return subset.filter(matches)
  1289. def reverse(repo, subset, x):
  1290. """``reverse(set)``
  1291. Reverse order of set.
  1292. """
  1293. l = getset(repo, subset, x)
  1294. l.reverse()
  1295. return l
  1296. def roots(repo, subset, x):
  1297. """``roots(set)``
  1298. Changesets in set with no parent changeset in set.
  1299. """
  1300. s = getset(repo, spanset(repo), x).set()
  1301. subset = baseset([r for r in s if r in subset.set()])
  1302. cs = _children(repo, subset, s)
  1303. return subset - cs
  1304. def secret(repo, subset, x):
  1305. """``secret()``
  1306. Changeset in secret phase."""
  1307. # i18n: "secret" is a keyword
  1308. getargs(x, 0, 0, _("secret takes no arguments"))
  1309. pc = repo._phasecache
  1310. return subset.filter(lambda x: pc.phase(repo, x) == phases.secret)
  1311. def sort(repo, subset, x):
  1312. """``sort(set[, [-]key...])``
  1313. Sort set by keys. The default sort order is ascending, specify a key
  1314. as ``-key`` to sort in descending order.
  1315. The keys can be:
  1316. - ``rev`` for the revision number,
  1317. - ``branch`` for the branch name,
  1318. - ``desc`` for the commit message (description),
  1319. - ``user`` for user name (``author`` can be used as an alias),
  1320. - ``date`` for the commit date
  1321. """
  1322. # i18n: "sort" is a keyword
  1323. l = getargs(x, 1, 2, _("sort requires one or two arguments"))
  1324. keys = "rev"
  1325. if len(l) == 2:
  1326. # i18n: "sort" is a keyword
  1327. keys = getstring(l[1], _("sort spec must be a string"))
  1328. s = l[0]
  1329. keys = keys.split()
  1330. l = []
  1331. def invert(s):
  1332. return "".join(chr(255 - ord(c)) for c in s)
  1333. revs = getset(repo, subset, s)
  1334. if keys == ["rev"]:
  1335. revs.sort()
  1336. return revs
  1337. elif keys == ["-rev"]:
  1338. revs.sort(reverse=True)
  1339. return revs
  1340. for r in revs:
  1341. c = repo[r]
  1342. e = []
  1343. for k in keys:
  1344. if k == 'rev':
  1345. e.append(r)
  1346. elif k == '-rev':
  1347. e.append(-r)
  1348. elif k == 'branch':
  1349. e.append(c.branch())
  1350. elif k == '-branch':
  1351. e.append(invert(c.branch()))
  1352. elif k == 'desc':
  1353. e.append(c.description())
  1354. elif k == '-desc':
  1355. e.append(invert(c.description()))
  1356. elif k in 'user author':
  1357. e.append(c.user())
  1358. elif k in '-user -author':
  1359. e.append(invert(c.user()))
  1360. elif k == 'date':
  1361. e.append(c.date()[0])
  1362. elif k == '-date':
  1363. e.append(-c.date()[0])
  1364. else:
  1365. raise error.ParseError(_("unknown sort key %r") % k)
  1366. e.append(r)
  1367. l.append(e)
  1368. l.sort()
  1369. return baseset([e[-1] for e in l])
  1370. def _stringmatcher(pattern):
  1371. """
  1372. accepts a string, possibly starting with 're:' or 'literal:' prefix.
  1373. returns the matcher name, pattern, and matcher function.
  1374. missing or unknown prefixes are treated as literal matches.
  1375. helper for tests:
  1376. >>> def test(pattern, *tests):
  1377. ... kind, pattern, matcher = _stringmatcher(pattern)
  1378. ... return (kind, pattern, [bool(matcher(t)) for t in tests])
  1379. exact matching (no prefix):
  1380. >>> test('abcdefg', 'abc', 'def', 'abcdefg')
  1381. ('literal', 'abcdefg', [False, False, True])
  1382. regex matching ('re:' prefix)
  1383. >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
  1384. ('re', 'a.+b', [False, False, True])
  1385. force exact matches ('literal:' prefix)
  1386. >>> test('literal:re:foobar', 'foobar', 're:foobar')
  1387. ('literal', 're:foobar', [False, True])
  1388. unknown prefixes are ignored and treated as literals
  1389. >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
  1390. ('literal', 'foo:bar', [False, False, True])
  1391. """
  1392. if pattern.startswith('re:'):
  1393. pattern = pattern[3:]
  1394. try:
  1395. regex = re.compile(pattern)
  1396. except re.error, e:
  1397. raise error.ParseError(_('invalid regular expression: %s')
  1398. % e)
  1399. return 're', pattern, regex.search
  1400. elif pattern.startswith('literal:'):
  1401. pattern = pattern[8:]
  1402. return 'literal', pattern, pattern.__eq__
  1403. def _substringmatcher(pattern):
  1404. kind, pattern, matcher = _stringmatcher(pattern)
  1405. if kind == 'literal':
  1406. matcher = lambda s: pattern in s
  1407. return kind, pattern, matcher
  1408. def tag(repo, subset, x):
  1409. """``tag([name])``
  1410. The specified tag by name, or all tagged revisions if no name is given.
  1411. If `name` starts with `re:`, the remainder of the name is treated as
  1412. a regular expression. To match a tag that actually starts with `re:`,
  1413. use the prefix `literal:`.
  1414. """
  1415. # i18n: "tag" is a keyword
  1416. args = getargs(x, 0, 1, _("tag takes one or no arguments"))
  1417. cl = repo.changelog
  1418. if args:
  1419. pattern = getstring(args[0],
  1420. # i18n: "tag" is a keyword
  1421. _('the argument to tag must be a string'))
  1422. kind, pattern, matcher = _stringmatcher(pattern)
  1423. if kind == 'literal':
  1424. # avoid resolving all tags
  1425. tn = repo._tagscache.tags.get(pattern, None)
  1426. if tn is None:
  1427. raise util.Abort(_("tag '%s' does not exist") % pattern)
  1428. s = set([repo[tn].rev()])
  1429. else:
  1430. s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
  1431. else:
  1432. s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
  1433. return subset & s
  1434. def tagged(repo, subset, x):
  1435. return tag(repo, subset, x)
  1436. def unstable(repo, subset, x):
  1437. """``unstable()``
  1438. Non-obsolete changesets with obsolete ancestors.
  1439. """
  1440. # i18n: "unstable" is a keyword
  1441. getargs(x, 0, 0, _("unstable takes no arguments"))
  1442. unstables = obsmod.getrevs(repo, 'unstable')
  1443. return subset & unstables
  1444. def user(repo, subset, x):
  1445. """``user(string)``
  1446. User name contains string. The match is case-insensitive.
  1447. If `string` starts with `re:`, the remainder of the string is treated as
  1448. a regular expression. To match a user that actually contains `re:`, use
  1449. the prefix `literal:`.
  1450. """
  1451. return author(repo, subset, x)
  1452. # for internal use
  1453. def _list(repo, subset, x):
  1454. s = getstring(x, "internal error")
  1455. if not s:
  1456. return baseset([])
  1457. ls = [repo[r].rev() for r in s.split('\0')]
  1458. s = subset.set()
  1459. return baseset([r for r in ls if r in s])
  1460. # for internal use
  1461. def _intlist(repo, subset, x):
  1462. s = getstring(x, "internal error")
  1463. if not s:
  1464. return baseset([])
  1465. ls = [int(r) for r in s.split('\0')]
  1466. s = subset.set()
  1467. return baseset([r for r in ls if r in s])
  1468. # for internal use
  1469. def _hexlist(repo, subset, x):
  1470. s = getstring(x, "internal error")
  1471. if not s:
  1472. return baseset([])
  1473. cl = repo.changelog
  1474. ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
  1475. s = subset.set()
  1476. return baseset([r for r in ls if r in s])
  1477. symbols = {
  1478. "adds": adds,
  1479. "all": getall,
  1480. "ancestor": ancestor,
  1481. "ancestors": ancestors,
  1482. "_firstancestors": _firstancestors,
  1483. "author": author,
  1484. "only": only,
  1485. "bisect": bisect,
  1486. "bisected": bisected,
  1487. "bookmark": bookmark,
  1488. "branch": branch,
  1489. "branchpoint": branchpoint,
  1490. "bumped": bumped,
  1491. "bundle": bundle,
  1492. "children": children,
  1493. "closed": closed,
  1494. "contains": contains,
  1495. "converted": converted,
  1496. "date": date,
  1497. "desc": desc,
  1498. "descendants": descendants,
  1499. "_firstdescendants": _firstdescendants,
  1500. "destination": destination,
  1501. "divergent": divergent,
  1502. "draft": draft,
  1503. "extinct": extinct,
  1504. "extra": extra,
  1505. "file": hasfile,
  1506. "filelog": filelog,
  1507. "first": first,
  1508. "follow": follow,
  1509. "_followfirst": _followfirst,
  1510. "grep": grep,
  1511. "head": head,
  1512. "heads": heads,
  1513. "hidden": hidden,
  1514. "id": node_,
  1515. "keyword": keyword,
  1516. "last": last,
  1517. "limit": limit,
  1518. "_matchfiles": _matchfiles,
  1519. "max": maxrev,
  1520. "merge": merge,
  1521. "min": minrev,
  1522. "_missingancestors": _missingancestors,
  1523. "modifies": modifies,
  1524. "obsolete": obsolete,
  1525. "origin": origin,
  1526. "outgoing": outgoing,
  1527. "p1": p1,
  1528. "p2": p2,
  1529. "parents": parents,
  1530. "present": present,
  1531. "public": public,
  1532. "remote": remote,
  1533. "removes": removes,
  1534. "rev": rev,
  1535. "reverse": reverse,
  1536. "roots": roots,
  1537. "sort": sort,
  1538. "secret": secret,
  1539. "matching": matching,
  1540. "tag": tag,
  1541. "tagged": tagged,
  1542. "user": user,
  1543. "unstable": unstable,
  1544. "_list": _list,
  1545. "_intlist": _intlist,
  1546. "_hexlist": _hexlist,
  1547. }
  1548. # symbols which can't be used for a DoS attack for any given input
  1549. # (e.g. those which accept regexes as plain strings shouldn't be included)
  1550. # functions that just return a lot of changesets (like all) don't count here
  1551. safesymbols = set([
  1552. "adds",
  1553. "all",
  1554. "ancestor",
  1555. "ancestors",
  1556. "_firstancestors",
  1557. "author",
  1558. "bisect",
  1559. "bisected",
  1560. "bookmark",
  1561. "branch",
  1562. "branchpoint",
  1563. "bumped",
  1564. "bundle",
  1565. "children",
  1566. "closed",
  1567. "converted",
  1568. "date",
  1569. "desc",
  1570. "descendants",
  1571. "_firstdescendants",
  1572. "destination",
  1573. "divergent",
  1574. "draft",
  1575. "extinct",
  1576. "extra",
  1577. "file",
  1578. "filelog",
  1579. "first",
  1580. "follow",
  1581. "_followfirst",
  1582. "head",
  1583. "heads",
  1584. "hidden",
  1585. "id",
  1586. "keyword",
  1587. "last",
  1588. "limit",
  1589. "_matchfiles",
  1590. "max",
  1591. "merge",
  1592. "min",
  1593. "_missingancestors",
  1594. "modifies",
  1595. "obsolete",
  1596. "origin",
  1597. "outgoing",
  1598. "p1",
  1599. "p2",
  1600. "parents",
  1601. "present",
  1602. "public",
  1603. "remote",
  1604. "removes",
  1605. "rev",
  1606. "reverse",
  1607. "roots",
  1608. "sort",
  1609. "secret",
  1610. "matching",
  1611. "tag",
  1612. "tagged",
  1613. "user",
  1614. "unstable",
  1615. "_list",
  1616. "_intlist",
  1617. "_hexlist",
  1618. ])
  1619. methods = {
  1620. "range": rangeset,
  1621. "dagrange": dagrange,
  1622. "string": stringset,
  1623. "symbol": symbolset,
  1624. "and": andset,
  1625. "or": orset,
  1626. "not": notset,
  1627. "list": listset,
  1628. "func": func,
  1629. "ancestor": ancestorspec,
  1630. "parent": parentspec,
  1631. "parentpost": p1,
  1632. }
  1633. def optimize(x, small):
  1634. if x is None:
  1635. return 0, x
  1636. smallbonus = 1
  1637. if small:
  1638. smallbonus = .5
  1639. op = x[0]
  1640. if op == 'minus':
  1641. return optimize(('and', x[1], ('not', x[2])), small)
  1642. elif op == 'dagrangepre':
  1643. return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
  1644. elif op == 'dagrangepost':
  1645. return optimize(('func', ('symbol', 'descendants'), x[1]), small)
  1646. elif op == 'rangepre':
  1647. return optimize(('range', ('string', '0'), x[1]), small)
  1648. elif op == 'rangepost':
  1649. return optimize(('range', x[1], ('string', 'tip')), small)
  1650. elif op == 'negate':
  1651. return optimize(('string',
  1652. '-' + getstring(x[1], _("can't negate that"))), small)
  1653. elif op in 'string symbol negate':
  1654. return smallbonus, x # single revisions are small
  1655. elif op == 'and':
  1656. wa, ta = optimize(x[1], True)
  1657. wb, tb = optimize(x[2], True)
  1658. # (::x and not ::y)/(not ::y and ::x) have a fast path
  1659. def ismissingancestors(revs, bases):
  1660. return (
  1661. revs[0] == 'func'
  1662. and getstring(revs[1], _('not a symbol')) == 'ancestors'
  1663. and bases[0] == 'not'
  1664. and bases[1][0] == 'func'
  1665. and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
  1666. w = min(wa, wb)
  1667. if ismissingancestors(ta, tb):
  1668. return w, ('func', ('symbol', '_missingancestors'),
  1669. ('list', ta[2], tb[1][2]))
  1670. if ismissingancestors(tb, ta):
  1671. return w, ('func', ('symbol', '_missingancestors'),
  1672. ('list', tb[2], ta[1][2]))
  1673. if wa > wb:
  1674. return w, (op, tb, ta)
  1675. return w, (op, ta, tb)
  1676. elif op == 'or':
  1677. wa, ta = optimize(x[1], False)
  1678. wb, tb = optimize(x[2], False)
  1679. if wb < wa:
  1680. wb, wa = wa, wb
  1681. return max(wa, wb), (op, ta, tb)
  1682. elif op == 'not':
  1683. o = optimize(x[1], not small)
  1684. return o[0], (op, o[1])
  1685. elif op == 'parentpost':
  1686. o = optimize(x[1], small)
  1687. return o[0], (op, o[1])
  1688. elif op == 'group':
  1689. return optimize(x[1], small)
  1690. elif op in 'dagrange range list parent ancestorspec':
  1691. if op == 'parent':
  1692. # x^:y means (x^) : y, not x ^ (:y)
  1693. post = ('parentpost', x[1])
  1694. if x[2][0] == 'dagrangepre':
  1695. return optimize(('dagrange', post, x[2][1]), small)
  1696. elif x[2][0] == 'rangepre':
  1697. return optimize(('range', post, x[2][1]), small)
  1698. wa, ta = optimize(x[1], small)
  1699. wb, tb = optimize(x[2], small)
  1700. return wa + wb, (op, ta, tb)
  1701. elif op == 'func':
  1702. f = getstring(x[1], _("not a symbol"))
  1703. wa, ta = optimize(x[2], small)
  1704. if f in ("author branch closed date desc file grep keyword "
  1705. "outgoing user"):
  1706. w = 10 # slow
  1707. elif f in "modifies adds removes":
  1708. w = 30 # slower
  1709. elif f == "contains":
  1710. w = 100 # very slow
  1711. elif f == "ancestor":
  1712. w = 1 * smallbonus
  1713. elif f in "reverse limit first":
  1714. w = 0
  1715. elif f in "sort":
  1716. w = 10 # assume most sorts look at changelog
  1717. else:
  1718. w = 1
  1719. return w + wa, (op, x[1], ta)
  1720. return 1, x
  1721. _aliasarg = ('func', ('symbol', '_aliasarg'))
  1722. def _getaliasarg(tree):
  1723. """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
  1724. return X, None otherwise.
  1725. """
  1726. if (len(tree) == 3 and tree[:2] == _aliasarg
  1727. and tree[2][0] == 'string'):
  1728. return tree[2][1]
  1729. return None
  1730. def _checkaliasarg(tree, known=None):
  1731. """Check tree contains no _aliasarg construct or only ones which
  1732. value is in known. Used to avoid alias placeholders injection.
  1733. """
  1734. if isinstance(tree, tuple):
  1735. arg = _getaliasarg(tree)
  1736. if arg is not None and (not known or arg not in known):
  1737. raise error.ParseError(_("not a function: %s") % '_aliasarg')
  1738. for t in tree:
  1739. _checkaliasarg(t, known)
  1740. class revsetalias(object):
  1741. funcre = re.compile('^([^(]+)\(([^)]+)\)$')
  1742. args = None
  1743. def __init__(self, name, value):
  1744. '''Aliases like:
  1745. h = heads(default)
  1746. b($1) = ancestors($1) - ancestors(default)
  1747. '''
  1748. m = self.funcre.search(name)
  1749. if m:
  1750. self.name = m.group(1)
  1751. self.tree = ('func', ('symbol', m.group(1)))
  1752. self.args = [x.strip() for x in m.group(2).split(',')]
  1753. for arg in self.args:
  1754. # _aliasarg() is an unknown symbol only used separate
  1755. # alias argument placeholders from regular strings.
  1756. value = value.replace(arg, '_aliasarg(%r)' % (arg,))
  1757. else:
  1758. self.name = name
  1759. self.tree = ('symbol', name)
  1760. self.replacement, pos = parse(value)
  1761. if pos != len(value):
  1762. raise error.ParseError(_('invalid token'), pos)
  1763. # Check for placeholder injection
  1764. _checkaliasarg(self.replacement, self.args)
  1765. def _getalias(aliases, tree):
  1766. """If tree looks like an unexpanded alias, return it. Return None
  1767. otherwise.
  1768. """
  1769. if isinstance(tree, tuple) and tree:
  1770. if tree[0] == 'symbol' and len(tree) == 2:
  1771. name = tree[1]
  1772. alias = aliases.get(name)
  1773. if alias and alias.args is None and alias.tree == tree:
  1774. return alias
  1775. if tree[0] == 'func' and len(tree) > 1:
  1776. if tree[1][0] == 'symbol' and len(tree[1]) == 2:
  1777. name = tree[1][1]
  1778. alias = aliases.get(name)
  1779. if alias and alias.args is not None and alias.tree == tree[:2]:
  1780. return alias
  1781. return None
  1782. def _expandargs(tree, args):
  1783. """Replace _aliasarg instances with the substitution value of the
  1784. same name in args, recursively.
  1785. """
  1786. if not tree or not isinstance(tree, tuple):
  1787. return tree
  1788. arg = _getaliasarg(tree)
  1789. if arg is not None:
  1790. return args[arg]
  1791. return tuple(_expandargs(t, args) for t in tree)
  1792. def _expandaliases(aliases, tree, expanding, cache):
  1793. """Expand aliases in tree, recursively.
  1794. 'aliases' is a dictionary mapping user defined aliases to
  1795. revsetalias objects.
  1796. """
  1797. if not isinstance(tree, tuple):
  1798. # Do not expand raw strings
  1799. return tree
  1800. alias = _getalias(aliases, tree)
  1801. if alias is not None:
  1802. if alias in expanding:
  1803. raise error.ParseError(_('infinite expansion of revset alias "%s" '
  1804. 'detected') % alias.name)
  1805. expanding.append(alias)
  1806. if alias.name not in cache:
  1807. cache[alias.name] = _expandaliases(aliases, alias.replacement,
  1808. expanding, cache)
  1809. result = cache[alias.name]
  1810. expanding.pop()
  1811. if alias.args is not None:
  1812. l = getlist(tree[2])
  1813. if len(l) != len(alias.args):
  1814. raise error.ParseError(
  1815. _('invalid number of arguments: %s') % len(l))
  1816. l = [_expandaliases(aliases, a, [], cache) for a in l]
  1817. result = _expandargs(result, dict(zip(alias.args, l)))
  1818. else:
  1819. result = tuple(_expandaliases(aliases, t, expanding, cache)
  1820. for t in tree)
  1821. return result
  1822. def findaliases(ui, tree):
  1823. _checkaliasarg(tree)
  1824. aliases = {}
  1825. for k, v in ui.configitems('revsetalias'):
  1826. alias = revsetalias(k, v)
  1827. aliases[alias.name] = alias
  1828. return _expandaliases(aliases, tree, [], {})
  1829. def parse(spec, lookup=None):
  1830. p = parser.parser(tokenize, elements)
  1831. return p.parse(spec, lookup=lookup)
  1832. def match(ui, spec, repo=None):
  1833. if not spec:
  1834. raise error.ParseError(_("empty query"))
  1835. lookup = None
  1836. if repo:
  1837. lookup = repo.__contains__
  1838. tree, pos = parse(spec, lookup)
  1839. if (pos != len(spec)):
  1840. raise error.ParseError(_("invalid token"), pos)
  1841. if ui:
  1842. tree = findaliases(ui, tree)
  1843. weight, tree = optimize(tree, True)
  1844. def mfunc(repo, subset):
  1845. if util.safehasattr(subset, 'set'):
  1846. return getset(repo, subset, tree)
  1847. return getset(repo, baseset(subset), tree)
  1848. return mfunc
  1849. def formatspec(expr, *args):
  1850. '''
  1851. This is a convenience function for using revsets internally, and
  1852. escapes arguments appropriately. Aliases are intentionally ignored
  1853. so that intended expression behavior isn't accidentally subverted.
  1854. Supported arguments:
  1855. %r = revset expression, parenthesized
  1856. %d = int(arg), no quoting
  1857. %s = string(arg), escaped and single-quoted
  1858. %b = arg.branch(), escaped and single-quoted
  1859. %n = hex(arg), single-quoted
  1860. %% = a literal '%'
  1861. Prefixing the type with 'l' specifies a parenthesized list of that type.
  1862. >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
  1863. '(10 or 11):: and ((this()) or (that()))'
  1864. >>> formatspec('%d:: and not %d::', 10, 20)
  1865. '10:: and not 20::'
  1866. >>> formatspec('%ld or %ld', [], [1])
  1867. "_list('') or 1"
  1868. >>> formatspec('keyword(%s)', 'foo\\xe9')
  1869. "keyword('foo\\\\xe9')"
  1870. >>> b = lambda: 'default'
  1871. >>> b.branch = b
  1872. >>> formatspec('branch(%b)', b)
  1873. "branch('default')"
  1874. >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
  1875. "root(_list('a\\x00b\\x00c\\x00d'))"
  1876. '''
  1877. def quote(s):
  1878. return repr(str(s))
  1879. def argtype(c, arg):
  1880. if c == 'd':
  1881. return str(int(arg))
  1882. elif c == 's':
  1883. return quote(arg)
  1884. elif c == 'r':
  1885. parse(arg) # make sure syntax errors are confined
  1886. return '(%s)' % arg
  1887. elif c == 'n':
  1888. return quote(node.hex(arg))
  1889. elif c == 'b':
  1890. return quote(arg.branch())
  1891. def listexp(s, t):
  1892. l = len(s)
  1893. if l == 0:
  1894. return "_list('')"
  1895. elif l == 1:
  1896. return argtype(t, s[0])
  1897. elif t == 'd':
  1898. return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
  1899. elif t == 's':
  1900. return "_list('%s')" % "\0".join(s)
  1901. elif t == 'n':
  1902. return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
  1903. elif t == 'b':
  1904. return "_list('%s')" % "\0".join(a.branch() for a in s)
  1905. m = l // 2
  1906. return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
  1907. ret = ''
  1908. pos = 0
  1909. arg = 0
  1910. while pos < len(expr):
  1911. c = expr[pos]
  1912. if c == '%':
  1913. pos += 1
  1914. d = expr[pos]
  1915. if d == '%':
  1916. ret += d
  1917. elif d in 'dsnbr':
  1918. ret += argtype(d, args[arg])
  1919. arg += 1
  1920. elif d == 'l':
  1921. # a list of some type
  1922. pos += 1
  1923. d = expr[pos]
  1924. ret += listexp(list(args[arg]), d)
  1925. arg += 1
  1926. else:
  1927. raise util.Abort('unexpected revspec format character %s' % d)
  1928. else:
  1929. ret += c
  1930. pos += 1
  1931. return ret
  1932. def prettyformat(tree):
  1933. def _prettyformat(tree, level, lines):
  1934. if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
  1935. lines.append((level, str(tree)))
  1936. else:
  1937. lines.append((level, '(%s' % tree[0]))
  1938. for s in tree[1:]:
  1939. _prettyformat(s, level + 1, lines)
  1940. lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
  1941. lines = []
  1942. _prettyformat(tree, 0, lines)
  1943. output = '\n'.join((' '*l + s) for l, s in lines)
  1944. return output
  1945. def depth(tree):
  1946. if isinstance(tree, tuple):
  1947. return max(map(depth, tree)) + 1
  1948. else:
  1949. return 0
  1950. def funcsused(tree):
  1951. if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
  1952. return set()
  1953. else:
  1954. funcs = set()
  1955. for s in tree[1:]:
  1956. funcs |= funcsused(s)
  1957. if tree[0] == 'func':
  1958. funcs.add(tree[1][1])
  1959. return funcs
  1960. class baseset(list):
  1961. """Basic data structure that represents a revset and contains the basic
  1962. operation that it should be able to perform.
  1963. Every method in this class should be implemented by any smartset class.
  1964. """
  1965. def __init__(self, data=()):
  1966. super(baseset, self).__init__(data)
  1967. self._set = None
  1968. def ascending(self):
  1969. """Sorts the set in ascending order (in place).
  1970. This is part of the mandatory API for smartset."""
  1971. self.sort()
  1972. def descending(self):
  1973. """Sorts the set in descending order (in place).
  1974. This is part of the mandatory API for smartset."""
  1975. self.sort(reverse=True)
  1976. def min(self):
  1977. return min(self)
  1978. def max(self):
  1979. return max(self)
  1980. def set(self):
  1981. """Returns a set or a smartset containing all the elements.
  1982. The returned structure should be the fastest option for membership
  1983. testing.
  1984. This is part of the mandatory API for smartset."""
  1985. if not self._set:
  1986. self._set = set(self)
  1987. return self._set
  1988. def __sub__(self, other):
  1989. """Returns a new object with the substraction of the two collections.
  1990. This is part of the mandatory API for smartset."""
  1991. return self.filter(lambda x: x not in other)
  1992. def __and__(self, other):
  1993. """Returns a new object with the intersection of the two collections.
  1994. This is part of the mandatory API for smartset."""
  1995. if isinstance(other, baseset):
  1996. other = other.set()
  1997. return baseset([y for y in self if y in other])
  1998. def __add__(self, other):
  1999. """Returns a new object with the union of the two collections.
  2000. This is part of the mandatory API for smartset."""
  2001. s = self.set()
  2002. l = [r for r in other if r not in s]
  2003. return baseset(list(self) + l)
  2004. def isascending(self):
  2005. """Returns True if the collection is ascending order, False if not.
  2006. This is part of the mandatory API for smartset."""
  2007. return False
  2008. def isdescending(self):
  2009. """Returns True if the collection is descending order, False if not.
  2010. This is part of the mandatory API for smartset."""
  2011. return False
  2012. def filter(self, condition):
  2013. """Returns this smartset filtered by condition as a new smartset.
  2014. `condition` is a callable which takes a revision number and returns a
  2015. boolean.
  2016. This is part of the mandatory API for smartset."""
  2017. return lazyset(self, condition)
  2018. class _orderedsetmixin(object):
  2019. """Mixin class with utility methods for smartsets
  2020. This should be extended by smartsets which have the isascending(),
  2021. isdescending() and reverse() methods"""
  2022. def _first(self):
  2023. """return the first revision in the set"""
  2024. for r in self:
  2025. return r
  2026. raise ValueError('arg is an empty sequence')
  2027. def _last(self):
  2028. """return the last revision in the set"""
  2029. self.reverse()
  2030. m = self._first()
  2031. self.reverse()
  2032. return m
  2033. def min(self):
  2034. """return the smallest element in the set"""
  2035. if self.isascending():
  2036. return self._first()
  2037. return self._last()
  2038. def max(self):
  2039. """return the largest element in the set"""
  2040. if self.isascending():
  2041. return self._last()
  2042. return self._first()
  2043. class lazyset(object):
  2044. """Duck type for baseset class which iterates lazily over the revisions in
  2045. the subset and contains a function which tests for membership in the
  2046. revset
  2047. """
  2048. def __init__(self, subset, condition=lambda x: True):
  2049. """
  2050. condition: a function that decide whether a revision in the subset
  2051. belongs to the revset or not.
  2052. """
  2053. self._subset = subset
  2054. self._condition = condition
  2055. self._cache = {}
  2056. def ascending(self):
  2057. self._subset.sort()
  2058. def descending(self):
  2059. self._subset.sort(reverse=True)
  2060. def min(self):
  2061. return min(self)
  2062. def max(self):
  2063. return max(self)
  2064. def __contains__(self, x):
  2065. c = self._cache
  2066. if x not in c:
  2067. c[x] = x in self._subset and self._condition(x)
  2068. return c[x]
  2069. def __iter__(self):
  2070. cond = self._condition
  2071. for x in self._subset:
  2072. if cond(x):
  2073. yield x
  2074. def __and__(self, x):
  2075. return lazyset(self, x.__contains__)
  2076. def __sub__(self, x):
  2077. return lazyset(self, lambda r: r not in x)
  2078. def __add__(self, x):
  2079. return _addset(self, x)
  2080. def __nonzero__(self):
  2081. for r in self:
  2082. return True
  2083. return False
  2084. def __len__(self):
  2085. # Basic implementation to be changed in future patches.
  2086. l = baseset([r for r in self])
  2087. return len(l)
  2088. def __getitem__(self, x):
  2089. # Basic implementation to be changed in future patches.
  2090. l = baseset([r for r in self])
  2091. return l[x]
  2092. def sort(self, reverse=False):
  2093. if not util.safehasattr(self._subset, 'sort'):
  2094. self._subset = baseset(self._subset)
  2095. self._subset.sort(reverse=reverse)
  2096. def reverse(self):
  2097. self._subset.reverse()
  2098. def set(self):
  2099. return set([r for r in self])
  2100. def isascending(self):
  2101. return False
  2102. def isdescending(self):
  2103. return False
  2104. def filter(self, l):
  2105. return lazyset(self, l)
  2106. class orderedlazyset(_orderedsetmixin, lazyset):
  2107. """Subclass of lazyset which subset can be ordered either ascending or
  2108. descendingly
  2109. """
  2110. def __init__(self, subset, condition, ascending=True):
  2111. super(orderedlazyset, self).__init__(subset, condition)
  2112. self._ascending = ascending
  2113. def filter(self, l):
  2114. return orderedlazyset(self, l, ascending=self._ascending)
  2115. def ascending(self):
  2116. if not self._ascending:
  2117. self.reverse()
  2118. def descending(self):
  2119. if self._ascending:
  2120. self.reverse()
  2121. def __and__(self, x):
  2122. return orderedlazyset(self, x.__contains__,
  2123. ascending=self._ascending)
  2124. def __sub__(self, x):
  2125. return orderedlazyset(self, lambda r: r not in x,
  2126. ascending=self._ascending)
  2127. def __add__(self, x):
  2128. kwargs = {}
  2129. if self.isascending() and x.isascending():
  2130. kwargs['ascending'] = True
  2131. if self.isdescending() and x.isdescending():
  2132. kwargs['ascending'] = False
  2133. return _addset(self, x, **kwargs)
  2134. def sort(self, reverse=False):
  2135. if reverse:
  2136. if self._ascending:
  2137. self._subset.sort(reverse=reverse)
  2138. else:
  2139. if not self._ascending:
  2140. self._subset.sort(reverse=reverse)
  2141. self._ascending = not reverse
  2142. def isascending(self):
  2143. return self._ascending
  2144. def isdescending(self):
  2145. return not self._ascending
  2146. def reverse(self):
  2147. self._subset.reverse()
  2148. self._ascending = not self._ascending
  2149. class _addset(_orderedsetmixin):
  2150. """Represent the addition of two sets
  2151. Wrapper structure for lazily adding two structures without losing much
  2152. performance on the __contains__ method
  2153. If the ascending attribute is set, that means the two structures are
  2154. ordered in either an ascending or descending way. Therefore, we can add
  2155. them maintaining the order by iterating over both at the same time
  2156. This class does not duck-type baseset and it's only supposed to be used
  2157. internally
  2158. """
  2159. def __init__(self, revs1, revs2, ascending=None):
  2160. self._r1 = revs1
  2161. self._r2 = revs2
  2162. self._iter = None
  2163. self._ascending = ascending
  2164. self._genlist = None
  2165. def __len__(self):
  2166. return len(self._list)
  2167. @util.propertycache
  2168. def _list(self):
  2169. if not self._genlist:
  2170. self._genlist = baseset(self._iterator())
  2171. return self._genlist
  2172. def filter(self, condition):
  2173. if self._ascending is not None:
  2174. return orderedlazyset(self, condition, ascending=self._ascending)
  2175. return lazyset(self, condition)
  2176. def ascending(self):
  2177. if self._ascending is None:
  2178. self.sort()
  2179. self._ascending = True
  2180. else:
  2181. if not self._ascending:
  2182. self.reverse()
  2183. def descending(self):
  2184. if self._ascending is None:
  2185. self.sort(reverse=True)
  2186. self._ascending = False
  2187. else:
  2188. if self._ascending:
  2189. self.reverse()
  2190. def __and__(self, other):
  2191. filterfunc = other.__contains__
  2192. if self._ascending is not None:
  2193. return orderedlazyset(self, filterfunc, ascending=self._ascending)
  2194. return lazyset(self, filterfunc)
  2195. def __sub__(self, other):
  2196. filterfunc = lambda r: r not in other
  2197. if self._ascending is not None:
  2198. return orderedlazyset(self, filterfunc, ascending=self._ascending)
  2199. return lazyset(self, filterfunc)
  2200. def __add__(self, other):
  2201. """When both collections are ascending or descending, preserve the order
  2202. """
  2203. kwargs = {}
  2204. if self._ascending is not None:
  2205. if self.isascending() and other.isascending():
  2206. kwargs['ascending'] = True
  2207. if self.isdescending() and other.isdescending():
  2208. kwargs['ascending'] = False
  2209. return _addset(self, other, **kwargs)
  2210. def _iterator(self):
  2211. """Iterate over both collections without repeating elements
  2212. If the ascending attribute is not set, iterate over the first one and
  2213. then over the second one checking for membership on the first one so we
  2214. dont yield any duplicates.
  2215. If the ascending attribute is set, iterate over both collections at the
  2216. same time, yielding only one value at a time in the given order.
  2217. """
  2218. if not self._iter:
  2219. def gen():
  2220. if self._ascending is None:
  2221. for r in self._r1:
  2222. yield r
  2223. s = self._r1.set()
  2224. for r in self._r2:
  2225. if r not in s:
  2226. yield r
  2227. else:
  2228. iter1 = iter(self._r1)
  2229. iter2 = iter(self._r2)
  2230. val1 = None
  2231. val2 = None
  2232. choice = max
  2233. if self._ascending:
  2234. choice = min
  2235. try:
  2236. # Consume both iterators in an ordered way until one is
  2237. # empty
  2238. while True:
  2239. if val1 is None:
  2240. val1 = iter1.next()
  2241. if val2 is None:
  2242. val2 = iter2.next()
  2243. next = choice(val1, val2)
  2244. yield next
  2245. if val1 == next:
  2246. val1 = None
  2247. if val2 == next:
  2248. val2 = None
  2249. except StopIteration:
  2250. # Flush any remaining values and consume the other one
  2251. it = iter2
  2252. if val1 is not None:
  2253. yield val1
  2254. it = iter1
  2255. elif val2 is not None:
  2256. # might have been equality and both are empty
  2257. yield val2
  2258. for val in it:
  2259. yield val
  2260. self._iter = _generatorset(gen())
  2261. return self._iter
  2262. def __iter__(self):
  2263. if self._genlist:
  2264. return iter(self._genlist)
  2265. return iter(self._iterator())
  2266. def __contains__(self, x):
  2267. return x in self._r1 or x in self._r2
  2268. def set(self):
  2269. return self
  2270. def sort(self, reverse=False):
  2271. """Sort the added set
  2272. For this we use the cached list with all the generated values and if we
  2273. know they are ascending or descending we can sort them in a smart way.
  2274. """
  2275. if self._ascending is None:
  2276. self._list.sort(reverse=reverse)
  2277. self._ascending = not reverse
  2278. else:
  2279. if bool(self._ascending) == bool(reverse):
  2280. self.reverse()
  2281. def isascending(self):
  2282. return self._ascending is not None and self._ascending
  2283. def isdescending(self):
  2284. return self._ascending is not None and not self._ascending
  2285. def reverse(self):
  2286. self._list.reverse()
  2287. if self._ascending is not None:
  2288. self._ascending = not self._ascending
  2289. class _generatorset(object):
  2290. """Wrap a generator for lazy iteration
  2291. Wrapper structure for generators that provides lazy membership and can
  2292. be iterated more than once.
  2293. When asked for membership it generates values until either it finds the
  2294. requested one or has gone through all the elements in the generator
  2295. This class does not duck-type baseset and it's only supposed to be used
  2296. internally
  2297. """
  2298. def __init__(self, gen):
  2299. """
  2300. gen: a generator producing the values for the generatorset.
  2301. """
  2302. self._gen = gen
  2303. self._cache = {}
  2304. self._genlist = baseset([])
  2305. self._finished = False
  2306. def __contains__(self, x):
  2307. if x in self._cache:
  2308. return self._cache[x]
  2309. # Use new values only, as existing values would be cached.
  2310. for l in self._consumegen():
  2311. if l == x:
  2312. return True
  2313. self._cache[x] = False
  2314. return False
  2315. def __iter__(self):
  2316. if self._finished:
  2317. for x in self._genlist:
  2318. yield x
  2319. return
  2320. i = 0
  2321. genlist = self._genlist
  2322. consume = self._consumegen()
  2323. while True:
  2324. if i < len(genlist):
  2325. yield genlist[i]
  2326. else:
  2327. yield consume.next()
  2328. i += 1
  2329. def _consumegen(self):
  2330. for item in self._gen:
  2331. self._cache[item] = True
  2332. self._genlist.append(item)
  2333. yield item
  2334. self._finished = True
  2335. def set(self):
  2336. return self
  2337. def sort(self, reverse=False):
  2338. if not self._finished:
  2339. for i in self:
  2340. continue
  2341. self._genlist.sort(reverse=reverse)
  2342. class _ascgeneratorset(_generatorset):
  2343. """Wrap a generator of ascending elements for lazy iteration
  2344. Same structure as _generatorset but stops iterating after it goes past
  2345. the value when asked for membership and the element is not contained
  2346. This class does not duck-type baseset and it's only supposed to be used
  2347. internally
  2348. """
  2349. def __contains__(self, x):
  2350. if x in self._cache:
  2351. return self._cache[x]
  2352. # Use new values only, as existing values would be cached.
  2353. for l in self._consumegen():
  2354. if l == x:
  2355. return True
  2356. if l > x:
  2357. break
  2358. self._cache[x] = False
  2359. return False
  2360. class _descgeneratorset(_generatorset):
  2361. """Wrap a generator of descending elements for lazy iteration
  2362. Same structure as _generatorset but stops iterating after it goes past
  2363. the value when asked for membership and the element is not contained
  2364. This class does not duck-type baseset and it's only supposed to be used
  2365. internally
  2366. """
  2367. def __contains__(self, x):
  2368. if x in self._cache:
  2369. return self._cache[x]
  2370. # Use new values only, as existing values would be cached.
  2371. for l in self._consumegen():
  2372. if l == x:
  2373. return True
  2374. if l < x:
  2375. break
  2376. self._cache[x] = False
  2377. return False
  2378. class spanset(_orderedsetmixin):
  2379. """Duck type for baseset class which represents a range of revisions and
  2380. can work lazily and without having all the range in memory
  2381. Note that spanset(x, y) behave almost like xrange(x, y) except for two
  2382. notable points:
  2383. - when x < y it will be automatically descending,
  2384. - revision filtered with this repoview will be skipped.
  2385. """
  2386. def __init__(self, repo, start=0, end=None):
  2387. """
  2388. start: first revision included the set
  2389. (default to 0)
  2390. end: first revision excluded (last+1)
  2391. (default to len(repo)
  2392. Spanset will be descending if `end` < `start`.
  2393. """
  2394. self._start = start
  2395. if end is not None:
  2396. self._end = end
  2397. else:
  2398. self._end = len(repo)
  2399. self._hiddenrevs = repo.changelog.filteredrevs
  2400. def ascending(self):
  2401. if self._start > self._end:
  2402. self.reverse()
  2403. def descending(self):
  2404. if self._start < self._end:
  2405. self.reverse()
  2406. def __iter__(self):
  2407. if self._start <= self._end:
  2408. iterrange = xrange(self._start, self._end)
  2409. else:
  2410. iterrange = xrange(self._start, self._end, -1)
  2411. if self._hiddenrevs:
  2412. s = self._hiddenrevs
  2413. for r in iterrange:
  2414. if r not in s:
  2415. yield r
  2416. else:
  2417. for r in iterrange:
  2418. yield r
  2419. def __contains__(self, rev):
  2420. return (((self._end < rev <= self._start)
  2421. or (self._start <= rev < self._end))
  2422. and not (self._hiddenrevs and rev in self._hiddenrevs))
  2423. def __nonzero__(self):
  2424. for r in self:
  2425. return True
  2426. return False
  2427. def __and__(self, x):
  2428. if isinstance(x, baseset):
  2429. x = x.set()
  2430. if self._start <= self._end:
  2431. return orderedlazyset(self, x.__contains__)
  2432. else:
  2433. return orderedlazyset(self, x.__contains__, ascending=False)
  2434. def __sub__(self, x):
  2435. if isinstance(x, baseset):
  2436. x = x.set()
  2437. if self._start <= self._end:
  2438. return orderedlazyset(self, lambda r: r not in x)
  2439. else:
  2440. return orderedlazyset(self, lambda r: r not in x, ascending=False)
  2441. def __add__(self, x):
  2442. kwargs = {}
  2443. if self.isascending() and x.isascending():
  2444. kwargs['ascending'] = True
  2445. if self.isdescending() and x.isdescending():
  2446. kwargs['ascending'] = False
  2447. return _addset(self, x, **kwargs)
  2448. def __len__(self):
  2449. if not self._hiddenrevs:
  2450. return abs(self._end - self._start)
  2451. else:
  2452. count = 0
  2453. start = self._start
  2454. end = self._end
  2455. for rev in self._hiddenrevs:
  2456. if (end < rev <= start) or (start <= rev < end):
  2457. count += 1
  2458. return abs(self._end - self._start) - count
  2459. def __getitem__(self, x):
  2460. # Basic implementation to be changed in future patches.
  2461. l = baseset([r for r in self])
  2462. return l[x]
  2463. def sort(self, reverse=False):
  2464. if bool(reverse) != (self._start > self._end):
  2465. self.reverse()
  2466. def reverse(self):
  2467. # Just switch the _start and _end parameters
  2468. if self._start <= self._end:
  2469. self._start, self._end = self._end - 1, self._start - 1
  2470. else:
  2471. self._start, self._end = self._end + 1, self._start + 1
  2472. def set(self):
  2473. return self
  2474. def isascending(self):
  2475. return self._start < self._end
  2476. def isdescending(self):
  2477. return self._start > self._end
  2478. def filter(self, l):
  2479. if self._start <= self._end:
  2480. return orderedlazyset(self, l)
  2481. else:
  2482. return orderedlazyset(self, l, ascending=False)
  2483. # tell hggettext to extract docstrings from these functions:
  2484. i18nfunctions = symbols.values()