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