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