##// END OF EJS Templates
fileset: drop bad "elif:" trying to check invalid size expression...
Yuya Nishihara -
r36523:db33c5bc default
parent child Browse files
Show More
@@ -1,662 +1,661
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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 match as matchmod,
15 match as matchmod,
16 merge,
16 merge,
17 parser,
17 parser,
18 pycompat,
18 pycompat,
19 registrar,
19 registrar,
20 scmutil,
20 scmutil,
21 util,
21 util,
22 )
22 )
23
23
24 elements = {
24 elements = {
25 # token-type: binding-strength, primary, prefix, infix, suffix
25 # token-type: binding-strength, primary, prefix, infix, suffix
26 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
26 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
27 ":": (15, None, None, ("kindpat", 15), None),
27 ":": (15, None, None, ("kindpat", 15), None),
28 "-": (5, None, ("negate", 19), ("minus", 5), None),
28 "-": (5, None, ("negate", 19), ("minus", 5), None),
29 "not": (10, None, ("not", 10), None, None),
29 "not": (10, None, ("not", 10), None, None),
30 "!": (10, None, ("not", 10), None, None),
30 "!": (10, None, ("not", 10), None, None),
31 "and": (5, None, None, ("and", 5), None),
31 "and": (5, None, None, ("and", 5), None),
32 "&": (5, None, None, ("and", 5), None),
32 "&": (5, None, None, ("and", 5), None),
33 "or": (4, None, None, ("or", 4), None),
33 "or": (4, None, None, ("or", 4), None),
34 "|": (4, None, None, ("or", 4), None),
34 "|": (4, None, None, ("or", 4), None),
35 "+": (4, None, None, ("or", 4), None),
35 "+": (4, None, None, ("or", 4), None),
36 ",": (2, None, None, ("list", 2), None),
36 ",": (2, None, None, ("list", 2), None),
37 ")": (0, None, None, None, None),
37 ")": (0, None, None, None, None),
38 "symbol": (0, "symbol", None, None, None),
38 "symbol": (0, "symbol", None, None, None),
39 "string": (0, "string", None, None, None),
39 "string": (0, "string", None, None, None),
40 "end": (0, None, None, None, None),
40 "end": (0, None, None, None, None),
41 }
41 }
42
42
43 keywords = {'and', 'or', 'not'}
43 keywords = {'and', 'or', 'not'}
44
44
45 globchars = ".*{}[]?/\\_"
45 globchars = ".*{}[]?/\\_"
46
46
47 def tokenize(program):
47 def tokenize(program):
48 pos, l = 0, len(program)
48 pos, l = 0, len(program)
49 program = pycompat.bytestr(program)
49 program = pycompat.bytestr(program)
50 while pos < l:
50 while pos < l:
51 c = program[pos]
51 c = program[pos]
52 if c.isspace(): # skip inter-token whitespace
52 if c.isspace(): # skip inter-token whitespace
53 pass
53 pass
54 elif c in "(),-:|&+!": # handle simple operators
54 elif c in "(),-:|&+!": # handle simple operators
55 yield (c, None, pos)
55 yield (c, None, pos)
56 elif (c in '"\'' or c == 'r' and
56 elif (c in '"\'' or c == 'r' and
57 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
57 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
58 if c == 'r':
58 if c == 'r':
59 pos += 1
59 pos += 1
60 c = program[pos]
60 c = program[pos]
61 decode = lambda x: x
61 decode = lambda x: x
62 else:
62 else:
63 decode = parser.unescapestr
63 decode = parser.unescapestr
64 pos += 1
64 pos += 1
65 s = pos
65 s = pos
66 while pos < l: # find closing quote
66 while pos < l: # find closing quote
67 d = program[pos]
67 d = program[pos]
68 if d == '\\': # skip over escaped characters
68 if d == '\\': # skip over escaped characters
69 pos += 2
69 pos += 2
70 continue
70 continue
71 if d == c:
71 if d == c:
72 yield ('string', decode(program[s:pos]), s)
72 yield ('string', decode(program[s:pos]), s)
73 break
73 break
74 pos += 1
74 pos += 1
75 else:
75 else:
76 raise error.ParseError(_("unterminated string"), s)
76 raise error.ParseError(_("unterminated string"), s)
77 elif c.isalnum() or c in globchars or ord(c) > 127:
77 elif c.isalnum() or c in globchars or ord(c) > 127:
78 # gather up a symbol/keyword
78 # gather up a symbol/keyword
79 s = pos
79 s = pos
80 pos += 1
80 pos += 1
81 while pos < l: # find end of symbol
81 while pos < l: # find end of symbol
82 d = program[pos]
82 d = program[pos]
83 if not (d.isalnum() or d in globchars or ord(d) > 127):
83 if not (d.isalnum() or d in globchars or ord(d) > 127):
84 break
84 break
85 pos += 1
85 pos += 1
86 sym = program[s:pos]
86 sym = program[s:pos]
87 if sym in keywords: # operator keywords
87 if sym in keywords: # operator keywords
88 yield (sym, None, s)
88 yield (sym, None, s)
89 else:
89 else:
90 yield ('symbol', sym, s)
90 yield ('symbol', sym, s)
91 pos -= 1
91 pos -= 1
92 else:
92 else:
93 raise error.ParseError(_("syntax error"), pos)
93 raise error.ParseError(_("syntax error"), pos)
94 pos += 1
94 pos += 1
95 yield ('end', None, pos)
95 yield ('end', None, pos)
96
96
97 def parse(expr):
97 def parse(expr):
98 p = parser.parser(elements)
98 p = parser.parser(elements)
99 tree, pos = p.parse(tokenize(expr))
99 tree, pos = p.parse(tokenize(expr))
100 if pos != len(expr):
100 if pos != len(expr):
101 raise error.ParseError(_("invalid token"), pos)
101 raise error.ParseError(_("invalid token"), pos)
102 return tree
102 return tree
103
103
104 def getsymbol(x):
104 def getsymbol(x):
105 if x and x[0] == 'symbol':
105 if x and x[0] == 'symbol':
106 return x[1]
106 return x[1]
107 raise error.ParseError(_('not a symbol'))
107 raise error.ParseError(_('not a symbol'))
108
108
109 def getstring(x, err):
109 def getstring(x, err):
110 if x and (x[0] == 'string' or x[0] == 'symbol'):
110 if x and (x[0] == 'string' or x[0] == 'symbol'):
111 return x[1]
111 return x[1]
112 raise error.ParseError(err)
112 raise error.ParseError(err)
113
113
114 def _getkindpat(x, y, allkinds, err):
114 def _getkindpat(x, y, allkinds, err):
115 kind = getsymbol(x)
115 kind = getsymbol(x)
116 pat = getstring(y, err)
116 pat = getstring(y, err)
117 if kind not in allkinds:
117 if kind not in allkinds:
118 raise error.ParseError(_("invalid pattern kind: %s") % kind)
118 raise error.ParseError(_("invalid pattern kind: %s") % kind)
119 return '%s:%s' % (kind, pat)
119 return '%s:%s' % (kind, pat)
120
120
121 def getpattern(x, allkinds, err):
121 def getpattern(x, allkinds, err):
122 if x and x[0] == 'kindpat':
122 if x and x[0] == 'kindpat':
123 return _getkindpat(x[1], x[2], allkinds, err)
123 return _getkindpat(x[1], x[2], allkinds, err)
124 return getstring(x, err)
124 return getstring(x, err)
125
125
126 def getset(mctx, x):
126 def getset(mctx, x):
127 if not x:
127 if not x:
128 raise error.ParseError(_("missing argument"))
128 raise error.ParseError(_("missing argument"))
129 return methods[x[0]](mctx, *x[1:])
129 return methods[x[0]](mctx, *x[1:])
130
130
131 def stringset(mctx, x):
131 def stringset(mctx, x):
132 m = mctx.matcher([x])
132 m = mctx.matcher([x])
133 return [f for f in mctx.subset if m(f)]
133 return [f for f in mctx.subset if m(f)]
134
134
135 def kindpatset(mctx, x, y):
135 def kindpatset(mctx, x, y):
136 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
136 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
137 _("pattern must be a string")))
137 _("pattern must be a string")))
138
138
139 def andset(mctx, x, y):
139 def andset(mctx, x, y):
140 return getset(mctx.narrow(getset(mctx, x)), y)
140 return getset(mctx.narrow(getset(mctx, x)), y)
141
141
142 def orset(mctx, x, y):
142 def orset(mctx, x, y):
143 # needs optimizing
143 # needs optimizing
144 xl = getset(mctx, x)
144 xl = getset(mctx, x)
145 yl = getset(mctx, y)
145 yl = getset(mctx, y)
146 return xl + [f for f in yl if f not in xl]
146 return xl + [f for f in yl if f not in xl]
147
147
148 def notset(mctx, x):
148 def notset(mctx, x):
149 s = set(getset(mctx, x))
149 s = set(getset(mctx, x))
150 return [r for r in mctx.subset if r not in s]
150 return [r for r in mctx.subset if r not in s]
151
151
152 def minusset(mctx, x, y):
152 def minusset(mctx, x, y):
153 xl = getset(mctx, x)
153 xl = getset(mctx, x)
154 yl = set(getset(mctx, y))
154 yl = set(getset(mctx, y))
155 return [f for f in xl if f not in yl]
155 return [f for f in xl if f not in yl]
156
156
157 def negateset(mctx, x):
157 def negateset(mctx, x):
158 raise error.ParseError(_("can't use negate operator in this context"))
158 raise error.ParseError(_("can't use negate operator in this context"))
159
159
160 def listset(mctx, a, b):
160 def listset(mctx, a, b):
161 raise error.ParseError(_("can't use a list in this context"),
161 raise error.ParseError(_("can't use a list in this context"),
162 hint=_('see hg help "filesets.x or y"'))
162 hint=_('see hg help "filesets.x or y"'))
163
163
164 # symbols are callable like:
164 # symbols are callable like:
165 # fun(mctx, x)
165 # fun(mctx, x)
166 # with:
166 # with:
167 # mctx - current matchctx instance
167 # mctx - current matchctx instance
168 # x - argument in tree form
168 # x - argument in tree form
169 symbols = {}
169 symbols = {}
170
170
171 # filesets using matchctx.status()
171 # filesets using matchctx.status()
172 _statuscallers = set()
172 _statuscallers = set()
173
173
174 # filesets using matchctx.existing()
174 # filesets using matchctx.existing()
175 _existingcallers = set()
175 _existingcallers = set()
176
176
177 predicate = registrar.filesetpredicate()
177 predicate = registrar.filesetpredicate()
178
178
179 @predicate('modified()', callstatus=True)
179 @predicate('modified()', callstatus=True)
180 def modified(mctx, x):
180 def modified(mctx, x):
181 """File that is modified according to :hg:`status`.
181 """File that is modified according to :hg:`status`.
182 """
182 """
183 # i18n: "modified" is a keyword
183 # i18n: "modified" is a keyword
184 getargs(x, 0, 0, _("modified takes no arguments"))
184 getargs(x, 0, 0, _("modified takes no arguments"))
185 s = set(mctx.status().modified)
185 s = set(mctx.status().modified)
186 return [f for f in mctx.subset if f in s]
186 return [f for f in mctx.subset if f in s]
187
187
188 @predicate('added()', callstatus=True)
188 @predicate('added()', callstatus=True)
189 def added(mctx, x):
189 def added(mctx, x):
190 """File that is added according to :hg:`status`.
190 """File that is added according to :hg:`status`.
191 """
191 """
192 # i18n: "added" is a keyword
192 # i18n: "added" is a keyword
193 getargs(x, 0, 0, _("added takes no arguments"))
193 getargs(x, 0, 0, _("added takes no arguments"))
194 s = set(mctx.status().added)
194 s = set(mctx.status().added)
195 return [f for f in mctx.subset if f in s]
195 return [f for f in mctx.subset if f in s]
196
196
197 @predicate('removed()', callstatus=True)
197 @predicate('removed()', callstatus=True)
198 def removed(mctx, x):
198 def removed(mctx, x):
199 """File that is removed according to :hg:`status`.
199 """File that is removed according to :hg:`status`.
200 """
200 """
201 # i18n: "removed" is a keyword
201 # i18n: "removed" is a keyword
202 getargs(x, 0, 0, _("removed takes no arguments"))
202 getargs(x, 0, 0, _("removed takes no arguments"))
203 s = set(mctx.status().removed)
203 s = set(mctx.status().removed)
204 return [f for f in mctx.subset if f in s]
204 return [f for f in mctx.subset if f in s]
205
205
206 @predicate('deleted()', callstatus=True)
206 @predicate('deleted()', callstatus=True)
207 def deleted(mctx, x):
207 def deleted(mctx, x):
208 """Alias for ``missing()``.
208 """Alias for ``missing()``.
209 """
209 """
210 # i18n: "deleted" is a keyword
210 # i18n: "deleted" is a keyword
211 getargs(x, 0, 0, _("deleted takes no arguments"))
211 getargs(x, 0, 0, _("deleted takes no arguments"))
212 s = set(mctx.status().deleted)
212 s = set(mctx.status().deleted)
213 return [f for f in mctx.subset if f in s]
213 return [f for f in mctx.subset if f in s]
214
214
215 @predicate('missing()', callstatus=True)
215 @predicate('missing()', callstatus=True)
216 def missing(mctx, x):
216 def missing(mctx, x):
217 """File that is missing according to :hg:`status`.
217 """File that is missing according to :hg:`status`.
218 """
218 """
219 # i18n: "missing" is a keyword
219 # i18n: "missing" is a keyword
220 getargs(x, 0, 0, _("missing takes no arguments"))
220 getargs(x, 0, 0, _("missing takes no arguments"))
221 s = set(mctx.status().deleted)
221 s = set(mctx.status().deleted)
222 return [f for f in mctx.subset if f in s]
222 return [f for f in mctx.subset if f in s]
223
223
224 @predicate('unknown()', callstatus=True)
224 @predicate('unknown()', callstatus=True)
225 def unknown(mctx, x):
225 def unknown(mctx, x):
226 """File that is unknown according to :hg:`status`. These files will only be
226 """File that is unknown according to :hg:`status`. These files will only be
227 considered if this predicate is used.
227 considered if this predicate is used.
228 """
228 """
229 # i18n: "unknown" is a keyword
229 # i18n: "unknown" is a keyword
230 getargs(x, 0, 0, _("unknown takes no arguments"))
230 getargs(x, 0, 0, _("unknown takes no arguments"))
231 s = set(mctx.status().unknown)
231 s = set(mctx.status().unknown)
232 return [f for f in mctx.subset if f in s]
232 return [f for f in mctx.subset if f in s]
233
233
234 @predicate('ignored()', callstatus=True)
234 @predicate('ignored()', callstatus=True)
235 def ignored(mctx, x):
235 def ignored(mctx, x):
236 """File that is ignored according to :hg:`status`. These files will only be
236 """File that is ignored according to :hg:`status`. These files will only be
237 considered if this predicate is used.
237 considered if this predicate is used.
238 """
238 """
239 # i18n: "ignored" is a keyword
239 # i18n: "ignored" is a keyword
240 getargs(x, 0, 0, _("ignored takes no arguments"))
240 getargs(x, 0, 0, _("ignored takes no arguments"))
241 s = set(mctx.status().ignored)
241 s = set(mctx.status().ignored)
242 return [f for f in mctx.subset if f in s]
242 return [f for f in mctx.subset if f in s]
243
243
244 @predicate('clean()', callstatus=True)
244 @predicate('clean()', callstatus=True)
245 def clean(mctx, x):
245 def clean(mctx, x):
246 """File that is clean according to :hg:`status`.
246 """File that is clean according to :hg:`status`.
247 """
247 """
248 # i18n: "clean" is a keyword
248 # i18n: "clean" is a keyword
249 getargs(x, 0, 0, _("clean takes no arguments"))
249 getargs(x, 0, 0, _("clean takes no arguments"))
250 s = set(mctx.status().clean)
250 s = set(mctx.status().clean)
251 return [f for f in mctx.subset if f in s]
251 return [f for f in mctx.subset if f in s]
252
252
253 def func(mctx, a, b):
253 def func(mctx, a, b):
254 funcname = getsymbol(a)
254 funcname = getsymbol(a)
255 if funcname in symbols:
255 if funcname in symbols:
256 enabled = mctx._existingenabled
256 enabled = mctx._existingenabled
257 mctx._existingenabled = funcname in _existingcallers
257 mctx._existingenabled = funcname in _existingcallers
258 try:
258 try:
259 return symbols[funcname](mctx, b)
259 return symbols[funcname](mctx, b)
260 finally:
260 finally:
261 mctx._existingenabled = enabled
261 mctx._existingenabled = enabled
262
262
263 keep = lambda fn: getattr(fn, '__doc__', None) is not None
263 keep = lambda fn: getattr(fn, '__doc__', None) is not None
264
264
265 syms = [s for (s, fn) in symbols.items() if keep(fn)]
265 syms = [s for (s, fn) in symbols.items() if keep(fn)]
266 raise error.UnknownIdentifier(funcname, syms)
266 raise error.UnknownIdentifier(funcname, syms)
267
267
268 def getlist(x):
268 def getlist(x):
269 if not x:
269 if not x:
270 return []
270 return []
271 if x[0] == 'list':
271 if x[0] == 'list':
272 return getlist(x[1]) + [x[2]]
272 return getlist(x[1]) + [x[2]]
273 return [x]
273 return [x]
274
274
275 def getargs(x, min, max, err):
275 def getargs(x, min, max, err):
276 l = getlist(x)
276 l = getlist(x)
277 if len(l) < min or len(l) > max:
277 if len(l) < min or len(l) > max:
278 raise error.ParseError(err)
278 raise error.ParseError(err)
279 return l
279 return l
280
280
281 @predicate('binary()', callexisting=True)
281 @predicate('binary()', callexisting=True)
282 def binary(mctx, x):
282 def binary(mctx, x):
283 """File that appears to be binary (contains NUL bytes).
283 """File that appears to be binary (contains NUL bytes).
284 """
284 """
285 # i18n: "binary" is a keyword
285 # i18n: "binary" is a keyword
286 getargs(x, 0, 0, _("binary takes no arguments"))
286 getargs(x, 0, 0, _("binary takes no arguments"))
287 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
287 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
288
288
289 @predicate('exec()', callexisting=True)
289 @predicate('exec()', callexisting=True)
290 def exec_(mctx, x):
290 def exec_(mctx, x):
291 """File that is marked as executable.
291 """File that is marked as executable.
292 """
292 """
293 # i18n: "exec" is a keyword
293 # i18n: "exec" is a keyword
294 getargs(x, 0, 0, _("exec takes no arguments"))
294 getargs(x, 0, 0, _("exec takes no arguments"))
295 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
295 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
296
296
297 @predicate('symlink()', callexisting=True)
297 @predicate('symlink()', callexisting=True)
298 def symlink(mctx, x):
298 def symlink(mctx, x):
299 """File that is marked as a symlink.
299 """File that is marked as a symlink.
300 """
300 """
301 # i18n: "symlink" is a keyword
301 # i18n: "symlink" is a keyword
302 getargs(x, 0, 0, _("symlink takes no arguments"))
302 getargs(x, 0, 0, _("symlink takes no arguments"))
303 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
303 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
304
304
305 @predicate('resolved()')
305 @predicate('resolved()')
306 def resolved(mctx, x):
306 def resolved(mctx, x):
307 """File that is marked resolved according to :hg:`resolve -l`.
307 """File that is marked resolved according to :hg:`resolve -l`.
308 """
308 """
309 # i18n: "resolved" is a keyword
309 # i18n: "resolved" is a keyword
310 getargs(x, 0, 0, _("resolved takes no arguments"))
310 getargs(x, 0, 0, _("resolved takes no arguments"))
311 if mctx.ctx.rev() is not None:
311 if mctx.ctx.rev() is not None:
312 return []
312 return []
313 ms = merge.mergestate.read(mctx.ctx.repo())
313 ms = merge.mergestate.read(mctx.ctx.repo())
314 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
314 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
315
315
316 @predicate('unresolved()')
316 @predicate('unresolved()')
317 def unresolved(mctx, x):
317 def unresolved(mctx, x):
318 """File that is marked unresolved according to :hg:`resolve -l`.
318 """File that is marked unresolved according to :hg:`resolve -l`.
319 """
319 """
320 # i18n: "unresolved" is a keyword
320 # i18n: "unresolved" is a keyword
321 getargs(x, 0, 0, _("unresolved takes no arguments"))
321 getargs(x, 0, 0, _("unresolved takes no arguments"))
322 if mctx.ctx.rev() is not None:
322 if mctx.ctx.rev() is not None:
323 return []
323 return []
324 ms = merge.mergestate.read(mctx.ctx.repo())
324 ms = merge.mergestate.read(mctx.ctx.repo())
325 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
325 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
326
326
327 @predicate('hgignore()')
327 @predicate('hgignore()')
328 def hgignore(mctx, x):
328 def hgignore(mctx, x):
329 """File that matches the active .hgignore pattern.
329 """File that matches the active .hgignore pattern.
330 """
330 """
331 # i18n: "hgignore" is a keyword
331 # i18n: "hgignore" is a keyword
332 getargs(x, 0, 0, _("hgignore takes no arguments"))
332 getargs(x, 0, 0, _("hgignore takes no arguments"))
333 ignore = mctx.ctx.repo().dirstate._ignore
333 ignore = mctx.ctx.repo().dirstate._ignore
334 return [f for f in mctx.subset if ignore(f)]
334 return [f for f in mctx.subset if ignore(f)]
335
335
336 @predicate('portable()')
336 @predicate('portable()')
337 def portable(mctx, x):
337 def portable(mctx, x):
338 """File that has a portable name. (This doesn't include filenames with case
338 """File that has a portable name. (This doesn't include filenames with case
339 collisions.)
339 collisions.)
340 """
340 """
341 # i18n: "portable" is a keyword
341 # i18n: "portable" is a keyword
342 getargs(x, 0, 0, _("portable takes no arguments"))
342 getargs(x, 0, 0, _("portable takes no arguments"))
343 checkwinfilename = util.checkwinfilename
343 checkwinfilename = util.checkwinfilename
344 return [f for f in mctx.subset if checkwinfilename(f) is None]
344 return [f for f in mctx.subset if checkwinfilename(f) is None]
345
345
346 @predicate('grep(regex)', callexisting=True)
346 @predicate('grep(regex)', callexisting=True)
347 def grep(mctx, x):
347 def grep(mctx, x):
348 """File contains the given regular expression.
348 """File contains the given regular expression.
349 """
349 """
350 try:
350 try:
351 # i18n: "grep" is a keyword
351 # i18n: "grep" is a keyword
352 r = re.compile(getstring(x, _("grep requires a pattern")))
352 r = re.compile(getstring(x, _("grep requires a pattern")))
353 except re.error as e:
353 except re.error as e:
354 raise error.ParseError(_('invalid match pattern: %s') % e)
354 raise error.ParseError(_('invalid match pattern: %s') % e)
355 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
355 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
356
356
357 def _sizetomax(s):
357 def _sizetomax(s):
358 try:
358 try:
359 s = s.strip().lower()
359 s = s.strip().lower()
360 for k, v in util._sizeunits:
360 for k, v in util._sizeunits:
361 if s.endswith(k):
361 if s.endswith(k):
362 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
362 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
363 n = s[:-len(k)]
363 n = s[:-len(k)]
364 inc = 1.0
364 inc = 1.0
365 if "." in n:
365 if "." in n:
366 inc /= 10 ** len(n.split(".")[1])
366 inc /= 10 ** len(n.split(".")[1])
367 return int((float(n) + inc) * v) - 1
367 return int((float(n) + inc) * v) - 1
368 # no extension, this is a precise value
368 # no extension, this is a precise value
369 return int(s)
369 return int(s)
370 except ValueError:
370 except ValueError:
371 raise error.ParseError(_("couldn't parse size: %s") % s)
371 raise error.ParseError(_("couldn't parse size: %s") % s)
372
372
373 def sizematcher(x):
373 def sizematcher(x):
374 """Return a function(size) -> bool from the ``size()`` expression"""
374 """Return a function(size) -> bool from the ``size()`` expression"""
375
375
376 # i18n: "size" is a keyword
376 # i18n: "size" is a keyword
377 expr = getstring(x, _("size requires an expression")).strip()
377 expr = getstring(x, _("size requires an expression")).strip()
378 if '-' in expr: # do we have a range?
378 if '-' in expr: # do we have a range?
379 a, b = expr.split('-', 1)
379 a, b = expr.split('-', 1)
380 a = util.sizetoint(a)
380 a = util.sizetoint(a)
381 b = util.sizetoint(b)
381 b = util.sizetoint(b)
382 return lambda x: x >= a and x <= b
382 return lambda x: x >= a and x <= b
383 elif expr.startswith("<="):
383 elif expr.startswith("<="):
384 a = util.sizetoint(expr[2:])
384 a = util.sizetoint(expr[2:])
385 return lambda x: x <= a
385 return lambda x: x <= a
386 elif expr.startswith("<"):
386 elif expr.startswith("<"):
387 a = util.sizetoint(expr[1:])
387 a = util.sizetoint(expr[1:])
388 return lambda x: x < a
388 return lambda x: x < a
389 elif expr.startswith(">="):
389 elif expr.startswith(">="):
390 a = util.sizetoint(expr[2:])
390 a = util.sizetoint(expr[2:])
391 return lambda x: x >= a
391 return lambda x: x >= a
392 elif expr.startswith(">"):
392 elif expr.startswith(">"):
393 a = util.sizetoint(expr[1:])
393 a = util.sizetoint(expr[1:])
394 return lambda x: x > a
394 return lambda x: x > a
395 elif expr[0:1].isdigit or expr.startswith('.'):
395 else:
396 a = util.sizetoint(expr)
396 a = util.sizetoint(expr)
397 b = _sizetomax(expr)
397 b = _sizetomax(expr)
398 return lambda x: x >= a and x <= b
398 return lambda x: x >= a and x <= b
399 raise error.ParseError(_("couldn't parse size: %s") % expr)
400
399
401 @predicate('size(expression)', callexisting=True)
400 @predicate('size(expression)', callexisting=True)
402 def size(mctx, x):
401 def size(mctx, x):
403 """File size matches the given expression. Examples:
402 """File size matches the given expression. Examples:
404
403
405 - size('1k') - files from 1024 to 2047 bytes
404 - size('1k') - files from 1024 to 2047 bytes
406 - size('< 20k') - files less than 20480 bytes
405 - size('< 20k') - files less than 20480 bytes
407 - size('>= .5MB') - files at least 524288 bytes
406 - size('>= .5MB') - files at least 524288 bytes
408 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
407 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
409 """
408 """
410 m = sizematcher(x)
409 m = sizematcher(x)
411 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
410 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
412
411
413 @predicate('encoding(name)', callexisting=True)
412 @predicate('encoding(name)', callexisting=True)
414 def encoding(mctx, x):
413 def encoding(mctx, x):
415 """File can be successfully decoded with the given character
414 """File can be successfully decoded with the given character
416 encoding. May not be useful for encodings other than ASCII and
415 encoding. May not be useful for encodings other than ASCII and
417 UTF-8.
416 UTF-8.
418 """
417 """
419
418
420 # i18n: "encoding" is a keyword
419 # i18n: "encoding" is a keyword
421 enc = getstring(x, _("encoding requires an encoding name"))
420 enc = getstring(x, _("encoding requires an encoding name"))
422
421
423 s = []
422 s = []
424 for f in mctx.existing():
423 for f in mctx.existing():
425 d = mctx.ctx[f].data()
424 d = mctx.ctx[f].data()
426 try:
425 try:
427 d.decode(enc)
426 d.decode(enc)
428 except LookupError:
427 except LookupError:
429 raise error.Abort(_("unknown encoding '%s'") % enc)
428 raise error.Abort(_("unknown encoding '%s'") % enc)
430 except UnicodeDecodeError:
429 except UnicodeDecodeError:
431 continue
430 continue
432 s.append(f)
431 s.append(f)
433
432
434 return s
433 return s
435
434
436 @predicate('eol(style)', callexisting=True)
435 @predicate('eol(style)', callexisting=True)
437 def eol(mctx, x):
436 def eol(mctx, x):
438 """File contains newlines of the given style (dos, unix, mac). Binary
437 """File contains newlines of the given style (dos, unix, mac). Binary
439 files are excluded, files with mixed line endings match multiple
438 files are excluded, files with mixed line endings match multiple
440 styles.
439 styles.
441 """
440 """
442
441
443 # i18n: "eol" is a keyword
442 # i18n: "eol" is a keyword
444 enc = getstring(x, _("eol requires a style name"))
443 enc = getstring(x, _("eol requires a style name"))
445
444
446 s = []
445 s = []
447 for f in mctx.existing():
446 for f in mctx.existing():
448 d = mctx.ctx[f].data()
447 d = mctx.ctx[f].data()
449 if util.binary(d):
448 if util.binary(d):
450 continue
449 continue
451 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
450 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
452 s.append(f)
451 s.append(f)
453 elif enc == 'unix' and re.search('(?<!\r)\n', d):
452 elif enc == 'unix' and re.search('(?<!\r)\n', d):
454 s.append(f)
453 s.append(f)
455 elif enc == 'mac' and re.search('\r(?!\n)', d):
454 elif enc == 'mac' and re.search('\r(?!\n)', d):
456 s.append(f)
455 s.append(f)
457 return s
456 return s
458
457
459 @predicate('copied()')
458 @predicate('copied()')
460 def copied(mctx, x):
459 def copied(mctx, x):
461 """File that is recorded as being copied.
460 """File that is recorded as being copied.
462 """
461 """
463 # i18n: "copied" is a keyword
462 # i18n: "copied" is a keyword
464 getargs(x, 0, 0, _("copied takes no arguments"))
463 getargs(x, 0, 0, _("copied takes no arguments"))
465 s = []
464 s = []
466 for f in mctx.subset:
465 for f in mctx.subset:
467 if f in mctx.ctx:
466 if f in mctx.ctx:
468 p = mctx.ctx[f].parents()
467 p = mctx.ctx[f].parents()
469 if p and p[0].path() != f:
468 if p and p[0].path() != f:
470 s.append(f)
469 s.append(f)
471 return s
470 return s
472
471
473 @predicate('revs(revs, pattern)')
472 @predicate('revs(revs, pattern)')
474 def revs(mctx, x):
473 def revs(mctx, x):
475 """Evaluate set in the specified revisions. If the revset match multiple
474 """Evaluate set in the specified revisions. If the revset match multiple
476 revs, this will return file matching pattern in any of the revision.
475 revs, this will return file matching pattern in any of the revision.
477 """
476 """
478 # i18n: "revs" is a keyword
477 # i18n: "revs" is a keyword
479 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
478 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
480 # i18n: "revs" is a keyword
479 # i18n: "revs" is a keyword
481 revspec = getstring(r, _("first argument to revs must be a revision"))
480 revspec = getstring(r, _("first argument to revs must be a revision"))
482 repo = mctx.ctx.repo()
481 repo = mctx.ctx.repo()
483 revs = scmutil.revrange(repo, [revspec])
482 revs = scmutil.revrange(repo, [revspec])
484
483
485 found = set()
484 found = set()
486 result = []
485 result = []
487 for r in revs:
486 for r in revs:
488 ctx = repo[r]
487 ctx = repo[r]
489 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
488 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
490 if f not in found:
489 if f not in found:
491 found.add(f)
490 found.add(f)
492 result.append(f)
491 result.append(f)
493 return result
492 return result
494
493
495 @predicate('status(base, rev, pattern)')
494 @predicate('status(base, rev, pattern)')
496 def status(mctx, x):
495 def status(mctx, x):
497 """Evaluate predicate using status change between ``base`` and
496 """Evaluate predicate using status change between ``base`` and
498 ``rev``. Examples:
497 ``rev``. Examples:
499
498
500 - ``status(3, 7, added())`` - matches files added from "3" to "7"
499 - ``status(3, 7, added())`` - matches files added from "3" to "7"
501 """
500 """
502 repo = mctx.ctx.repo()
501 repo = mctx.ctx.repo()
503 # i18n: "status" is a keyword
502 # i18n: "status" is a keyword
504 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
503 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
505 # i18n: "status" is a keyword
504 # i18n: "status" is a keyword
506 baseerr = _("first argument to status must be a revision")
505 baseerr = _("first argument to status must be a revision")
507 baserevspec = getstring(b, baseerr)
506 baserevspec = getstring(b, baseerr)
508 if not baserevspec:
507 if not baserevspec:
509 raise error.ParseError(baseerr)
508 raise error.ParseError(baseerr)
510 reverr = _("second argument to status must be a revision")
509 reverr = _("second argument to status must be a revision")
511 revspec = getstring(r, reverr)
510 revspec = getstring(r, reverr)
512 if not revspec:
511 if not revspec:
513 raise error.ParseError(reverr)
512 raise error.ParseError(reverr)
514 basenode, node = scmutil.revpair(repo, [baserevspec, revspec])
513 basenode, node = scmutil.revpair(repo, [baserevspec, revspec])
515 basectx = repo[basenode]
514 basectx = repo[basenode]
516 ctx = repo[node]
515 ctx = repo[node]
517 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
516 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
518
517
519 @predicate('subrepo([pattern])')
518 @predicate('subrepo([pattern])')
520 def subrepo(mctx, x):
519 def subrepo(mctx, x):
521 """Subrepositories whose paths match the given pattern.
520 """Subrepositories whose paths match the given pattern.
522 """
521 """
523 # i18n: "subrepo" is a keyword
522 # i18n: "subrepo" is a keyword
524 getargs(x, 0, 1, _("subrepo takes at most one argument"))
523 getargs(x, 0, 1, _("subrepo takes at most one argument"))
525 ctx = mctx.ctx
524 ctx = mctx.ctx
526 sstate = sorted(ctx.substate)
525 sstate = sorted(ctx.substate)
527 if x:
526 if x:
528 pat = getpattern(x, matchmod.allpatternkinds,
527 pat = getpattern(x, matchmod.allpatternkinds,
529 # i18n: "subrepo" is a keyword
528 # i18n: "subrepo" is a keyword
530 _("subrepo requires a pattern or no arguments"))
529 _("subrepo requires a pattern or no arguments"))
531 fast = not matchmod.patkind(pat)
530 fast = not matchmod.patkind(pat)
532 if fast:
531 if fast:
533 def m(s):
532 def m(s):
534 return (s == pat)
533 return (s == pat)
535 else:
534 else:
536 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
535 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
537 return [sub for sub in sstate if m(sub)]
536 return [sub for sub in sstate if m(sub)]
538 else:
537 else:
539 return [sub for sub in sstate]
538 return [sub for sub in sstate]
540
539
541 methods = {
540 methods = {
542 'string': stringset,
541 'string': stringset,
543 'symbol': stringset,
542 'symbol': stringset,
544 'kindpat': kindpatset,
543 'kindpat': kindpatset,
545 'and': andset,
544 'and': andset,
546 'or': orset,
545 'or': orset,
547 'minus': minusset,
546 'minus': minusset,
548 'negate': negateset,
547 'negate': negateset,
549 'list': listset,
548 'list': listset,
550 'group': getset,
549 'group': getset,
551 'not': notset,
550 'not': notset,
552 'func': func,
551 'func': func,
553 }
552 }
554
553
555 class matchctx(object):
554 class matchctx(object):
556 def __init__(self, ctx, subset, status=None):
555 def __init__(self, ctx, subset, status=None):
557 self.ctx = ctx
556 self.ctx = ctx
558 self.subset = subset
557 self.subset = subset
559 self._status = status
558 self._status = status
560 self._existingenabled = False
559 self._existingenabled = False
561 def status(self):
560 def status(self):
562 return self._status
561 return self._status
563 def matcher(self, patterns):
562 def matcher(self, patterns):
564 return self.ctx.match(patterns)
563 return self.ctx.match(patterns)
565 def filter(self, files):
564 def filter(self, files):
566 return [f for f in files if f in self.subset]
565 return [f for f in files if f in self.subset]
567 def existing(self):
566 def existing(self):
568 assert self._existingenabled, 'unexpected existing() invocation'
567 assert self._existingenabled, 'unexpected existing() invocation'
569 if self._status is not None:
568 if self._status is not None:
570 removed = set(self._status[3])
569 removed = set(self._status[3])
571 unknown = set(self._status[4] + self._status[5])
570 unknown = set(self._status[4] + self._status[5])
572 else:
571 else:
573 removed = set()
572 removed = set()
574 unknown = set()
573 unknown = set()
575 return (f for f in self.subset
574 return (f for f in self.subset
576 if (f in self.ctx and f not in removed) or f in unknown)
575 if (f in self.ctx and f not in removed) or f in unknown)
577 def narrow(self, files):
576 def narrow(self, files):
578 return matchctx(self.ctx, self.filter(files), self._status)
577 return matchctx(self.ctx, self.filter(files), self._status)
579 def switch(self, ctx, status=None):
578 def switch(self, ctx, status=None):
580 subset = self.filter(_buildsubset(ctx, status))
579 subset = self.filter(_buildsubset(ctx, status))
581 return matchctx(ctx, subset, status)
580 return matchctx(ctx, subset, status)
582
581
583 class fullmatchctx(matchctx):
582 class fullmatchctx(matchctx):
584 """A match context where any files in any revisions should be valid"""
583 """A match context where any files in any revisions should be valid"""
585
584
586 def __init__(self, ctx, status=None):
585 def __init__(self, ctx, status=None):
587 subset = _buildsubset(ctx, status)
586 subset = _buildsubset(ctx, status)
588 super(fullmatchctx, self).__init__(ctx, subset, status)
587 super(fullmatchctx, self).__init__(ctx, subset, status)
589 def switch(self, ctx, status=None):
588 def switch(self, ctx, status=None):
590 return fullmatchctx(ctx, status)
589 return fullmatchctx(ctx, status)
591
590
592 # filesets using matchctx.switch()
591 # filesets using matchctx.switch()
593 _switchcallers = [
592 _switchcallers = [
594 'revs',
593 'revs',
595 'status',
594 'status',
596 ]
595 ]
597
596
598 def _intree(funcs, tree):
597 def _intree(funcs, tree):
599 if isinstance(tree, tuple):
598 if isinstance(tree, tuple):
600 if tree[0] == 'func' and tree[1][0] == 'symbol':
599 if tree[0] == 'func' and tree[1][0] == 'symbol':
601 if tree[1][1] in funcs:
600 if tree[1][1] in funcs:
602 return True
601 return True
603 if tree[1][1] in _switchcallers:
602 if tree[1][1] in _switchcallers:
604 # arguments won't be evaluated in the current context
603 # arguments won't be evaluated in the current context
605 return False
604 return False
606 for s in tree[1:]:
605 for s in tree[1:]:
607 if _intree(funcs, s):
606 if _intree(funcs, s):
608 return True
607 return True
609 return False
608 return False
610
609
611 def _buildsubset(ctx, status):
610 def _buildsubset(ctx, status):
612 if status:
611 if status:
613 subset = []
612 subset = []
614 for c in status:
613 for c in status:
615 subset.extend(c)
614 subset.extend(c)
616 return subset
615 return subset
617 else:
616 else:
618 return list(ctx.walk(ctx.match([])))
617 return list(ctx.walk(ctx.match([])))
619
618
620 def getfileset(ctx, expr):
619 def getfileset(ctx, expr):
621 tree = parse(expr)
620 tree = parse(expr)
622 return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
621 return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
623
622
624 def _buildstatus(ctx, tree, basectx=None):
623 def _buildstatus(ctx, tree, basectx=None):
625 # do we need status info?
624 # do we need status info?
626
625
627 # temporaty boolean to simplify the next conditional
626 # temporaty boolean to simplify the next conditional
628 purewdir = ctx.rev() is None and basectx is None
627 purewdir = ctx.rev() is None and basectx is None
629
628
630 if (_intree(_statuscallers, tree) or
629 if (_intree(_statuscallers, tree) or
631 # Using matchctx.existing() on a workingctx requires us to check
630 # Using matchctx.existing() on a workingctx requires us to check
632 # for deleted files.
631 # for deleted files.
633 (purewdir and _intree(_existingcallers, tree))):
632 (purewdir and _intree(_existingcallers, tree))):
634 unknown = _intree(['unknown'], tree)
633 unknown = _intree(['unknown'], tree)
635 ignored = _intree(['ignored'], tree)
634 ignored = _intree(['ignored'], tree)
636
635
637 r = ctx.repo()
636 r = ctx.repo()
638 if basectx is None:
637 if basectx is None:
639 basectx = ctx.p1()
638 basectx = ctx.p1()
640 return r.status(basectx, ctx,
639 return r.status(basectx, ctx,
641 unknown=unknown, ignored=ignored, clean=True)
640 unknown=unknown, ignored=ignored, clean=True)
642 else:
641 else:
643 return None
642 return None
644
643
645 def prettyformat(tree):
644 def prettyformat(tree):
646 return parser.prettyformat(tree, ('string', 'symbol'))
645 return parser.prettyformat(tree, ('string', 'symbol'))
647
646
648 def loadpredicate(ui, extname, registrarobj):
647 def loadpredicate(ui, extname, registrarobj):
649 """Load fileset predicates from specified registrarobj
648 """Load fileset predicates from specified registrarobj
650 """
649 """
651 for name, func in registrarobj._table.iteritems():
650 for name, func in registrarobj._table.iteritems():
652 symbols[name] = func
651 symbols[name] = func
653 if func._callstatus:
652 if func._callstatus:
654 _statuscallers.add(name)
653 _statuscallers.add(name)
655 if func._callexisting:
654 if func._callexisting:
656 _existingcallers.add(name)
655 _existingcallers.add(name)
657
656
658 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
657 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
659 loadpredicate(None, None, predicate)
658 loadpredicate(None, None, predicate)
660
659
661 # tell hggettext to extract docstrings from these functions:
660 # tell hggettext to extract docstrings from these functions:
662 i18nfunctions = symbols.values()
661 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now