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