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