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