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