##// END OF EJS Templates
revset: retrieve a bit less parents in roots()
Patrick Mezard -
r16395:c3fd35f8 stable
parent child Browse files
Show More
@@ -1,1225 +1,1226
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 as nodemod
10 import node as nodemod
11 import bookmarks as bookmarksmod
11 import bookmarks as bookmarksmod
12 import match as matchmod
12 import match as matchmod
13 from i18n import _
13 from i18n import _
14 import encoding
14 import encoding
15
15
16 elements = {
16 elements = {
17 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
17 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
18 "~": (18, None, ("ancestor", 18)),
18 "~": (18, None, ("ancestor", 18)),
19 "^": (18, None, ("parent", 18), ("parentpost", 18)),
19 "^": (18, None, ("parent", 18), ("parentpost", 18)),
20 "-": (5, ("negate", 19), ("minus", 5)),
20 "-": (5, ("negate", 19), ("minus", 5)),
21 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
21 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
22 ("dagrangepost", 17)),
22 ("dagrangepost", 17)),
23 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
23 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
24 ("dagrangepost", 17)),
24 ("dagrangepost", 17)),
25 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
25 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
26 "not": (10, ("not", 10)),
26 "not": (10, ("not", 10)),
27 "!": (10, ("not", 10)),
27 "!": (10, ("not", 10)),
28 "and": (5, None, ("and", 5)),
28 "and": (5, None, ("and", 5)),
29 "&": (5, None, ("and", 5)),
29 "&": (5, None, ("and", 5)),
30 "or": (4, None, ("or", 4)),
30 "or": (4, None, ("or", 4)),
31 "|": (4, None, ("or", 4)),
31 "|": (4, None, ("or", 4)),
32 "+": (4, None, ("or", 4)),
32 "+": (4, None, ("or", 4)),
33 ",": (2, None, ("list", 2)),
33 ",": (2, None, ("list", 2)),
34 ")": (0, None, None),
34 ")": (0, None, None),
35 "symbol": (0, ("symbol",), None),
35 "symbol": (0, ("symbol",), None),
36 "string": (0, ("string",), None),
36 "string": (0, ("string",), None),
37 "end": (0, None, None),
37 "end": (0, None, None),
38 }
38 }
39
39
40 keywords = set(['and', 'or', 'not'])
40 keywords = set(['and', 'or', 'not'])
41
41
42 def tokenize(program):
42 def tokenize(program):
43 pos, l = 0, len(program)
43 pos, l = 0, len(program)
44 while pos < l:
44 while pos < l:
45 c = program[pos]
45 c = program[pos]
46 if c.isspace(): # skip inter-token whitespace
46 if c.isspace(): # skip inter-token whitespace
47 pass
47 pass
48 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
48 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
49 yield ('::', None, pos)
49 yield ('::', None, pos)
50 pos += 1 # skip ahead
50 pos += 1 # skip ahead
51 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
51 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
52 yield ('..', None, pos)
52 yield ('..', None, pos)
53 pos += 1 # skip ahead
53 pos += 1 # skip ahead
54 elif c in "():,-|&+!~^": # handle simple operators
54 elif c in "():,-|&+!~^": # handle simple operators
55 yield (c, None, pos)
55 yield (c, None, pos)
56 elif (c in '"\'' or c == 'r' and
56 elif (c in '"\'' or c == 'r' and
57 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
57 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
58 if c == 'r':
58 if c == 'r':
59 pos += 1
59 pos += 1
60 c = program[pos]
60 c = program[pos]
61 decode = lambda x: x
61 decode = lambda x: x
62 else:
62 else:
63 decode = lambda x: x.decode('string-escape')
63 decode = lambda x: x.decode('string-escape')
64 pos += 1
64 pos += 1
65 s = pos
65 s = pos
66 while pos < l: # find closing quote
66 while pos < l: # find closing quote
67 d = program[pos]
67 d = program[pos]
68 if d == '\\': # skip over escaped characters
68 if d == '\\': # skip over escaped characters
69 pos += 2
69 pos += 2
70 continue
70 continue
71 if d == c:
71 if d == c:
72 yield ('string', decode(program[s:pos]), s)
72 yield ('string', decode(program[s:pos]), s)
73 break
73 break
74 pos += 1
74 pos += 1
75 else:
75 else:
76 raise error.ParseError(_("unterminated string"), s)
76 raise error.ParseError(_("unterminated string"), s)
77 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
77 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
78 s = pos
78 s = pos
79 pos += 1
79 pos += 1
80 while pos < l: # find end of symbol
80 while pos < l: # find end of symbol
81 d = program[pos]
81 d = program[pos]
82 if not (d.isalnum() or d in "._/" or ord(d) > 127):
82 if not (d.isalnum() or d in "._/" or ord(d) > 127):
83 break
83 break
84 if d == '.' and program[pos - 1] == '.': # special case for ..
84 if d == '.' and program[pos - 1] == '.': # special case for ..
85 pos -= 1
85 pos -= 1
86 break
86 break
87 pos += 1
87 pos += 1
88 sym = program[s:pos]
88 sym = program[s:pos]
89 if sym in keywords: # operator keywords
89 if sym in keywords: # operator keywords
90 yield (sym, None, s)
90 yield (sym, None, s)
91 else:
91 else:
92 yield ('symbol', sym, s)
92 yield ('symbol', sym, s)
93 pos -= 1
93 pos -= 1
94 else:
94 else:
95 raise error.ParseError(_("syntax error"), pos)
95 raise error.ParseError(_("syntax error"), pos)
96 pos += 1
96 pos += 1
97 yield ('end', None, pos)
97 yield ('end', None, pos)
98
98
99 # helpers
99 # helpers
100
100
101 def getstring(x, err):
101 def getstring(x, err):
102 if x and (x[0] == 'string' or x[0] == 'symbol'):
102 if x and (x[0] == 'string' or x[0] == 'symbol'):
103 return x[1]
103 return x[1]
104 raise error.ParseError(err)
104 raise error.ParseError(err)
105
105
106 def getlist(x):
106 def getlist(x):
107 if not x:
107 if not x:
108 return []
108 return []
109 if x[0] == 'list':
109 if x[0] == 'list':
110 return getlist(x[1]) + [x[2]]
110 return getlist(x[1]) + [x[2]]
111 return [x]
111 return [x]
112
112
113 def getargs(x, min, max, err):
113 def getargs(x, min, max, err):
114 l = getlist(x)
114 l = getlist(x)
115 if len(l) < min or len(l) > max:
115 if len(l) < min or len(l) > max:
116 raise error.ParseError(err)
116 raise error.ParseError(err)
117 return l
117 return l
118
118
119 def getset(repo, subset, x):
119 def getset(repo, subset, x):
120 if not x:
120 if not x:
121 raise error.ParseError(_("missing argument"))
121 raise error.ParseError(_("missing argument"))
122 return methods[x[0]](repo, subset, *x[1:])
122 return methods[x[0]](repo, subset, *x[1:])
123
123
124 # operator methods
124 # operator methods
125
125
126 def stringset(repo, subset, x):
126 def stringset(repo, subset, x):
127 x = repo[x].rev()
127 x = repo[x].rev()
128 if x == -1 and len(subset) == len(repo):
128 if x == -1 and len(subset) == len(repo):
129 return [-1]
129 return [-1]
130 if len(subset) == len(repo) or x in subset:
130 if len(subset) == len(repo) or x in subset:
131 return [x]
131 return [x]
132 return []
132 return []
133
133
134 def symbolset(repo, subset, x):
134 def symbolset(repo, subset, x):
135 if x in symbols:
135 if x in symbols:
136 raise error.ParseError(_("can't use %s here") % x)
136 raise error.ParseError(_("can't use %s here") % x)
137 return stringset(repo, subset, x)
137 return stringset(repo, subset, x)
138
138
139 def rangeset(repo, subset, x, y):
139 def rangeset(repo, subset, x, y):
140 m = getset(repo, subset, x)
140 m = getset(repo, subset, x)
141 if not m:
141 if not m:
142 m = getset(repo, range(len(repo)), x)
142 m = getset(repo, range(len(repo)), x)
143
143
144 n = getset(repo, subset, y)
144 n = getset(repo, subset, y)
145 if not n:
145 if not n:
146 n = getset(repo, range(len(repo)), y)
146 n = getset(repo, range(len(repo)), y)
147
147
148 if not m or not n:
148 if not m or not n:
149 return []
149 return []
150 m, n = m[0], n[-1]
150 m, n = m[0], n[-1]
151
151
152 if m < n:
152 if m < n:
153 r = range(m, n + 1)
153 r = range(m, n + 1)
154 else:
154 else:
155 r = range(m, n - 1, -1)
155 r = range(m, n - 1, -1)
156 s = set(subset)
156 s = set(subset)
157 return [x for x in r if x in s]
157 return [x for x in r if x in s]
158
158
159 def andset(repo, subset, x, y):
159 def andset(repo, subset, x, y):
160 return getset(repo, getset(repo, subset, x), y)
160 return getset(repo, getset(repo, subset, x), y)
161
161
162 def orset(repo, subset, x, y):
162 def orset(repo, subset, x, y):
163 xl = getset(repo, subset, x)
163 xl = getset(repo, subset, x)
164 s = set(xl)
164 s = set(xl)
165 yl = getset(repo, [r for r in subset if r not in s], y)
165 yl = getset(repo, [r for r in subset if r not in s], y)
166 return xl + yl
166 return xl + yl
167
167
168 def notset(repo, subset, x):
168 def notset(repo, subset, x):
169 s = set(getset(repo, subset, x))
169 s = set(getset(repo, subset, x))
170 return [r for r in subset if r not in s]
170 return [r for r in subset if r not in s]
171
171
172 def listset(repo, subset, a, b):
172 def listset(repo, subset, a, b):
173 raise error.ParseError(_("can't use a list in this context"))
173 raise error.ParseError(_("can't use a list in this context"))
174
174
175 def func(repo, subset, a, b):
175 def func(repo, subset, a, b):
176 if a[0] == 'symbol' and a[1] in symbols:
176 if a[0] == 'symbol' and a[1] in symbols:
177 return symbols[a[1]](repo, subset, b)
177 return symbols[a[1]](repo, subset, b)
178 raise error.ParseError(_("not a function: %s") % a[1])
178 raise error.ParseError(_("not a function: %s") % a[1])
179
179
180 # functions
180 # functions
181
181
182 def adds(repo, subset, x):
182 def adds(repo, subset, x):
183 """``adds(pattern)``
183 """``adds(pattern)``
184 Changesets that add a file matching pattern.
184 Changesets that add a file matching pattern.
185 """
185 """
186 # i18n: "adds" is a keyword
186 # i18n: "adds" is a keyword
187 pat = getstring(x, _("adds requires a pattern"))
187 pat = getstring(x, _("adds requires a pattern"))
188 return checkstatus(repo, subset, pat, 1)
188 return checkstatus(repo, subset, pat, 1)
189
189
190 def ancestor(repo, subset, x):
190 def ancestor(repo, subset, x):
191 """``ancestor(single, single)``
191 """``ancestor(single, single)``
192 Greatest common ancestor of the two changesets.
192 Greatest common ancestor of the two changesets.
193 """
193 """
194 # i18n: "ancestor" is a keyword
194 # i18n: "ancestor" is a keyword
195 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
195 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
196 r = range(len(repo))
196 r = range(len(repo))
197 a = getset(repo, r, l[0])
197 a = getset(repo, r, l[0])
198 b = getset(repo, r, l[1])
198 b = getset(repo, r, l[1])
199 if len(a) != 1 or len(b) != 1:
199 if len(a) != 1 or len(b) != 1:
200 # i18n: "ancestor" is a keyword
200 # i18n: "ancestor" is a keyword
201 raise error.ParseError(_("ancestor arguments must be single revisions"))
201 raise error.ParseError(_("ancestor arguments must be single revisions"))
202 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
202 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
203
203
204 return [r for r in an if r in subset]
204 return [r for r in an if r in subset]
205
205
206 def ancestors(repo, subset, x):
206 def ancestors(repo, subset, x):
207 """``ancestors(set)``
207 """``ancestors(set)``
208 Changesets that are ancestors of a changeset in set.
208 Changesets that are ancestors of a changeset in set.
209 """
209 """
210 args = getset(repo, range(len(repo)), x)
210 args = getset(repo, range(len(repo)), x)
211 if not args:
211 if not args:
212 return []
212 return []
213 s = set(repo.changelog.ancestors(*args)) | set(args)
213 s = set(repo.changelog.ancestors(*args)) | set(args)
214 return [r for r in subset if r in s]
214 return [r for r in subset if r in s]
215
215
216 def ancestorspec(repo, subset, x, n):
216 def ancestorspec(repo, subset, x, n):
217 """``set~n``
217 """``set~n``
218 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
218 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
219 """
219 """
220 try:
220 try:
221 n = int(n[1])
221 n = int(n[1])
222 except (TypeError, ValueError):
222 except (TypeError, ValueError):
223 raise error.ParseError(_("~ expects a number"))
223 raise error.ParseError(_("~ expects a number"))
224 ps = set()
224 ps = set()
225 cl = repo.changelog
225 cl = repo.changelog
226 for r in getset(repo, subset, x):
226 for r in getset(repo, subset, x):
227 for i in range(n):
227 for i in range(n):
228 r = cl.parentrevs(r)[0]
228 r = cl.parentrevs(r)[0]
229 ps.add(r)
229 ps.add(r)
230 return [r for r in subset if r in ps]
230 return [r for r in subset if r in ps]
231
231
232 def author(repo, subset, x):
232 def author(repo, subset, x):
233 """``author(string)``
233 """``author(string)``
234 Alias for ``user(string)``.
234 Alias for ``user(string)``.
235 """
235 """
236 # i18n: "author" is a keyword
236 # i18n: "author" is a keyword
237 n = encoding.lower(getstring(x, _("author requires a string")))
237 n = encoding.lower(getstring(x, _("author requires a string")))
238 return [r for r in subset if n in encoding.lower(repo[r].user())]
238 return [r for r in subset if n in encoding.lower(repo[r].user())]
239
239
240 def bisect(repo, subset, x):
240 def bisect(repo, subset, x):
241 """``bisect(string)``
241 """``bisect(string)``
242 Changesets marked in the specified bisect status:
242 Changesets marked in the specified bisect status:
243
243
244 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
244 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
245 - ``goods``, ``bads`` : csets topologicaly good/bad
245 - ``goods``, ``bads`` : csets topologicaly good/bad
246 - ``range`` : csets taking part in the bisection
246 - ``range`` : csets taking part in the bisection
247 - ``pruned`` : csets that are goods, bads or skipped
247 - ``pruned`` : csets that are goods, bads or skipped
248 - ``untested`` : csets whose fate is yet unknown
248 - ``untested`` : csets whose fate is yet unknown
249 - ``ignored`` : csets ignored due to DAG topology
249 - ``ignored`` : csets ignored due to DAG topology
250 """
250 """
251 status = getstring(x, _("bisect requires a string")).lower()
251 status = getstring(x, _("bisect requires a string")).lower()
252 return [r for r in subset if r in hbisect.get(repo, status)]
252 return [r for r in subset if r in hbisect.get(repo, status)]
253
253
254 # Backward-compatibility
254 # Backward-compatibility
255 # - no help entry so that we do not advertise it any more
255 # - no help entry so that we do not advertise it any more
256 def bisected(repo, subset, x):
256 def bisected(repo, subset, x):
257 return bisect(repo, subset, x)
257 return bisect(repo, subset, x)
258
258
259 def bookmark(repo, subset, x):
259 def bookmark(repo, subset, x):
260 """``bookmark([name])``
260 """``bookmark([name])``
261 The named bookmark or all bookmarks.
261 The named bookmark or all bookmarks.
262 """
262 """
263 # i18n: "bookmark" is a keyword
263 # i18n: "bookmark" is a keyword
264 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
264 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
265 if args:
265 if args:
266 bm = getstring(args[0],
266 bm = getstring(args[0],
267 # i18n: "bookmark" is a keyword
267 # i18n: "bookmark" is a keyword
268 _('the argument to bookmark must be a string'))
268 _('the argument to bookmark must be a string'))
269 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
269 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
270 if not bmrev:
270 if not bmrev:
271 raise util.Abort(_("bookmark '%s' does not exist") % bm)
271 raise util.Abort(_("bookmark '%s' does not exist") % bm)
272 bmrev = repo[bmrev].rev()
272 bmrev = repo[bmrev].rev()
273 return [r for r in subset if r == bmrev]
273 return [r for r in subset if r == bmrev]
274 bms = set([repo[r].rev()
274 bms = set([repo[r].rev()
275 for r in bookmarksmod.listbookmarks(repo).values()])
275 for r in bookmarksmod.listbookmarks(repo).values()])
276 return [r for r in subset if r in bms]
276 return [r for r in subset if r in bms]
277
277
278 def branch(repo, subset, x):
278 def branch(repo, subset, x):
279 """``branch(string or set)``
279 """``branch(string or set)``
280 All changesets belonging to the given branch or the branches of the given
280 All changesets belonging to the given branch or the branches of the given
281 changesets.
281 changesets.
282 """
282 """
283 try:
283 try:
284 b = getstring(x, '')
284 b = getstring(x, '')
285 if b in repo.branchmap():
285 if b in repo.branchmap():
286 return [r for r in subset if repo[r].branch() == b]
286 return [r for r in subset if repo[r].branch() == b]
287 except error.ParseError:
287 except error.ParseError:
288 # not a string, but another revspec, e.g. tip()
288 # not a string, but another revspec, e.g. tip()
289 pass
289 pass
290
290
291 s = getset(repo, range(len(repo)), x)
291 s = getset(repo, range(len(repo)), x)
292 b = set()
292 b = set()
293 for r in s:
293 for r in s:
294 b.add(repo[r].branch())
294 b.add(repo[r].branch())
295 s = set(s)
295 s = set(s)
296 return [r for r in subset if r in s or repo[r].branch() in b]
296 return [r for r in subset if r in s or repo[r].branch() in b]
297
297
298 def checkstatus(repo, subset, pat, field):
298 def checkstatus(repo, subset, pat, field):
299 m = None
299 m = None
300 s = []
300 s = []
301 fast = not matchmod.patkind(pat)
301 fast = not matchmod.patkind(pat)
302 for r in subset:
302 for r in subset:
303 c = repo[r]
303 c = repo[r]
304 if fast:
304 if fast:
305 if pat not in c.files():
305 if pat not in c.files():
306 continue
306 continue
307 else:
307 else:
308 if not m or matchmod.patkind(pat) == 'set':
308 if not m or matchmod.patkind(pat) == 'set':
309 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
309 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
310 for f in c.files():
310 for f in c.files():
311 if m(f):
311 if m(f):
312 break
312 break
313 else:
313 else:
314 continue
314 continue
315 files = repo.status(c.p1().node(), c.node())[field]
315 files = repo.status(c.p1().node(), c.node())[field]
316 if fast:
316 if fast:
317 if pat in files:
317 if pat in files:
318 s.append(r)
318 s.append(r)
319 else:
319 else:
320 for f in files:
320 for f in files:
321 if m(f):
321 if m(f):
322 s.append(r)
322 s.append(r)
323 break
323 break
324 return s
324 return s
325
325
326 def _children(repo, narrow, s):
326 def _children(repo, narrow, s):
327 cs = set()
327 cs = set()
328 pr = repo.changelog.parentrevs
328 pr = repo.changelog.parentrevs
329 s = set(s)
329 s = set(s)
330 for r in narrow:
330 for r in narrow:
331 for p in pr(r):
331 for p in pr(r):
332 if p in s:
332 if p in s:
333 cs.add(r)
333 cs.add(r)
334 return cs
334 return cs
335
335
336 def children(repo, subset, x):
336 def children(repo, subset, x):
337 """``children(set)``
337 """``children(set)``
338 Child changesets of changesets in set.
338 Child changesets of changesets in set.
339 """
339 """
340 s = getset(repo, range(len(repo)), x)
340 s = getset(repo, range(len(repo)), x)
341 cs = _children(repo, subset, s)
341 cs = _children(repo, subset, s)
342 return [r for r in subset if r in cs]
342 return [r for r in subset if r in cs]
343
343
344 def closed(repo, subset, x):
344 def closed(repo, subset, x):
345 """``closed()``
345 """``closed()``
346 Changeset is closed.
346 Changeset is closed.
347 """
347 """
348 # i18n: "closed" is a keyword
348 # i18n: "closed" is a keyword
349 getargs(x, 0, 0, _("closed takes no arguments"))
349 getargs(x, 0, 0, _("closed takes no arguments"))
350 return [r for r in subset if repo[r].extra().get('close')]
350 return [r for r in subset if repo[r].extra().get('close')]
351
351
352 def contains(repo, subset, x):
352 def contains(repo, subset, x):
353 """``contains(pattern)``
353 """``contains(pattern)``
354 Revision contains a file matching pattern. See :hg:`help patterns`
354 Revision contains a file matching pattern. See :hg:`help patterns`
355 for information about file patterns.
355 for information about file patterns.
356 """
356 """
357 # i18n: "contains" is a keyword
357 # i18n: "contains" is a keyword
358 pat = getstring(x, _("contains requires a pattern"))
358 pat = getstring(x, _("contains requires a pattern"))
359 m = None
359 m = None
360 s = []
360 s = []
361 if not matchmod.patkind(pat):
361 if not matchmod.patkind(pat):
362 for r in subset:
362 for r in subset:
363 if pat in repo[r]:
363 if pat in repo[r]:
364 s.append(r)
364 s.append(r)
365 else:
365 else:
366 for r in subset:
366 for r in subset:
367 c = repo[r]
367 c = repo[r]
368 if not m or matchmod.patkind(pat) == 'set':
368 if not m or matchmod.patkind(pat) == 'set':
369 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
369 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
370 for f in c.manifest():
370 for f in c.manifest():
371 if m(f):
371 if m(f):
372 s.append(r)
372 s.append(r)
373 break
373 break
374 return s
374 return s
375
375
376 def date(repo, subset, x):
376 def date(repo, subset, x):
377 """``date(interval)``
377 """``date(interval)``
378 Changesets within the interval, see :hg:`help dates`.
378 Changesets within the interval, see :hg:`help dates`.
379 """
379 """
380 # i18n: "date" is a keyword
380 # i18n: "date" is a keyword
381 ds = getstring(x, _("date requires a string"))
381 ds = getstring(x, _("date requires a string"))
382 dm = util.matchdate(ds)
382 dm = util.matchdate(ds)
383 return [r for r in subset if dm(repo[r].date()[0])]
383 return [r for r in subset if dm(repo[r].date()[0])]
384
384
385 def desc(repo, subset, x):
385 def desc(repo, subset, x):
386 """``desc(string)``
386 """``desc(string)``
387 Search commit message for string. The match is case-insensitive.
387 Search commit message for string. The match is case-insensitive.
388 """
388 """
389 # i18n: "desc" is a keyword
389 # i18n: "desc" is a keyword
390 ds = encoding.lower(getstring(x, _("desc requires a string")))
390 ds = encoding.lower(getstring(x, _("desc requires a string")))
391 l = []
391 l = []
392 for r in subset:
392 for r in subset:
393 c = repo[r]
393 c = repo[r]
394 if ds in encoding.lower(c.description()):
394 if ds in encoding.lower(c.description()):
395 l.append(r)
395 l.append(r)
396 return l
396 return l
397
397
398 def descendants(repo, subset, x):
398 def descendants(repo, subset, x):
399 """``descendants(set)``
399 """``descendants(set)``
400 Changesets which are descendants of changesets in set.
400 Changesets which are descendants of changesets in set.
401 """
401 """
402 args = getset(repo, range(len(repo)), x)
402 args = getset(repo, range(len(repo)), x)
403 if not args:
403 if not args:
404 return []
404 return []
405 s = set(repo.changelog.descendants(*args)) | set(args)
405 s = set(repo.changelog.descendants(*args)) | set(args)
406 return [r for r in subset if r in s]
406 return [r for r in subset if r in s]
407
407
408 def draft(repo, subset, x):
408 def draft(repo, subset, x):
409 """``draft()``
409 """``draft()``
410 Changeset in draft phase."""
410 Changeset in draft phase."""
411 getargs(x, 0, 0, _("draft takes no arguments"))
411 getargs(x, 0, 0, _("draft takes no arguments"))
412 return [r for r in subset if repo._phaserev[r] == phases.draft]
412 return [r for r in subset if repo._phaserev[r] == phases.draft]
413
413
414 def filelog(repo, subset, x):
414 def filelog(repo, subset, x):
415 """``filelog(pattern)``
415 """``filelog(pattern)``
416 Changesets connected to the specified filelog.
416 Changesets connected to the specified filelog.
417 """
417 """
418
418
419 pat = getstring(x, _("filelog requires a pattern"))
419 pat = getstring(x, _("filelog requires a pattern"))
420 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
420 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
421 ctx=repo[None])
421 ctx=repo[None])
422 s = set()
422 s = set()
423
423
424 if not matchmod.patkind(pat):
424 if not matchmod.patkind(pat):
425 for f in m.files():
425 for f in m.files():
426 fl = repo.file(f)
426 fl = repo.file(f)
427 for fr in fl:
427 for fr in fl:
428 s.add(fl.linkrev(fr))
428 s.add(fl.linkrev(fr))
429 else:
429 else:
430 for f in repo[None]:
430 for f in repo[None]:
431 if m(f):
431 if m(f):
432 fl = repo.file(f)
432 fl = repo.file(f)
433 for fr in fl:
433 for fr in fl:
434 s.add(fl.linkrev(fr))
434 s.add(fl.linkrev(fr))
435
435
436 return [r for r in subset if r in s]
436 return [r for r in subset if r in s]
437
437
438 def first(repo, subset, x):
438 def first(repo, subset, x):
439 """``first(set, [n])``
439 """``first(set, [n])``
440 An alias for limit().
440 An alias for limit().
441 """
441 """
442 return limit(repo, subset, x)
442 return limit(repo, subset, x)
443
443
444 def follow(repo, subset, x):
444 def follow(repo, subset, x):
445 """``follow([file])``
445 """``follow([file])``
446 An alias for ``::.`` (ancestors of the working copy's first parent).
446 An alias for ``::.`` (ancestors of the working copy's first parent).
447 If a filename is specified, the history of the given file is followed,
447 If a filename is specified, the history of the given file is followed,
448 including copies.
448 including copies.
449 """
449 """
450 # i18n: "follow" is a keyword
450 # i18n: "follow" is a keyword
451 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
451 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
452 c = repo['.']
452 c = repo['.']
453 if l:
453 if l:
454 x = getstring(l[0], _("follow expected a filename"))
454 x = getstring(l[0], _("follow expected a filename"))
455 if x in c:
455 if x in c:
456 cx = c[x]
456 cx = c[x]
457 s = set(ctx.rev() for ctx in cx.ancestors())
457 s = set(ctx.rev() for ctx in cx.ancestors())
458 # include the revision responsible for the most recent version
458 # include the revision responsible for the most recent version
459 s.add(cx.linkrev())
459 s.add(cx.linkrev())
460 else:
460 else:
461 return []
461 return []
462 else:
462 else:
463 s = set(repo.changelog.ancestors(c.rev()))
463 s = set(repo.changelog.ancestors(c.rev()))
464 s.add(c.rev())
464 s.add(c.rev())
465
465
466 return [r for r in subset if r in s]
466 return [r for r in subset if r in s]
467
467
468 def getall(repo, subset, x):
468 def getall(repo, subset, x):
469 """``all()``
469 """``all()``
470 All changesets, the same as ``0:tip``.
470 All changesets, the same as ``0:tip``.
471 """
471 """
472 # i18n: "all" is a keyword
472 # i18n: "all" is a keyword
473 getargs(x, 0, 0, _("all takes no arguments"))
473 getargs(x, 0, 0, _("all takes no arguments"))
474 return subset
474 return subset
475
475
476 def grep(repo, subset, x):
476 def grep(repo, subset, x):
477 """``grep(regex)``
477 """``grep(regex)``
478 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
478 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
479 to ensure special escape characters are handled correctly. Unlike
479 to ensure special escape characters are handled correctly. Unlike
480 ``keyword(string)``, the match is case-sensitive.
480 ``keyword(string)``, the match is case-sensitive.
481 """
481 """
482 try:
482 try:
483 # i18n: "grep" is a keyword
483 # i18n: "grep" is a keyword
484 gr = re.compile(getstring(x, _("grep requires a string")))
484 gr = re.compile(getstring(x, _("grep requires a string")))
485 except re.error, e:
485 except re.error, e:
486 raise error.ParseError(_('invalid match pattern: %s') % e)
486 raise error.ParseError(_('invalid match pattern: %s') % e)
487 l = []
487 l = []
488 for r in subset:
488 for r in subset:
489 c = repo[r]
489 c = repo[r]
490 for e in c.files() + [c.user(), c.description()]:
490 for e in c.files() + [c.user(), c.description()]:
491 if gr.search(e):
491 if gr.search(e):
492 l.append(r)
492 l.append(r)
493 break
493 break
494 return l
494 return l
495
495
496 def hasfile(repo, subset, x):
496 def hasfile(repo, subset, x):
497 """``file(pattern)``
497 """``file(pattern)``
498 Changesets affecting files matched by pattern.
498 Changesets affecting files matched by pattern.
499 """
499 """
500 # i18n: "file" is a keyword
500 # i18n: "file" is a keyword
501 pat = getstring(x, _("file requires a pattern"))
501 pat = getstring(x, _("file requires a pattern"))
502 m = None
502 m = None
503 s = []
503 s = []
504 for r in subset:
504 for r in subset:
505 c = repo[r]
505 c = repo[r]
506 if not m or matchmod.patkind(pat) == 'set':
506 if not m or matchmod.patkind(pat) == 'set':
507 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
507 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
508 for f in c.files():
508 for f in c.files():
509 if m(f):
509 if m(f):
510 s.append(r)
510 s.append(r)
511 break
511 break
512 return s
512 return s
513
513
514 def head(repo, subset, x):
514 def head(repo, subset, x):
515 """``head()``
515 """``head()``
516 Changeset is a named branch head.
516 Changeset is a named branch head.
517 """
517 """
518 # i18n: "head" is a keyword
518 # i18n: "head" is a keyword
519 getargs(x, 0, 0, _("head takes no arguments"))
519 getargs(x, 0, 0, _("head takes no arguments"))
520 hs = set()
520 hs = set()
521 for b, ls in repo.branchmap().iteritems():
521 for b, ls in repo.branchmap().iteritems():
522 hs.update(repo[h].rev() for h in ls)
522 hs.update(repo[h].rev() for h in ls)
523 return [r for r in subset if r in hs]
523 return [r for r in subset if r in hs]
524
524
525 def heads(repo, subset, x):
525 def heads(repo, subset, x):
526 """``heads(set)``
526 """``heads(set)``
527 Members of set with no children in set.
527 Members of set with no children in set.
528 """
528 """
529 s = getset(repo, subset, x)
529 s = getset(repo, subset, x)
530 ps = set(parents(repo, subset, x))
530 ps = set(parents(repo, subset, x))
531 return [r for r in s if r not in ps]
531 return [r for r in s if r not in ps]
532
532
533 def keyword(repo, subset, x):
533 def keyword(repo, subset, x):
534 """``keyword(string)``
534 """``keyword(string)``
535 Search commit message, user name, and names of changed files for
535 Search commit message, user name, and names of changed files for
536 string. The match is case-insensitive.
536 string. The match is case-insensitive.
537 """
537 """
538 # i18n: "keyword" is a keyword
538 # i18n: "keyword" is a keyword
539 kw = encoding.lower(getstring(x, _("keyword requires a string")))
539 kw = encoding.lower(getstring(x, _("keyword requires a string")))
540 l = []
540 l = []
541 for r in subset:
541 for r in subset:
542 c = repo[r]
542 c = repo[r]
543 t = " ".join(c.files() + [c.user(), c.description()])
543 t = " ".join(c.files() + [c.user(), c.description()])
544 if kw in encoding.lower(t):
544 if kw in encoding.lower(t):
545 l.append(r)
545 l.append(r)
546 return l
546 return l
547
547
548 def limit(repo, subset, x):
548 def limit(repo, subset, x):
549 """``limit(set, [n])``
549 """``limit(set, [n])``
550 First n members of set, defaulting to 1.
550 First n members of set, defaulting to 1.
551 """
551 """
552 # i18n: "limit" is a keyword
552 # i18n: "limit" is a keyword
553 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
553 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
554 try:
554 try:
555 lim = 1
555 lim = 1
556 if len(l) == 2:
556 if len(l) == 2:
557 # i18n: "limit" is a keyword
557 # i18n: "limit" is a keyword
558 lim = int(getstring(l[1], _("limit requires a number")))
558 lim = int(getstring(l[1], _("limit requires a number")))
559 except (TypeError, ValueError):
559 except (TypeError, ValueError):
560 # i18n: "limit" is a keyword
560 # i18n: "limit" is a keyword
561 raise error.ParseError(_("limit expects a number"))
561 raise error.ParseError(_("limit expects a number"))
562 ss = set(subset)
562 ss = set(subset)
563 os = getset(repo, range(len(repo)), l[0])[:lim]
563 os = getset(repo, range(len(repo)), l[0])[:lim]
564 return [r for r in os if r in ss]
564 return [r for r in os if r in ss]
565
565
566 def last(repo, subset, x):
566 def last(repo, subset, x):
567 """``last(set, [n])``
567 """``last(set, [n])``
568 Last n members of set, defaulting to 1.
568 Last n members of set, defaulting to 1.
569 """
569 """
570 # i18n: "last" is a keyword
570 # i18n: "last" is a keyword
571 l = getargs(x, 1, 2, _("last requires one or two arguments"))
571 l = getargs(x, 1, 2, _("last requires one or two arguments"))
572 try:
572 try:
573 lim = 1
573 lim = 1
574 if len(l) == 2:
574 if len(l) == 2:
575 # i18n: "last" is a keyword
575 # i18n: "last" is a keyword
576 lim = int(getstring(l[1], _("last requires a number")))
576 lim = int(getstring(l[1], _("last requires a number")))
577 except (TypeError, ValueError):
577 except (TypeError, ValueError):
578 # i18n: "last" is a keyword
578 # i18n: "last" is a keyword
579 raise error.ParseError(_("last expects a number"))
579 raise error.ParseError(_("last expects a number"))
580 ss = set(subset)
580 ss = set(subset)
581 os = getset(repo, range(len(repo)), l[0])[-lim:]
581 os = getset(repo, range(len(repo)), l[0])[-lim:]
582 return [r for r in os if r in ss]
582 return [r for r in os if r in ss]
583
583
584 def maxrev(repo, subset, x):
584 def maxrev(repo, subset, x):
585 """``max(set)``
585 """``max(set)``
586 Changeset with highest revision number in set.
586 Changeset with highest revision number in set.
587 """
587 """
588 os = getset(repo, range(len(repo)), x)
588 os = getset(repo, range(len(repo)), x)
589 if os:
589 if os:
590 m = max(os)
590 m = max(os)
591 if m in subset:
591 if m in subset:
592 return [m]
592 return [m]
593 return []
593 return []
594
594
595 def merge(repo, subset, x):
595 def merge(repo, subset, x):
596 """``merge()``
596 """``merge()``
597 Changeset is a merge changeset.
597 Changeset is a merge changeset.
598 """
598 """
599 # i18n: "merge" is a keyword
599 # i18n: "merge" is a keyword
600 getargs(x, 0, 0, _("merge takes no arguments"))
600 getargs(x, 0, 0, _("merge takes no arguments"))
601 cl = repo.changelog
601 cl = repo.changelog
602 return [r for r in subset if cl.parentrevs(r)[1] != -1]
602 return [r for r in subset if cl.parentrevs(r)[1] != -1]
603
603
604 def minrev(repo, subset, x):
604 def minrev(repo, subset, x):
605 """``min(set)``
605 """``min(set)``
606 Changeset with lowest revision number in set.
606 Changeset with lowest revision number in set.
607 """
607 """
608 os = getset(repo, range(len(repo)), x)
608 os = getset(repo, range(len(repo)), x)
609 if os:
609 if os:
610 m = min(os)
610 m = min(os)
611 if m in subset:
611 if m in subset:
612 return [m]
612 return [m]
613 return []
613 return []
614
614
615 def modifies(repo, subset, x):
615 def modifies(repo, subset, x):
616 """``modifies(pattern)``
616 """``modifies(pattern)``
617 Changesets modifying files matched by pattern.
617 Changesets modifying files matched by pattern.
618 """
618 """
619 # i18n: "modifies" is a keyword
619 # i18n: "modifies" is a keyword
620 pat = getstring(x, _("modifies requires a pattern"))
620 pat = getstring(x, _("modifies requires a pattern"))
621 return checkstatus(repo, subset, pat, 0)
621 return checkstatus(repo, subset, pat, 0)
622
622
623 def node(repo, subset, x):
623 def node(repo, subset, x):
624 """``id(string)``
624 """``id(string)``
625 Revision non-ambiguously specified by the given hex string prefix.
625 Revision non-ambiguously specified by the given hex string prefix.
626 """
626 """
627 # i18n: "id" is a keyword
627 # i18n: "id" is a keyword
628 l = getargs(x, 1, 1, _("id requires one argument"))
628 l = getargs(x, 1, 1, _("id requires one argument"))
629 # i18n: "id" is a keyword
629 # i18n: "id" is a keyword
630 n = getstring(l[0], _("id requires a string"))
630 n = getstring(l[0], _("id requires a string"))
631 if len(n) == 40:
631 if len(n) == 40:
632 rn = repo[n].rev()
632 rn = repo[n].rev()
633 else:
633 else:
634 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
634 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
635 return [r for r in subset if r == rn]
635 return [r for r in subset if r == rn]
636
636
637 def outgoing(repo, subset, x):
637 def outgoing(repo, subset, x):
638 """``outgoing([path])``
638 """``outgoing([path])``
639 Changesets not found in the specified destination repository, or the
639 Changesets not found in the specified destination repository, or the
640 default push location.
640 default push location.
641 """
641 """
642 import hg # avoid start-up nasties
642 import hg # avoid start-up nasties
643 # i18n: "outgoing" is a keyword
643 # i18n: "outgoing" is a keyword
644 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
644 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
645 # i18n: "outgoing" is a keyword
645 # i18n: "outgoing" is a keyword
646 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
646 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
647 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
647 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
648 dest, branches = hg.parseurl(dest)
648 dest, branches = hg.parseurl(dest)
649 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
649 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
650 if revs:
650 if revs:
651 revs = [repo.lookup(rev) for rev in revs]
651 revs = [repo.lookup(rev) for rev in revs]
652 other = hg.peer(repo, {}, dest)
652 other = hg.peer(repo, {}, dest)
653 repo.ui.pushbuffer()
653 repo.ui.pushbuffer()
654 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
654 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
655 repo.ui.popbuffer()
655 repo.ui.popbuffer()
656 cl = repo.changelog
656 cl = repo.changelog
657 o = set([cl.rev(r) for r in outgoing.missing])
657 o = set([cl.rev(r) for r in outgoing.missing])
658 return [r for r in subset if r in o]
658 return [r for r in subset if r in o]
659
659
660 def p1(repo, subset, x):
660 def p1(repo, subset, x):
661 """``p1([set])``
661 """``p1([set])``
662 First parent of changesets in set, or the working directory.
662 First parent of changesets in set, or the working directory.
663 """
663 """
664 if x is None:
664 if x is None:
665 p = repo[x].p1().rev()
665 p = repo[x].p1().rev()
666 return [r for r in subset if r == p]
666 return [r for r in subset if r == p]
667
667
668 ps = set()
668 ps = set()
669 cl = repo.changelog
669 cl = repo.changelog
670 for r in getset(repo, range(len(repo)), x):
670 for r in getset(repo, range(len(repo)), x):
671 ps.add(cl.parentrevs(r)[0])
671 ps.add(cl.parentrevs(r)[0])
672 return [r for r in subset if r in ps]
672 return [r for r in subset if r in ps]
673
673
674 def p2(repo, subset, x):
674 def p2(repo, subset, x):
675 """``p2([set])``
675 """``p2([set])``
676 Second parent of changesets in set, or the working directory.
676 Second parent of changesets in set, or the working directory.
677 """
677 """
678 if x is None:
678 if x is None:
679 ps = repo[x].parents()
679 ps = repo[x].parents()
680 try:
680 try:
681 p = ps[1].rev()
681 p = ps[1].rev()
682 return [r for r in subset if r == p]
682 return [r for r in subset if r == p]
683 except IndexError:
683 except IndexError:
684 return []
684 return []
685
685
686 ps = set()
686 ps = set()
687 cl = repo.changelog
687 cl = repo.changelog
688 for r in getset(repo, range(len(repo)), x):
688 for r in getset(repo, range(len(repo)), x):
689 ps.add(cl.parentrevs(r)[1])
689 ps.add(cl.parentrevs(r)[1])
690 return [r for r in subset if r in ps]
690 return [r for r in subset if r in ps]
691
691
692 def parents(repo, subset, x):
692 def parents(repo, subset, x):
693 """``parents([set])``
693 """``parents([set])``
694 The set of all parents for all changesets in set, or the working directory.
694 The set of all parents for all changesets in set, or the working directory.
695 """
695 """
696 if x is None:
696 if x is None:
697 ps = tuple(p.rev() for p in repo[x].parents())
697 ps = tuple(p.rev() for p in repo[x].parents())
698 return [r for r in subset if r in ps]
698 return [r for r in subset if r in ps]
699
699
700 ps = set()
700 ps = set()
701 cl = repo.changelog
701 cl = repo.changelog
702 for r in getset(repo, range(len(repo)), x):
702 for r in getset(repo, range(len(repo)), x):
703 ps.update(cl.parentrevs(r))
703 ps.update(cl.parentrevs(r))
704 return [r for r in subset if r in ps]
704 return [r for r in subset if r in ps]
705
705
706 def parentspec(repo, subset, x, n):
706 def parentspec(repo, subset, x, n):
707 """``set^0``
707 """``set^0``
708 The set.
708 The set.
709 ``set^1`` (or ``set^``), ``set^2``
709 ``set^1`` (or ``set^``), ``set^2``
710 First or second parent, respectively, of all changesets in set.
710 First or second parent, respectively, of all changesets in set.
711 """
711 """
712 try:
712 try:
713 n = int(n[1])
713 n = int(n[1])
714 if n not in (0, 1, 2):
714 if n not in (0, 1, 2):
715 raise ValueError
715 raise ValueError
716 except (TypeError, ValueError):
716 except (TypeError, ValueError):
717 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
717 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
718 ps = set()
718 ps = set()
719 cl = repo.changelog
719 cl = repo.changelog
720 for r in getset(repo, subset, x):
720 for r in getset(repo, subset, x):
721 if n == 0:
721 if n == 0:
722 ps.add(r)
722 ps.add(r)
723 elif n == 1:
723 elif n == 1:
724 ps.add(cl.parentrevs(r)[0])
724 ps.add(cl.parentrevs(r)[0])
725 elif n == 2:
725 elif n == 2:
726 parents = cl.parentrevs(r)
726 parents = cl.parentrevs(r)
727 if len(parents) > 1:
727 if len(parents) > 1:
728 ps.add(parents[1])
728 ps.add(parents[1])
729 return [r for r in subset if r in ps]
729 return [r for r in subset if r in ps]
730
730
731 def present(repo, subset, x):
731 def present(repo, subset, x):
732 """``present(set)``
732 """``present(set)``
733 An empty set, if any revision in set isn't found; otherwise,
733 An empty set, if any revision in set isn't found; otherwise,
734 all revisions in set.
734 all revisions in set.
735 """
735 """
736 try:
736 try:
737 return getset(repo, subset, x)
737 return getset(repo, subset, x)
738 except error.RepoLookupError:
738 except error.RepoLookupError:
739 return []
739 return []
740
740
741 def public(repo, subset, x):
741 def public(repo, subset, x):
742 """``public()``
742 """``public()``
743 Changeset in public phase."""
743 Changeset in public phase."""
744 getargs(x, 0, 0, _("public takes no arguments"))
744 getargs(x, 0, 0, _("public takes no arguments"))
745 return [r for r in subset if repo._phaserev[r] == phases.public]
745 return [r for r in subset if repo._phaserev[r] == phases.public]
746
746
747 def remote(repo, subset, x):
747 def remote(repo, subset, x):
748 """``remote([id [,path]])``
748 """``remote([id [,path]])``
749 Local revision that corresponds to the given identifier in a
749 Local revision that corresponds to the given identifier in a
750 remote repository, if present. Here, the '.' identifier is a
750 remote repository, if present. Here, the '.' identifier is a
751 synonym for the current local branch.
751 synonym for the current local branch.
752 """
752 """
753
753
754 import hg # avoid start-up nasties
754 import hg # avoid start-up nasties
755 # i18n: "remote" is a keyword
755 # i18n: "remote" is a keyword
756 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
756 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
757
757
758 q = '.'
758 q = '.'
759 if len(l) > 0:
759 if len(l) > 0:
760 # i18n: "remote" is a keyword
760 # i18n: "remote" is a keyword
761 q = getstring(l[0], _("remote requires a string id"))
761 q = getstring(l[0], _("remote requires a string id"))
762 if q == '.':
762 if q == '.':
763 q = repo['.'].branch()
763 q = repo['.'].branch()
764
764
765 dest = ''
765 dest = ''
766 if len(l) > 1:
766 if len(l) > 1:
767 # i18n: "remote" is a keyword
767 # i18n: "remote" is a keyword
768 dest = getstring(l[1], _("remote requires a repository path"))
768 dest = getstring(l[1], _("remote requires a repository path"))
769 dest = repo.ui.expandpath(dest or 'default')
769 dest = repo.ui.expandpath(dest or 'default')
770 dest, branches = hg.parseurl(dest)
770 dest, branches = hg.parseurl(dest)
771 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
771 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
772 if revs:
772 if revs:
773 revs = [repo.lookup(rev) for rev in revs]
773 revs = [repo.lookup(rev) for rev in revs]
774 other = hg.peer(repo, {}, dest)
774 other = hg.peer(repo, {}, dest)
775 n = other.lookup(q)
775 n = other.lookup(q)
776 if n in repo:
776 if n in repo:
777 r = repo[n].rev()
777 r = repo[n].rev()
778 if r in subset:
778 if r in subset:
779 return [r]
779 return [r]
780 return []
780 return []
781
781
782 def removes(repo, subset, x):
782 def removes(repo, subset, x):
783 """``removes(pattern)``
783 """``removes(pattern)``
784 Changesets which remove files matching pattern.
784 Changesets which remove files matching pattern.
785 """
785 """
786 # i18n: "removes" is a keyword
786 # i18n: "removes" is a keyword
787 pat = getstring(x, _("removes requires a pattern"))
787 pat = getstring(x, _("removes requires a pattern"))
788 return checkstatus(repo, subset, pat, 2)
788 return checkstatus(repo, subset, pat, 2)
789
789
790 def rev(repo, subset, x):
790 def rev(repo, subset, x):
791 """``rev(number)``
791 """``rev(number)``
792 Revision with the given numeric identifier.
792 Revision with the given numeric identifier.
793 """
793 """
794 # i18n: "rev" is a keyword
794 # i18n: "rev" is a keyword
795 l = getargs(x, 1, 1, _("rev requires one argument"))
795 l = getargs(x, 1, 1, _("rev requires one argument"))
796 try:
796 try:
797 # i18n: "rev" is a keyword
797 # i18n: "rev" is a keyword
798 l = int(getstring(l[0], _("rev requires a number")))
798 l = int(getstring(l[0], _("rev requires a number")))
799 except (TypeError, ValueError):
799 except (TypeError, ValueError):
800 # i18n: "rev" is a keyword
800 # i18n: "rev" is a keyword
801 raise error.ParseError(_("rev expects a number"))
801 raise error.ParseError(_("rev expects a number"))
802 return [r for r in subset if r == l]
802 return [r for r in subset if r == l]
803
803
804 def reverse(repo, subset, x):
804 def reverse(repo, subset, x):
805 """``reverse(set)``
805 """``reverse(set)``
806 Reverse order of set.
806 Reverse order of set.
807 """
807 """
808 l = getset(repo, subset, x)
808 l = getset(repo, subset, x)
809 l.reverse()
809 l.reverse()
810 return l
810 return l
811
811
812 def roots(repo, subset, x):
812 def roots(repo, subset, x):
813 """``roots(set)``
813 """``roots(set)``
814 Changesets in set with no parent changeset in set.
814 Changesets in set with no parent changeset in set.
815 """
815 """
816 s = set(getset(repo, xrange(len(repo)), x))
816 s = set(getset(repo, xrange(len(repo)), x))
817 subset = [r for r in subset if r in s]
817 cs = _children(repo, subset, s)
818 cs = _children(repo, subset, s)
818 return [r for r in subset if r in s and r not in cs]
819 return [r for r in subset if r not in cs]
819
820
820 def secret(repo, subset, x):
821 def secret(repo, subset, x):
821 """``secret()``
822 """``secret()``
822 Changeset in secret phase."""
823 Changeset in secret phase."""
823 getargs(x, 0, 0, _("secret takes no arguments"))
824 getargs(x, 0, 0, _("secret takes no arguments"))
824 return [r for r in subset if repo._phaserev[r] == phases.secret]
825 return [r for r in subset if repo._phaserev[r] == phases.secret]
825
826
826 def sort(repo, subset, x):
827 def sort(repo, subset, x):
827 """``sort(set[, [-]key...])``
828 """``sort(set[, [-]key...])``
828 Sort set by keys. The default sort order is ascending, specify a key
829 Sort set by keys. The default sort order is ascending, specify a key
829 as ``-key`` to sort in descending order.
830 as ``-key`` to sort in descending order.
830
831
831 The keys can be:
832 The keys can be:
832
833
833 - ``rev`` for the revision number,
834 - ``rev`` for the revision number,
834 - ``branch`` for the branch name,
835 - ``branch`` for the branch name,
835 - ``desc`` for the commit message (description),
836 - ``desc`` for the commit message (description),
836 - ``user`` for user name (``author`` can be used as an alias),
837 - ``user`` for user name (``author`` can be used as an alias),
837 - ``date`` for the commit date
838 - ``date`` for the commit date
838 """
839 """
839 # i18n: "sort" is a keyword
840 # i18n: "sort" is a keyword
840 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
841 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
841 keys = "rev"
842 keys = "rev"
842 if len(l) == 2:
843 if len(l) == 2:
843 keys = getstring(l[1], _("sort spec must be a string"))
844 keys = getstring(l[1], _("sort spec must be a string"))
844
845
845 s = l[0]
846 s = l[0]
846 keys = keys.split()
847 keys = keys.split()
847 l = []
848 l = []
848 def invert(s):
849 def invert(s):
849 return "".join(chr(255 - ord(c)) for c in s)
850 return "".join(chr(255 - ord(c)) for c in s)
850 for r in getset(repo, subset, s):
851 for r in getset(repo, subset, s):
851 c = repo[r]
852 c = repo[r]
852 e = []
853 e = []
853 for k in keys:
854 for k in keys:
854 if k == 'rev':
855 if k == 'rev':
855 e.append(r)
856 e.append(r)
856 elif k == '-rev':
857 elif k == '-rev':
857 e.append(-r)
858 e.append(-r)
858 elif k == 'branch':
859 elif k == 'branch':
859 e.append(c.branch())
860 e.append(c.branch())
860 elif k == '-branch':
861 elif k == '-branch':
861 e.append(invert(c.branch()))
862 e.append(invert(c.branch()))
862 elif k == 'desc':
863 elif k == 'desc':
863 e.append(c.description())
864 e.append(c.description())
864 elif k == '-desc':
865 elif k == '-desc':
865 e.append(invert(c.description()))
866 e.append(invert(c.description()))
866 elif k in 'user author':
867 elif k in 'user author':
867 e.append(c.user())
868 e.append(c.user())
868 elif k in '-user -author':
869 elif k in '-user -author':
869 e.append(invert(c.user()))
870 e.append(invert(c.user()))
870 elif k == 'date':
871 elif k == 'date':
871 e.append(c.date()[0])
872 e.append(c.date()[0])
872 elif k == '-date':
873 elif k == '-date':
873 e.append(-c.date()[0])
874 e.append(-c.date()[0])
874 else:
875 else:
875 raise error.ParseError(_("unknown sort key %r") % k)
876 raise error.ParseError(_("unknown sort key %r") % k)
876 e.append(r)
877 e.append(r)
877 l.append(e)
878 l.append(e)
878 l.sort()
879 l.sort()
879 return [e[-1] for e in l]
880 return [e[-1] for e in l]
880
881
881 def tag(repo, subset, x):
882 def tag(repo, subset, x):
882 """``tag([name])``
883 """``tag([name])``
883 The specified tag by name, or all tagged revisions if no name is given.
884 The specified tag by name, or all tagged revisions if no name is given.
884 """
885 """
885 # i18n: "tag" is a keyword
886 # i18n: "tag" is a keyword
886 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
887 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
887 cl = repo.changelog
888 cl = repo.changelog
888 if args:
889 if args:
889 tn = getstring(args[0],
890 tn = getstring(args[0],
890 # i18n: "tag" is a keyword
891 # i18n: "tag" is a keyword
891 _('the argument to tag must be a string'))
892 _('the argument to tag must be a string'))
892 if not repo.tags().get(tn, None):
893 if not repo.tags().get(tn, None):
893 raise util.Abort(_("tag '%s' does not exist") % tn)
894 raise util.Abort(_("tag '%s' does not exist") % tn)
894 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
895 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
895 else:
896 else:
896 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
897 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
897 return [r for r in subset if r in s]
898 return [r for r in subset if r in s]
898
899
899 def tagged(repo, subset, x):
900 def tagged(repo, subset, x):
900 return tag(repo, subset, x)
901 return tag(repo, subset, x)
901
902
902 def user(repo, subset, x):
903 def user(repo, subset, x):
903 """``user(string)``
904 """``user(string)``
904 User name contains string. The match is case-insensitive.
905 User name contains string. The match is case-insensitive.
905 """
906 """
906 return author(repo, subset, x)
907 return author(repo, subset, x)
907
908
908 # for internal use
909 # for internal use
909 def _list(repo, subset, x):
910 def _list(repo, subset, x):
910 s = getstring(x, "internal error")
911 s = getstring(x, "internal error")
911 if not s:
912 if not s:
912 return []
913 return []
913 if not isinstance(subset, set):
914 if not isinstance(subset, set):
914 subset = set(subset)
915 subset = set(subset)
915 ls = [repo[r].rev() for r in s.split('\0')]
916 ls = [repo[r].rev() for r in s.split('\0')]
916 return [r for r in ls if r in subset]
917 return [r for r in ls if r in subset]
917
918
918 symbols = {
919 symbols = {
919 "adds": adds,
920 "adds": adds,
920 "all": getall,
921 "all": getall,
921 "ancestor": ancestor,
922 "ancestor": ancestor,
922 "ancestors": ancestors,
923 "ancestors": ancestors,
923 "author": author,
924 "author": author,
924 "bisect": bisect,
925 "bisect": bisect,
925 "bisected": bisected,
926 "bisected": bisected,
926 "bookmark": bookmark,
927 "bookmark": bookmark,
927 "branch": branch,
928 "branch": branch,
928 "children": children,
929 "children": children,
929 "closed": closed,
930 "closed": closed,
930 "contains": contains,
931 "contains": contains,
931 "date": date,
932 "date": date,
932 "desc": desc,
933 "desc": desc,
933 "descendants": descendants,
934 "descendants": descendants,
934 "draft": draft,
935 "draft": draft,
935 "file": hasfile,
936 "file": hasfile,
936 "filelog": filelog,
937 "filelog": filelog,
937 "first": first,
938 "first": first,
938 "follow": follow,
939 "follow": follow,
939 "grep": grep,
940 "grep": grep,
940 "head": head,
941 "head": head,
941 "heads": heads,
942 "heads": heads,
942 "id": node,
943 "id": node,
943 "keyword": keyword,
944 "keyword": keyword,
944 "last": last,
945 "last": last,
945 "limit": limit,
946 "limit": limit,
946 "max": maxrev,
947 "max": maxrev,
947 "merge": merge,
948 "merge": merge,
948 "min": minrev,
949 "min": minrev,
949 "modifies": modifies,
950 "modifies": modifies,
950 "outgoing": outgoing,
951 "outgoing": outgoing,
951 "p1": p1,
952 "p1": p1,
952 "p2": p2,
953 "p2": p2,
953 "parents": parents,
954 "parents": parents,
954 "present": present,
955 "present": present,
955 "public": public,
956 "public": public,
956 "remote": remote,
957 "remote": remote,
957 "removes": removes,
958 "removes": removes,
958 "rev": rev,
959 "rev": rev,
959 "reverse": reverse,
960 "reverse": reverse,
960 "roots": roots,
961 "roots": roots,
961 "sort": sort,
962 "sort": sort,
962 "secret": secret,
963 "secret": secret,
963 "tag": tag,
964 "tag": tag,
964 "tagged": tagged,
965 "tagged": tagged,
965 "user": user,
966 "user": user,
966 "_list": _list,
967 "_list": _list,
967 }
968 }
968
969
969 methods = {
970 methods = {
970 "range": rangeset,
971 "range": rangeset,
971 "string": stringset,
972 "string": stringset,
972 "symbol": symbolset,
973 "symbol": symbolset,
973 "and": andset,
974 "and": andset,
974 "or": orset,
975 "or": orset,
975 "not": notset,
976 "not": notset,
976 "list": listset,
977 "list": listset,
977 "func": func,
978 "func": func,
978 "ancestor": ancestorspec,
979 "ancestor": ancestorspec,
979 "parent": parentspec,
980 "parent": parentspec,
980 "parentpost": p1,
981 "parentpost": p1,
981 }
982 }
982
983
983 def optimize(x, small):
984 def optimize(x, small):
984 if x is None:
985 if x is None:
985 return 0, x
986 return 0, x
986
987
987 smallbonus = 1
988 smallbonus = 1
988 if small:
989 if small:
989 smallbonus = .5
990 smallbonus = .5
990
991
991 op = x[0]
992 op = x[0]
992 if op == 'minus':
993 if op == 'minus':
993 return optimize(('and', x[1], ('not', x[2])), small)
994 return optimize(('and', x[1], ('not', x[2])), small)
994 elif op == 'dagrange':
995 elif op == 'dagrange':
995 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
996 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
996 ('func', ('symbol', 'ancestors'), x[2])), small)
997 ('func', ('symbol', 'ancestors'), x[2])), small)
997 elif op == 'dagrangepre':
998 elif op == 'dagrangepre':
998 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
999 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
999 elif op == 'dagrangepost':
1000 elif op == 'dagrangepost':
1000 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1001 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1001 elif op == 'rangepre':
1002 elif op == 'rangepre':
1002 return optimize(('range', ('string', '0'), x[1]), small)
1003 return optimize(('range', ('string', '0'), x[1]), small)
1003 elif op == 'rangepost':
1004 elif op == 'rangepost':
1004 return optimize(('range', x[1], ('string', 'tip')), small)
1005 return optimize(('range', x[1], ('string', 'tip')), small)
1005 elif op == 'negate':
1006 elif op == 'negate':
1006 return optimize(('string',
1007 return optimize(('string',
1007 '-' + getstring(x[1], _("can't negate that"))), small)
1008 '-' + getstring(x[1], _("can't negate that"))), small)
1008 elif op in 'string symbol negate':
1009 elif op in 'string symbol negate':
1009 return smallbonus, x # single revisions are small
1010 return smallbonus, x # single revisions are small
1010 elif op == 'and' or op == 'dagrange':
1011 elif op == 'and' or op == 'dagrange':
1011 wa, ta = optimize(x[1], True)
1012 wa, ta = optimize(x[1], True)
1012 wb, tb = optimize(x[2], True)
1013 wb, tb = optimize(x[2], True)
1013 w = min(wa, wb)
1014 w = min(wa, wb)
1014 if wa > wb:
1015 if wa > wb:
1015 return w, (op, tb, ta)
1016 return w, (op, tb, ta)
1016 return w, (op, ta, tb)
1017 return w, (op, ta, tb)
1017 elif op == 'or':
1018 elif op == 'or':
1018 wa, ta = optimize(x[1], False)
1019 wa, ta = optimize(x[1], False)
1019 wb, tb = optimize(x[2], False)
1020 wb, tb = optimize(x[2], False)
1020 if wb < wa:
1021 if wb < wa:
1021 wb, wa = wa, wb
1022 wb, wa = wa, wb
1022 return max(wa, wb), (op, ta, tb)
1023 return max(wa, wb), (op, ta, tb)
1023 elif op == 'not':
1024 elif op == 'not':
1024 o = optimize(x[1], not small)
1025 o = optimize(x[1], not small)
1025 return o[0], (op, o[1])
1026 return o[0], (op, o[1])
1026 elif op == 'parentpost':
1027 elif op == 'parentpost':
1027 o = optimize(x[1], small)
1028 o = optimize(x[1], small)
1028 return o[0], (op, o[1])
1029 return o[0], (op, o[1])
1029 elif op == 'group':
1030 elif op == 'group':
1030 return optimize(x[1], small)
1031 return optimize(x[1], small)
1031 elif op in 'range list parent ancestorspec':
1032 elif op in 'range list parent ancestorspec':
1032 if op == 'parent':
1033 if op == 'parent':
1033 # x^:y means (x^) : y, not x ^ (:y)
1034 # x^:y means (x^) : y, not x ^ (:y)
1034 post = ('parentpost', x[1])
1035 post = ('parentpost', x[1])
1035 if x[2][0] == 'dagrangepre':
1036 if x[2][0] == 'dagrangepre':
1036 return optimize(('dagrange', post, x[2][1]), small)
1037 return optimize(('dagrange', post, x[2][1]), small)
1037 elif x[2][0] == 'rangepre':
1038 elif x[2][0] == 'rangepre':
1038 return optimize(('range', post, x[2][1]), small)
1039 return optimize(('range', post, x[2][1]), small)
1039
1040
1040 wa, ta = optimize(x[1], small)
1041 wa, ta = optimize(x[1], small)
1041 wb, tb = optimize(x[2], small)
1042 wb, tb = optimize(x[2], small)
1042 return wa + wb, (op, ta, tb)
1043 return wa + wb, (op, ta, tb)
1043 elif op == 'func':
1044 elif op == 'func':
1044 f = getstring(x[1], _("not a symbol"))
1045 f = getstring(x[1], _("not a symbol"))
1045 wa, ta = optimize(x[2], small)
1046 wa, ta = optimize(x[2], small)
1046 if f in ("author branch closed date desc file grep keyword "
1047 if f in ("author branch closed date desc file grep keyword "
1047 "outgoing user"):
1048 "outgoing user"):
1048 w = 10 # slow
1049 w = 10 # slow
1049 elif f in "modifies adds removes":
1050 elif f in "modifies adds removes":
1050 w = 30 # slower
1051 w = 30 # slower
1051 elif f == "contains":
1052 elif f == "contains":
1052 w = 100 # very slow
1053 w = 100 # very slow
1053 elif f == "ancestor":
1054 elif f == "ancestor":
1054 w = 1 * smallbonus
1055 w = 1 * smallbonus
1055 elif f in "reverse limit first":
1056 elif f in "reverse limit first":
1056 w = 0
1057 w = 0
1057 elif f in "sort":
1058 elif f in "sort":
1058 w = 10 # assume most sorts look at changelog
1059 w = 10 # assume most sorts look at changelog
1059 else:
1060 else:
1060 w = 1
1061 w = 1
1061 return w + wa, (op, x[1], ta)
1062 return w + wa, (op, x[1], ta)
1062 return 1, x
1063 return 1, x
1063
1064
1064 class revsetalias(object):
1065 class revsetalias(object):
1065 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1066 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1066 args = None
1067 args = None
1067
1068
1068 def __init__(self, name, value):
1069 def __init__(self, name, value):
1069 '''Aliases like:
1070 '''Aliases like:
1070
1071
1071 h = heads(default)
1072 h = heads(default)
1072 b($1) = ancestors($1) - ancestors(default)
1073 b($1) = ancestors($1) - ancestors(default)
1073 '''
1074 '''
1074 if isinstance(name, tuple): # parameter substitution
1075 if isinstance(name, tuple): # parameter substitution
1075 self.tree = name
1076 self.tree = name
1076 self.replacement = value
1077 self.replacement = value
1077 else: # alias definition
1078 else: # alias definition
1078 m = self.funcre.search(name)
1079 m = self.funcre.search(name)
1079 if m:
1080 if m:
1080 self.tree = ('func', ('symbol', m.group(1)))
1081 self.tree = ('func', ('symbol', m.group(1)))
1081 self.args = [x.strip() for x in m.group(2).split(',')]
1082 self.args = [x.strip() for x in m.group(2).split(',')]
1082 for arg in self.args:
1083 for arg in self.args:
1083 value = value.replace(arg, repr(arg))
1084 value = value.replace(arg, repr(arg))
1084 else:
1085 else:
1085 self.tree = ('symbol', name)
1086 self.tree = ('symbol', name)
1086
1087
1087 self.replacement, pos = parse(value)
1088 self.replacement, pos = parse(value)
1088 if pos != len(value):
1089 if pos != len(value):
1089 raise error.ParseError(_('invalid token'), pos)
1090 raise error.ParseError(_('invalid token'), pos)
1090
1091
1091 def process(self, tree):
1092 def process(self, tree):
1092 if isinstance(tree, tuple):
1093 if isinstance(tree, tuple):
1093 if self.args is None:
1094 if self.args is None:
1094 if tree == self.tree:
1095 if tree == self.tree:
1095 return self.replacement
1096 return self.replacement
1096 elif tree[:2] == self.tree:
1097 elif tree[:2] == self.tree:
1097 l = getlist(tree[2])
1098 l = getlist(tree[2])
1098 if len(l) != len(self.args):
1099 if len(l) != len(self.args):
1099 raise error.ParseError(
1100 raise error.ParseError(
1100 _('invalid number of arguments: %s') % len(l))
1101 _('invalid number of arguments: %s') % len(l))
1101 result = self.replacement
1102 result = self.replacement
1102 for a, v in zip(self.args, l):
1103 for a, v in zip(self.args, l):
1103 valalias = revsetalias(('string', a), v)
1104 valalias = revsetalias(('string', a), v)
1104 result = valalias.process(result)
1105 result = valalias.process(result)
1105 return result
1106 return result
1106 return tuple(map(self.process, tree))
1107 return tuple(map(self.process, tree))
1107 return tree
1108 return tree
1108
1109
1109 def findaliases(ui, tree):
1110 def findaliases(ui, tree):
1110 for k, v in ui.configitems('revsetalias'):
1111 for k, v in ui.configitems('revsetalias'):
1111 alias = revsetalias(k, v)
1112 alias = revsetalias(k, v)
1112 tree = alias.process(tree)
1113 tree = alias.process(tree)
1113 return tree
1114 return tree
1114
1115
1115 parse = parser.parser(tokenize, elements).parse
1116 parse = parser.parser(tokenize, elements).parse
1116
1117
1117 def match(ui, spec):
1118 def match(ui, spec):
1118 if not spec:
1119 if not spec:
1119 raise error.ParseError(_("empty query"))
1120 raise error.ParseError(_("empty query"))
1120 tree, pos = parse(spec)
1121 tree, pos = parse(spec)
1121 if (pos != len(spec)):
1122 if (pos != len(spec)):
1122 raise error.ParseError(_("invalid token"), pos)
1123 raise error.ParseError(_("invalid token"), pos)
1123 if ui:
1124 if ui:
1124 tree = findaliases(ui, tree)
1125 tree = findaliases(ui, tree)
1125 weight, tree = optimize(tree, True)
1126 weight, tree = optimize(tree, True)
1126 def mfunc(repo, subset):
1127 def mfunc(repo, subset):
1127 return getset(repo, subset, tree)
1128 return getset(repo, subset, tree)
1128 return mfunc
1129 return mfunc
1129
1130
1130 def formatspec(expr, *args):
1131 def formatspec(expr, *args):
1131 '''
1132 '''
1132 This is a convenience function for using revsets internally, and
1133 This is a convenience function for using revsets internally, and
1133 escapes arguments appropriately. Aliases are intentionally ignored
1134 escapes arguments appropriately. Aliases are intentionally ignored
1134 so that intended expression behavior isn't accidentally subverted.
1135 so that intended expression behavior isn't accidentally subverted.
1135
1136
1136 Supported arguments:
1137 Supported arguments:
1137
1138
1138 %r = revset expression, parenthesized
1139 %r = revset expression, parenthesized
1139 %d = int(arg), no quoting
1140 %d = int(arg), no quoting
1140 %s = string(arg), escaped and single-quoted
1141 %s = string(arg), escaped and single-quoted
1141 %b = arg.branch(), escaped and single-quoted
1142 %b = arg.branch(), escaped and single-quoted
1142 %n = hex(arg), single-quoted
1143 %n = hex(arg), single-quoted
1143 %% = a literal '%'
1144 %% = a literal '%'
1144
1145
1145 Prefixing the type with 'l' specifies a parenthesized list of that type.
1146 Prefixing the type with 'l' specifies a parenthesized list of that type.
1146
1147
1147 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1148 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1148 '(10 or 11):: and ((this()) or (that()))'
1149 '(10 or 11):: and ((this()) or (that()))'
1149 >>> formatspec('%d:: and not %d::', 10, 20)
1150 >>> formatspec('%d:: and not %d::', 10, 20)
1150 '10:: and not 20::'
1151 '10:: and not 20::'
1151 >>> formatspec('%ld or %ld', [], [1])
1152 >>> formatspec('%ld or %ld', [], [1])
1152 "_list('') or 1"
1153 "_list('') or 1"
1153 >>> formatspec('keyword(%s)', 'foo\\xe9')
1154 >>> formatspec('keyword(%s)', 'foo\\xe9')
1154 "keyword('foo\\\\xe9')"
1155 "keyword('foo\\\\xe9')"
1155 >>> b = lambda: 'default'
1156 >>> b = lambda: 'default'
1156 >>> b.branch = b
1157 >>> b.branch = b
1157 >>> formatspec('branch(%b)', b)
1158 >>> formatspec('branch(%b)', b)
1158 "branch('default')"
1159 "branch('default')"
1159 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1160 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1160 "root(_list('a\\x00b\\x00c\\x00d'))"
1161 "root(_list('a\\x00b\\x00c\\x00d'))"
1161 '''
1162 '''
1162
1163
1163 def quote(s):
1164 def quote(s):
1164 return repr(str(s))
1165 return repr(str(s))
1165
1166
1166 def argtype(c, arg):
1167 def argtype(c, arg):
1167 if c == 'd':
1168 if c == 'd':
1168 return str(int(arg))
1169 return str(int(arg))
1169 elif c == 's':
1170 elif c == 's':
1170 return quote(arg)
1171 return quote(arg)
1171 elif c == 'r':
1172 elif c == 'r':
1172 parse(arg) # make sure syntax errors are confined
1173 parse(arg) # make sure syntax errors are confined
1173 return '(%s)' % arg
1174 return '(%s)' % arg
1174 elif c == 'n':
1175 elif c == 'n':
1175 return quote(nodemod.hex(arg))
1176 return quote(nodemod.hex(arg))
1176 elif c == 'b':
1177 elif c == 'b':
1177 return quote(arg.branch())
1178 return quote(arg.branch())
1178
1179
1179 def listexp(s, t):
1180 def listexp(s, t):
1180 l = len(s)
1181 l = len(s)
1181 if l == 0:
1182 if l == 0:
1182 return "_list('')"
1183 return "_list('')"
1183 elif l == 1:
1184 elif l == 1:
1184 return argtype(t, s[0])
1185 return argtype(t, s[0])
1185 elif t == 'd':
1186 elif t == 'd':
1186 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1187 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1187 elif t == 's':
1188 elif t == 's':
1188 return "_list('%s')" % "\0".join(s)
1189 return "_list('%s')" % "\0".join(s)
1189 elif t == 'n':
1190 elif t == 'n':
1190 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1191 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1191 elif t == 'b':
1192 elif t == 'b':
1192 return "_list('%s')" % "\0".join(a.branch() for a in s)
1193 return "_list('%s')" % "\0".join(a.branch() for a in s)
1193
1194
1194 m = l // 2
1195 m = l // 2
1195 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1196 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1196
1197
1197 ret = ''
1198 ret = ''
1198 pos = 0
1199 pos = 0
1199 arg = 0
1200 arg = 0
1200 while pos < len(expr):
1201 while pos < len(expr):
1201 c = expr[pos]
1202 c = expr[pos]
1202 if c == '%':
1203 if c == '%':
1203 pos += 1
1204 pos += 1
1204 d = expr[pos]
1205 d = expr[pos]
1205 if d == '%':
1206 if d == '%':
1206 ret += d
1207 ret += d
1207 elif d in 'dsnbr':
1208 elif d in 'dsnbr':
1208 ret += argtype(d, args[arg])
1209 ret += argtype(d, args[arg])
1209 arg += 1
1210 arg += 1
1210 elif d == 'l':
1211 elif d == 'l':
1211 # a list of some type
1212 # a list of some type
1212 pos += 1
1213 pos += 1
1213 d = expr[pos]
1214 d = expr[pos]
1214 ret += listexp(list(args[arg]), d)
1215 ret += listexp(list(args[arg]), d)
1215 arg += 1
1216 arg += 1
1216 else:
1217 else:
1217 raise util.Abort('unexpected revspec format character %s' % d)
1218 raise util.Abort('unexpected revspec format character %s' % d)
1218 else:
1219 else:
1219 ret += c
1220 ret += c
1220 pos += 1
1221 pos += 1
1221
1222
1222 return ret
1223 return ret
1223
1224
1224 # tell hggettext to extract docstrings from these functions:
1225 # tell hggettext to extract docstrings from these functions:
1225 i18nfunctions = symbols.values()
1226 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now