##// END OF EJS Templates
merge with stable
Augie Fackler -
r20209:174d9b8b merge default
parent child Browse files
Show More
@@ -1,505 +1,507 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 re
8 import re
9 import parser, error, util, merge
9 import parser, error, util, merge
10 from i18n import _
10 from i18n import _
11
11
12 elements = {
12 elements = {
13 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
13 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
14 "-": (5, ("negate", 19), ("minus", 5)),
14 "-": (5, ("negate", 19), ("minus", 5)),
15 "not": (10, ("not", 10)),
15 "not": (10, ("not", 10)),
16 "!": (10, ("not", 10)),
16 "!": (10, ("not", 10)),
17 "and": (5, None, ("and", 5)),
17 "and": (5, None, ("and", 5)),
18 "&": (5, None, ("and", 5)),
18 "&": (5, None, ("and", 5)),
19 "or": (4, None, ("or", 4)),
19 "or": (4, None, ("or", 4)),
20 "|": (4, None, ("or", 4)),
20 "|": (4, None, ("or", 4)),
21 "+": (4, None, ("or", 4)),
21 "+": (4, None, ("or", 4)),
22 ",": (2, None, ("list", 2)),
22 ",": (2, None, ("list", 2)),
23 ")": (0, None, None),
23 ")": (0, None, None),
24 "symbol": (0, ("symbol",), None),
24 "symbol": (0, ("symbol",), None),
25 "string": (0, ("string",), None),
25 "string": (0, ("string",), None),
26 "end": (0, None, None),
26 "end": (0, None, None),
27 }
27 }
28
28
29 keywords = set(['and', 'or', 'not'])
29 keywords = set(['and', 'or', 'not'])
30
30
31 globchars = ".*{}[]?/\\_"
31 globchars = ".*{}[]?/\\_"
32
32
33 def tokenize(program):
33 def tokenize(program):
34 pos, l = 0, len(program)
34 pos, l = 0, len(program)
35 while pos < l:
35 while pos < l:
36 c = program[pos]
36 c = program[pos]
37 if c.isspace(): # skip inter-token whitespace
37 if c.isspace(): # skip inter-token whitespace
38 pass
38 pass
39 elif c in "(),-|&+!": # handle simple operators
39 elif c in "(),-|&+!": # handle simple operators
40 yield (c, None, pos)
40 yield (c, None, pos)
41 elif (c in '"\'' or c == 'r' and
41 elif (c in '"\'' or c == 'r' and
42 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
42 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
43 if c == 'r':
43 if c == 'r':
44 pos += 1
44 pos += 1
45 c = program[pos]
45 c = program[pos]
46 decode = lambda x: x
46 decode = lambda x: x
47 else:
47 else:
48 decode = lambda x: x.decode('string-escape')
48 decode = lambda x: x.decode('string-escape')
49 pos += 1
49 pos += 1
50 s = pos
50 s = pos
51 while pos < l: # find closing quote
51 while pos < l: # find closing quote
52 d = program[pos]
52 d = program[pos]
53 if d == '\\': # skip over escaped characters
53 if d == '\\': # skip over escaped characters
54 pos += 2
54 pos += 2
55 continue
55 continue
56 if d == c:
56 if d == c:
57 yield ('string', decode(program[s:pos]), s)
57 yield ('string', decode(program[s:pos]), s)
58 break
58 break
59 pos += 1
59 pos += 1
60 else:
60 else:
61 raise error.ParseError(_("unterminated string"), s)
61 raise error.ParseError(_("unterminated string"), s)
62 elif c.isalnum() or c in globchars or ord(c) > 127:
62 elif c.isalnum() or c in globchars or ord(c) > 127:
63 # gather up a symbol/keyword
63 # gather up a symbol/keyword
64 s = pos
64 s = pos
65 pos += 1
65 pos += 1
66 while pos < l: # find end of symbol
66 while pos < l: # find end of symbol
67 d = program[pos]
67 d = program[pos]
68 if not (d.isalnum() or d in globchars or ord(d) > 127):
68 if not (d.isalnum() or d in globchars or ord(d) > 127):
69 break
69 break
70 pos += 1
70 pos += 1
71 sym = program[s:pos]
71 sym = program[s:pos]
72 if sym in keywords: # operator keywords
72 if sym in keywords: # operator keywords
73 yield (sym, None, s)
73 yield (sym, None, s)
74 else:
74 else:
75 yield ('symbol', sym, s)
75 yield ('symbol', sym, s)
76 pos -= 1
76 pos -= 1
77 else:
77 else:
78 raise error.ParseError(_("syntax error"), pos)
78 raise error.ParseError(_("syntax error"), pos)
79 pos += 1
79 pos += 1
80 yield ('end', None, pos)
80 yield ('end', None, pos)
81
81
82 parse = parser.parser(tokenize, elements).parse
82 def parse(expr):
83 p = parser.parser(tokenize, elements)
84 return p.parse(expr)
83
85
84 def getstring(x, err):
86 def getstring(x, err):
85 if x and (x[0] == 'string' or x[0] == 'symbol'):
87 if x and (x[0] == 'string' or x[0] == 'symbol'):
86 return x[1]
88 return x[1]
87 raise error.ParseError(err)
89 raise error.ParseError(err)
88
90
89 def getset(mctx, x):
91 def getset(mctx, x):
90 if not x:
92 if not x:
91 raise error.ParseError(_("missing argument"))
93 raise error.ParseError(_("missing argument"))
92 return methods[x[0]](mctx, *x[1:])
94 return methods[x[0]](mctx, *x[1:])
93
95
94 def stringset(mctx, x):
96 def stringset(mctx, x):
95 m = mctx.matcher([x])
97 m = mctx.matcher([x])
96 return [f for f in mctx.subset if m(f)]
98 return [f for f in mctx.subset if m(f)]
97
99
98 def andset(mctx, x, y):
100 def andset(mctx, x, y):
99 return getset(mctx.narrow(getset(mctx, x)), y)
101 return getset(mctx.narrow(getset(mctx, x)), y)
100
102
101 def orset(mctx, x, y):
103 def orset(mctx, x, y):
102 # needs optimizing
104 # needs optimizing
103 xl = getset(mctx, x)
105 xl = getset(mctx, x)
104 yl = getset(mctx, y)
106 yl = getset(mctx, y)
105 return xl + [f for f in yl if f not in xl]
107 return xl + [f for f in yl if f not in xl]
106
108
107 def notset(mctx, x):
109 def notset(mctx, x):
108 s = set(getset(mctx, x))
110 s = set(getset(mctx, x))
109 return [r for r in mctx.subset if r not in s]
111 return [r for r in mctx.subset if r not in s]
110
112
111 def minusset(mctx, x, y):
113 def minusset(mctx, x, y):
112 xl = getset(mctx, x)
114 xl = getset(mctx, x)
113 yl = set(getset(mctx, y))
115 yl = set(getset(mctx, y))
114 return [f for f in xl if f not in yl]
116 return [f for f in xl if f not in yl]
115
117
116 def listset(mctx, a, b):
118 def listset(mctx, a, b):
117 raise error.ParseError(_("can't use a list in this context"))
119 raise error.ParseError(_("can't use a list in this context"))
118
120
119 def modified(mctx, x):
121 def modified(mctx, x):
120 """``modified()``
122 """``modified()``
121 File that is modified according to status.
123 File that is modified according to status.
122 """
124 """
123 # i18n: "modified" is a keyword
125 # i18n: "modified" is a keyword
124 getargs(x, 0, 0, _("modified takes no arguments"))
126 getargs(x, 0, 0, _("modified takes no arguments"))
125 s = mctx.status()[0]
127 s = mctx.status()[0]
126 return [f for f in mctx.subset if f in s]
128 return [f for f in mctx.subset if f in s]
127
129
128 def added(mctx, x):
130 def added(mctx, x):
129 """``added()``
131 """``added()``
130 File that is added according to status.
132 File that is added according to status.
131 """
133 """
132 # i18n: "added" is a keyword
134 # i18n: "added" is a keyword
133 getargs(x, 0, 0, _("added takes no arguments"))
135 getargs(x, 0, 0, _("added takes no arguments"))
134 s = mctx.status()[1]
136 s = mctx.status()[1]
135 return [f for f in mctx.subset if f in s]
137 return [f for f in mctx.subset if f in s]
136
138
137 def removed(mctx, x):
139 def removed(mctx, x):
138 """``removed()``
140 """``removed()``
139 File that is removed according to status.
141 File that is removed according to status.
140 """
142 """
141 # i18n: "removed" is a keyword
143 # i18n: "removed" is a keyword
142 getargs(x, 0, 0, _("removed takes no arguments"))
144 getargs(x, 0, 0, _("removed takes no arguments"))
143 s = mctx.status()[2]
145 s = mctx.status()[2]
144 return [f for f in mctx.subset if f in s]
146 return [f for f in mctx.subset if f in s]
145
147
146 def deleted(mctx, x):
148 def deleted(mctx, x):
147 """``deleted()``
149 """``deleted()``
148 File that is deleted according to status.
150 File that is deleted according to status.
149 """
151 """
150 # i18n: "deleted" is a keyword
152 # i18n: "deleted" is a keyword
151 getargs(x, 0, 0, _("deleted takes no arguments"))
153 getargs(x, 0, 0, _("deleted takes no arguments"))
152 s = mctx.status()[3]
154 s = mctx.status()[3]
153 return [f for f in mctx.subset if f in s]
155 return [f for f in mctx.subset if f in s]
154
156
155 def unknown(mctx, x):
157 def unknown(mctx, x):
156 """``unknown()``
158 """``unknown()``
157 File that is unknown according to status. These files will only be
159 File that is unknown according to status. These files will only be
158 considered if this predicate is used.
160 considered if this predicate is used.
159 """
161 """
160 # i18n: "unknown" is a keyword
162 # i18n: "unknown" is a keyword
161 getargs(x, 0, 0, _("unknown takes no arguments"))
163 getargs(x, 0, 0, _("unknown takes no arguments"))
162 s = mctx.status()[4]
164 s = mctx.status()[4]
163 return [f for f in mctx.subset if f in s]
165 return [f for f in mctx.subset if f in s]
164
166
165 def ignored(mctx, x):
167 def ignored(mctx, x):
166 """``ignored()``
168 """``ignored()``
167 File that is ignored according to status. These files will only be
169 File that is ignored according to status. These files will only be
168 considered if this predicate is used.
170 considered if this predicate is used.
169 """
171 """
170 # i18n: "ignored" is a keyword
172 # i18n: "ignored" is a keyword
171 getargs(x, 0, 0, _("ignored takes no arguments"))
173 getargs(x, 0, 0, _("ignored takes no arguments"))
172 s = mctx.status()[5]
174 s = mctx.status()[5]
173 return [f for f in mctx.subset if f in s]
175 return [f for f in mctx.subset if f in s]
174
176
175 def clean(mctx, x):
177 def clean(mctx, x):
176 """``clean()``
178 """``clean()``
177 File that is clean according to status.
179 File that is clean according to status.
178 """
180 """
179 # i18n: "clean" is a keyword
181 # i18n: "clean" is a keyword
180 getargs(x, 0, 0, _("clean takes no arguments"))
182 getargs(x, 0, 0, _("clean takes no arguments"))
181 s = mctx.status()[6]
183 s = mctx.status()[6]
182 return [f for f in mctx.subset if f in s]
184 return [f for f in mctx.subset if f in s]
183
185
184 def func(mctx, a, b):
186 def func(mctx, a, b):
185 if a[0] == 'symbol' and a[1] in symbols:
187 if a[0] == 'symbol' and a[1] in symbols:
186 return symbols[a[1]](mctx, b)
188 return symbols[a[1]](mctx, b)
187 raise error.ParseError(_("not a function: %s") % a[1])
189 raise error.ParseError(_("not a function: %s") % a[1])
188
190
189 def getlist(x):
191 def getlist(x):
190 if not x:
192 if not x:
191 return []
193 return []
192 if x[0] == 'list':
194 if x[0] == 'list':
193 return getlist(x[1]) + [x[2]]
195 return getlist(x[1]) + [x[2]]
194 return [x]
196 return [x]
195
197
196 def getargs(x, min, max, err):
198 def getargs(x, min, max, err):
197 l = getlist(x)
199 l = getlist(x)
198 if len(l) < min or len(l) > max:
200 if len(l) < min or len(l) > max:
199 raise error.ParseError(err)
201 raise error.ParseError(err)
200 return l
202 return l
201
203
202 def binary(mctx, x):
204 def binary(mctx, x):
203 """``binary()``
205 """``binary()``
204 File that appears to be binary (contains NUL bytes).
206 File that appears to be binary (contains NUL bytes).
205 """
207 """
206 # i18n: "binary" is a keyword
208 # i18n: "binary" is a keyword
207 getargs(x, 0, 0, _("binary takes no arguments"))
209 getargs(x, 0, 0, _("binary takes no arguments"))
208 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
210 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
209
211
210 def exec_(mctx, x):
212 def exec_(mctx, x):
211 """``exec()``
213 """``exec()``
212 File that is marked as executable.
214 File that is marked as executable.
213 """
215 """
214 # i18n: "exec" is a keyword
216 # i18n: "exec" is a keyword
215 getargs(x, 0, 0, _("exec takes no arguments"))
217 getargs(x, 0, 0, _("exec takes no arguments"))
216 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
218 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
217
219
218 def symlink(mctx, x):
220 def symlink(mctx, x):
219 """``symlink()``
221 """``symlink()``
220 File that is marked as a symlink.
222 File that is marked as a symlink.
221 """
223 """
222 # i18n: "symlink" is a keyword
224 # i18n: "symlink" is a keyword
223 getargs(x, 0, 0, _("symlink takes no arguments"))
225 getargs(x, 0, 0, _("symlink takes no arguments"))
224 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
226 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
225
227
226 def resolved(mctx, x):
228 def resolved(mctx, x):
227 """``resolved()``
229 """``resolved()``
228 File that is marked resolved according to the resolve state.
230 File that is marked resolved according to the resolve state.
229 """
231 """
230 # i18n: "resolved" is a keyword
232 # i18n: "resolved" is a keyword
231 getargs(x, 0, 0, _("resolved takes no arguments"))
233 getargs(x, 0, 0, _("resolved takes no arguments"))
232 if mctx.ctx.rev() is not None:
234 if mctx.ctx.rev() is not None:
233 return []
235 return []
234 ms = merge.mergestate(mctx.ctx._repo)
236 ms = merge.mergestate(mctx.ctx._repo)
235 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
237 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
236
238
237 def unresolved(mctx, x):
239 def unresolved(mctx, x):
238 """``unresolved()``
240 """``unresolved()``
239 File that is marked unresolved according to the resolve state.
241 File that is marked unresolved according to the resolve state.
240 """
242 """
241 # i18n: "unresolved" is a keyword
243 # i18n: "unresolved" is a keyword
242 getargs(x, 0, 0, _("unresolved takes no arguments"))
244 getargs(x, 0, 0, _("unresolved takes no arguments"))
243 if mctx.ctx.rev() is not None:
245 if mctx.ctx.rev() is not None:
244 return []
246 return []
245 ms = merge.mergestate(mctx.ctx._repo)
247 ms = merge.mergestate(mctx.ctx._repo)
246 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
248 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
247
249
248 def hgignore(mctx, x):
250 def hgignore(mctx, x):
249 """``hgignore()``
251 """``hgignore()``
250 File that matches the active .hgignore pattern.
252 File that matches the active .hgignore pattern.
251 """
253 """
252 getargs(x, 0, 0, _("hgignore takes no arguments"))
254 getargs(x, 0, 0, _("hgignore takes no arguments"))
253 ignore = mctx.ctx._repo.dirstate._ignore
255 ignore = mctx.ctx._repo.dirstate._ignore
254 return [f for f in mctx.subset if ignore(f)]
256 return [f for f in mctx.subset if ignore(f)]
255
257
256 def grep(mctx, x):
258 def grep(mctx, x):
257 """``grep(regex)``
259 """``grep(regex)``
258 File contains the given regular expression.
260 File contains the given regular expression.
259 """
261 """
260 try:
262 try:
261 # i18n: "grep" is a keyword
263 # i18n: "grep" is a keyword
262 r = re.compile(getstring(x, _("grep requires a pattern")))
264 r = re.compile(getstring(x, _("grep requires a pattern")))
263 except re.error, e:
265 except re.error, e:
264 raise error.ParseError(_('invalid match pattern: %s') % e)
266 raise error.ParseError(_('invalid match pattern: %s') % e)
265 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
267 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
266
268
267 def _sizetomax(s):
269 def _sizetomax(s):
268 try:
270 try:
269 s = s.strip()
271 s = s.strip()
270 for k, v in util._sizeunits:
272 for k, v in util._sizeunits:
271 if s.endswith(k):
273 if s.endswith(k):
272 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
274 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
273 n = s[:-len(k)]
275 n = s[:-len(k)]
274 inc = 1.0
276 inc = 1.0
275 if "." in n:
277 if "." in n:
276 inc /= 10 ** len(n.split(".")[1])
278 inc /= 10 ** len(n.split(".")[1])
277 return int((float(n) + inc) * v) - 1
279 return int((float(n) + inc) * v) - 1
278 # no extension, this is a precise value
280 # no extension, this is a precise value
279 return int(s)
281 return int(s)
280 except ValueError:
282 except ValueError:
281 raise error.ParseError(_("couldn't parse size: %s") % s)
283 raise error.ParseError(_("couldn't parse size: %s") % s)
282
284
283 def size(mctx, x):
285 def size(mctx, x):
284 """``size(expression)``
286 """``size(expression)``
285 File size matches the given expression. Examples:
287 File size matches the given expression. Examples:
286
288
287 - 1k (files from 1024 to 2047 bytes)
289 - 1k (files from 1024 to 2047 bytes)
288 - < 20k (files less than 20480 bytes)
290 - < 20k (files less than 20480 bytes)
289 - >= .5MB (files at least 524288 bytes)
291 - >= .5MB (files at least 524288 bytes)
290 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
292 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
291 """
293 """
292
294
293 # i18n: "size" is a keyword
295 # i18n: "size" is a keyword
294 expr = getstring(x, _("size requires an expression")).strip()
296 expr = getstring(x, _("size requires an expression")).strip()
295 if '-' in expr: # do we have a range?
297 if '-' in expr: # do we have a range?
296 a, b = expr.split('-', 1)
298 a, b = expr.split('-', 1)
297 a = util.sizetoint(a)
299 a = util.sizetoint(a)
298 b = util.sizetoint(b)
300 b = util.sizetoint(b)
299 m = lambda x: x >= a and x <= b
301 m = lambda x: x >= a and x <= b
300 elif expr.startswith("<="):
302 elif expr.startswith("<="):
301 a = util.sizetoint(expr[2:])
303 a = util.sizetoint(expr[2:])
302 m = lambda x: x <= a
304 m = lambda x: x <= a
303 elif expr.startswith("<"):
305 elif expr.startswith("<"):
304 a = util.sizetoint(expr[1:])
306 a = util.sizetoint(expr[1:])
305 m = lambda x: x < a
307 m = lambda x: x < a
306 elif expr.startswith(">="):
308 elif expr.startswith(">="):
307 a = util.sizetoint(expr[2:])
309 a = util.sizetoint(expr[2:])
308 m = lambda x: x >= a
310 m = lambda x: x >= a
309 elif expr.startswith(">"):
311 elif expr.startswith(">"):
310 a = util.sizetoint(expr[1:])
312 a = util.sizetoint(expr[1:])
311 m = lambda x: x > a
313 m = lambda x: x > a
312 elif expr[0].isdigit or expr[0] == '.':
314 elif expr[0].isdigit or expr[0] == '.':
313 a = util.sizetoint(expr)
315 a = util.sizetoint(expr)
314 b = _sizetomax(expr)
316 b = _sizetomax(expr)
315 m = lambda x: x >= a and x <= b
317 m = lambda x: x >= a and x <= b
316 else:
318 else:
317 raise error.ParseError(_("couldn't parse size: %s") % expr)
319 raise error.ParseError(_("couldn't parse size: %s") % expr)
318
320
319 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
321 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
320
322
321 def encoding(mctx, x):
323 def encoding(mctx, x):
322 """``encoding(name)``
324 """``encoding(name)``
323 File can be successfully decoded with the given character
325 File can be successfully decoded with the given character
324 encoding. May not be useful for encodings other than ASCII and
326 encoding. May not be useful for encodings other than ASCII and
325 UTF-8.
327 UTF-8.
326 """
328 """
327
329
328 # i18n: "encoding" is a keyword
330 # i18n: "encoding" is a keyword
329 enc = getstring(x, _("encoding requires an encoding name"))
331 enc = getstring(x, _("encoding requires an encoding name"))
330
332
331 s = []
333 s = []
332 for f in mctx.existing():
334 for f in mctx.existing():
333 d = mctx.ctx[f].data()
335 d = mctx.ctx[f].data()
334 try:
336 try:
335 d.decode(enc)
337 d.decode(enc)
336 except LookupError:
338 except LookupError:
337 raise util.Abort(_("unknown encoding '%s'") % enc)
339 raise util.Abort(_("unknown encoding '%s'") % enc)
338 except UnicodeDecodeError:
340 except UnicodeDecodeError:
339 continue
341 continue
340 s.append(f)
342 s.append(f)
341
343
342 return s
344 return s
343
345
344 def eol(mctx, x):
346 def eol(mctx, x):
345 """``eol(style)``
347 """``eol(style)``
346 File contains newlines of the given style (dos, unix, mac). Binary
348 File contains newlines of the given style (dos, unix, mac). Binary
347 files are excluded, files with mixed line endings match multiple
349 files are excluded, files with mixed line endings match multiple
348 styles.
350 styles.
349 """
351 """
350
352
351 # i18n: "encoding" is a keyword
353 # i18n: "encoding" is a keyword
352 enc = getstring(x, _("encoding requires an encoding name"))
354 enc = getstring(x, _("encoding requires an encoding name"))
353
355
354 s = []
356 s = []
355 for f in mctx.existing():
357 for f in mctx.existing():
356 d = mctx.ctx[f].data()
358 d = mctx.ctx[f].data()
357 if util.binary(d):
359 if util.binary(d):
358 continue
360 continue
359 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
361 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
360 s.append(f)
362 s.append(f)
361 elif enc == 'unix' and re.search('(?<!\r)\n', d):
363 elif enc == 'unix' and re.search('(?<!\r)\n', d):
362 s.append(f)
364 s.append(f)
363 elif enc == 'mac' and re.search('\r(?!\n)', d):
365 elif enc == 'mac' and re.search('\r(?!\n)', d):
364 s.append(f)
366 s.append(f)
365 return s
367 return s
366
368
367 def copied(mctx, x):
369 def copied(mctx, x):
368 """``copied()``
370 """``copied()``
369 File that is recorded as being copied.
371 File that is recorded as being copied.
370 """
372 """
371 # i18n: "copied" is a keyword
373 # i18n: "copied" is a keyword
372 getargs(x, 0, 0, _("copied takes no arguments"))
374 getargs(x, 0, 0, _("copied takes no arguments"))
373 s = []
375 s = []
374 for f in mctx.subset:
376 for f in mctx.subset:
375 p = mctx.ctx[f].parents()
377 p = mctx.ctx[f].parents()
376 if p and p[0].path() != f:
378 if p and p[0].path() != f:
377 s.append(f)
379 s.append(f)
378 return s
380 return s
379
381
380 def subrepo(mctx, x):
382 def subrepo(mctx, x):
381 """``subrepo([pattern])``
383 """``subrepo([pattern])``
382 Subrepositories whose paths match the given pattern.
384 Subrepositories whose paths match the given pattern.
383 """
385 """
384 # i18n: "subrepo" is a keyword
386 # i18n: "subrepo" is a keyword
385 getargs(x, 0, 1, _("subrepo takes at most one argument"))
387 getargs(x, 0, 1, _("subrepo takes at most one argument"))
386 ctx = mctx.ctx
388 ctx = mctx.ctx
387 sstate = sorted(ctx.substate)
389 sstate = sorted(ctx.substate)
388 if x:
390 if x:
389 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
391 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
390
392
391 import match as matchmod # avoid circular import issues
393 import match as matchmod # avoid circular import issues
392 fast = not matchmod.patkind(pat)
394 fast = not matchmod.patkind(pat)
393 if fast:
395 if fast:
394 def m(s):
396 def m(s):
395 return (s == pat)
397 return (s == pat)
396 else:
398 else:
397 m = matchmod.match(ctx._repo.root, '', [pat], ctx=ctx)
399 m = matchmod.match(ctx._repo.root, '', [pat], ctx=ctx)
398 return [sub for sub in sstate if m(sub)]
400 return [sub for sub in sstate if m(sub)]
399 else:
401 else:
400 return [sub for sub in sstate]
402 return [sub for sub in sstate]
401
403
402 symbols = {
404 symbols = {
403 'added': added,
405 'added': added,
404 'binary': binary,
406 'binary': binary,
405 'clean': clean,
407 'clean': clean,
406 'copied': copied,
408 'copied': copied,
407 'deleted': deleted,
409 'deleted': deleted,
408 'encoding': encoding,
410 'encoding': encoding,
409 'eol': eol,
411 'eol': eol,
410 'exec': exec_,
412 'exec': exec_,
411 'grep': grep,
413 'grep': grep,
412 'ignored': ignored,
414 'ignored': ignored,
413 'hgignore': hgignore,
415 'hgignore': hgignore,
414 'modified': modified,
416 'modified': modified,
415 'removed': removed,
417 'removed': removed,
416 'resolved': resolved,
418 'resolved': resolved,
417 'size': size,
419 'size': size,
418 'symlink': symlink,
420 'symlink': symlink,
419 'unknown': unknown,
421 'unknown': unknown,
420 'unresolved': unresolved,
422 'unresolved': unresolved,
421 'subrepo': subrepo,
423 'subrepo': subrepo,
422 }
424 }
423
425
424 methods = {
426 methods = {
425 'string': stringset,
427 'string': stringset,
426 'symbol': stringset,
428 'symbol': stringset,
427 'and': andset,
429 'and': andset,
428 'or': orset,
430 'or': orset,
429 'minus': minusset,
431 'minus': minusset,
430 'list': listset,
432 'list': listset,
431 'group': getset,
433 'group': getset,
432 'not': notset,
434 'not': notset,
433 'func': func,
435 'func': func,
434 }
436 }
435
437
436 class matchctx(object):
438 class matchctx(object):
437 def __init__(self, ctx, subset=None, status=None):
439 def __init__(self, ctx, subset=None, status=None):
438 self.ctx = ctx
440 self.ctx = ctx
439 self.subset = subset
441 self.subset = subset
440 self._status = status
442 self._status = status
441 def status(self):
443 def status(self):
442 return self._status
444 return self._status
443 def matcher(self, patterns):
445 def matcher(self, patterns):
444 return self.ctx.match(patterns)
446 return self.ctx.match(patterns)
445 def filter(self, files):
447 def filter(self, files):
446 return [f for f in files if f in self.subset]
448 return [f for f in files if f in self.subset]
447 def existing(self):
449 def existing(self):
448 if self._status is not None:
450 if self._status is not None:
449 removed = set(self._status[3])
451 removed = set(self._status[3])
450 unknown = set(self._status[4] + self._status[5])
452 unknown = set(self._status[4] + self._status[5])
451 else:
453 else:
452 removed = set()
454 removed = set()
453 unknown = set()
455 unknown = set()
454 return (f for f in self.subset
456 return (f for f in self.subset
455 if (f in self.ctx and f not in removed) or f in unknown)
457 if (f in self.ctx and f not in removed) or f in unknown)
456 def narrow(self, files):
458 def narrow(self, files):
457 return matchctx(self.ctx, self.filter(files), self._status)
459 return matchctx(self.ctx, self.filter(files), self._status)
458
460
459 def _intree(funcs, tree):
461 def _intree(funcs, tree):
460 if isinstance(tree, tuple):
462 if isinstance(tree, tuple):
461 if tree[0] == 'func' and tree[1][0] == 'symbol':
463 if tree[0] == 'func' and tree[1][0] == 'symbol':
462 if tree[1][1] in funcs:
464 if tree[1][1] in funcs:
463 return True
465 return True
464 for s in tree[1:]:
466 for s in tree[1:]:
465 if _intree(funcs, s):
467 if _intree(funcs, s):
466 return True
468 return True
467 return False
469 return False
468
470
469 # filesets using matchctx.existing()
471 # filesets using matchctx.existing()
470 _existingcallers = [
472 _existingcallers = [
471 'binary',
473 'binary',
472 'exec',
474 'exec',
473 'grep',
475 'grep',
474 'size',
476 'size',
475 'symlink',
477 'symlink',
476 ]
478 ]
477
479
478 def getfileset(ctx, expr):
480 def getfileset(ctx, expr):
479 tree, pos = parse(expr)
481 tree, pos = parse(expr)
480 if (pos != len(expr)):
482 if (pos != len(expr)):
481 raise error.ParseError(_("invalid token"), pos)
483 raise error.ParseError(_("invalid token"), pos)
482
484
483 # do we need status info?
485 # do we need status info?
484 if (_intree(['modified', 'added', 'removed', 'deleted',
486 if (_intree(['modified', 'added', 'removed', 'deleted',
485 'unknown', 'ignored', 'clean'], tree) or
487 'unknown', 'ignored', 'clean'], tree) or
486 # Using matchctx.existing() on a workingctx requires us to check
488 # Using matchctx.existing() on a workingctx requires us to check
487 # for deleted files.
489 # for deleted files.
488 (ctx.rev() is None and _intree(_existingcallers, tree))):
490 (ctx.rev() is None and _intree(_existingcallers, tree))):
489 unknown = _intree(['unknown'], tree)
491 unknown = _intree(['unknown'], tree)
490 ignored = _intree(['ignored'], tree)
492 ignored = _intree(['ignored'], tree)
491
493
492 r = ctx._repo
494 r = ctx._repo
493 status = r.status(ctx.p1(), ctx,
495 status = r.status(ctx.p1(), ctx,
494 unknown=unknown, ignored=ignored, clean=True)
496 unknown=unknown, ignored=ignored, clean=True)
495 subset = []
497 subset = []
496 for c in status:
498 for c in status:
497 subset.extend(c)
499 subset.extend(c)
498 else:
500 else:
499 status = None
501 status = None
500 subset = list(ctx.walk(ctx.match([])))
502 subset = list(ctx.walk(ctx.match([])))
501
503
502 return getset(matchctx(ctx, subset, status), tree)
504 return getset(matchctx(ctx, subset, status), tree)
503
505
504 # tell hggettext to extract docstrings from these functions:
506 # tell hggettext to extract docstrings from these functions:
505 i18nfunctions = symbols.values()
507 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