PageRenderTime 53ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/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

Large files files are truncated, but you can click here to view the full file

  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"]:

Large files files are truncated, but you can click here to view the full file