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