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