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