##// END OF EJS Templates
fileset: use ctx1.status(ctx2) instead of repo.status(ctx1, ctx2)...
Martin von Zweigbergk -
r38795:af5c0c93 default
parent child Browse files
Show More
@@ -1,673 +1,672
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 # open()-ing a directory fails with EACCES on Windows
595 # open()-ing a directory fails with EACCES on Windows
596 if e.errno in (errno.ENOENT, errno.EACCES, errno.ENOTDIR,
596 if e.errno in (errno.ENOENT, errno.EACCES, errno.ENOTDIR,
597 errno.EISDIR):
597 errno.EISDIR):
598 return False
598 return False
599 raise
599 raise
600 else:
600 else:
601 def fctxpredfn(f):
601 def fctxpredfn(f):
602 try:
602 try:
603 fctx = ctx[f]
603 fctx = ctx[f]
604 except error.LookupError:
604 except error.LookupError:
605 return False
605 return False
606 return predfn(fctx)
606 return predfn(fctx)
607 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
607 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
608
608
609 def never(self):
609 def never(self):
610 """Create a matcher to select nothing"""
610 """Create a matcher to select nothing"""
611 repo = self.ctx.repo()
611 repo = self.ctx.repo()
612 return matchmod.nevermatcher(repo.root, repo.getcwd(),
612 return matchmod.nevermatcher(repo.root, repo.getcwd(),
613 badfn=self._badfn)
613 badfn=self._badfn)
614
614
615 def switch(self, ctx, status=None):
615 def switch(self, ctx, status=None):
616 return matchctx(ctx, status, self._badfn)
616 return matchctx(ctx, status, self._badfn)
617
617
618 # filesets using matchctx.switch()
618 # filesets using matchctx.switch()
619 _switchcallers = [
619 _switchcallers = [
620 'revs',
620 'revs',
621 'status',
621 'status',
622 ]
622 ]
623
623
624 def _intree(funcs, tree):
624 def _intree(funcs, tree):
625 if isinstance(tree, tuple):
625 if isinstance(tree, tuple):
626 if tree[0] == 'func' and tree[1][0] == 'symbol':
626 if tree[0] == 'func' and tree[1][0] == 'symbol':
627 if tree[1][1] in funcs:
627 if tree[1][1] in funcs:
628 return True
628 return True
629 if tree[1][1] in _switchcallers:
629 if tree[1][1] in _switchcallers:
630 # arguments won't be evaluated in the current context
630 # arguments won't be evaluated in the current context
631 return False
631 return False
632 for s in tree[1:]:
632 for s in tree[1:]:
633 if _intree(funcs, s):
633 if _intree(funcs, s):
634 return True
634 return True
635 return False
635 return False
636
636
637 def match(ctx, expr, badfn=None):
637 def match(ctx, expr, badfn=None):
638 """Create a matcher for a single fileset expression"""
638 """Create a matcher for a single fileset expression"""
639 tree = parse(expr)
639 tree = parse(expr)
640 mctx = matchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
640 mctx = matchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
641 return getmatch(mctx, tree)
641 return getmatch(mctx, tree)
642
642
643 def _buildstatus(ctx, tree, basectx=None):
643 def _buildstatus(ctx, tree, basectx=None):
644 # do we need status info?
644 # do we need status info?
645
645
646 if _intree(_statuscallers, tree):
646 if _intree(_statuscallers, tree):
647 unknown = _intree(['unknown'], tree)
647 unknown = _intree(['unknown'], tree)
648 ignored = _intree(['ignored'], tree)
648 ignored = _intree(['ignored'], tree)
649
649
650 r = ctx.repo()
651 if basectx is None:
650 if basectx is None:
652 basectx = ctx.p1()
651 basectx = ctx.p1()
653 return r.status(basectx, ctx,
652 return basectx.status(ctx, listunknown=unknown, listignored=ignored,
654 unknown=unknown, ignored=ignored, clean=True)
653 listclean=True)
655 else:
654 else:
656 return None
655 return None
657
656
658 def prettyformat(tree):
657 def prettyformat(tree):
659 return parser.prettyformat(tree, ('string', 'symbol'))
658 return parser.prettyformat(tree, ('string', 'symbol'))
660
659
661 def loadpredicate(ui, extname, registrarobj):
660 def loadpredicate(ui, extname, registrarobj):
662 """Load fileset predicates from specified registrarobj
661 """Load fileset predicates from specified registrarobj
663 """
662 """
664 for name, func in registrarobj._table.iteritems():
663 for name, func in registrarobj._table.iteritems():
665 symbols[name] = func
664 symbols[name] = func
666 if func._callstatus:
665 if func._callstatus:
667 _statuscallers.add(name)
666 _statuscallers.add(name)
668
667
669 # load built-in predicates explicitly to setup _statuscallers
668 # load built-in predicates explicitly to setup _statuscallers
670 loadpredicate(None, None, predicate)
669 loadpredicate(None, None, predicate)
671
670
672 # tell hggettext to extract docstrings from these functions:
671 # tell hggettext to extract docstrings from these functions:
673 i18nfunctions = symbols.values()
672 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now