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