##// END OF EJS Templates
fileset: allow to specify a basectx for status...
Pierre-Yves David -
r31194:016c63d6 default
parent child Browse files
Show More
@@ -1,601 +1,607
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 """``revs(set, revspec)``
445
445
446 Evaluate set in the specified revisions. If the revset match multiple revs,
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.
447 this will return file matching pattern in any of the revision.
448 """
448 """
449 # i18n: "revs" is a keyword
449 # i18n: "revs" is a keyword
450 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
450 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
451 # i18n: "revs" is a keyword
451 # i18n: "revs" is a keyword
452 revspec = getstring(r, _("first argument to revs must be a revision"))
452 revspec = getstring(r, _("first argument to revs must be a revision"))
453 repo = mctx.ctx.repo()
453 repo = mctx.ctx.repo()
454 revs = scmutil.revrange(repo, [revspec])
454 revs = scmutil.revrange(repo, [revspec])
455
455
456 found = set()
456 found = set()
457 result = []
457 result = []
458 for r in revs:
458 for r in revs:
459 ctx = repo[r]
459 ctx = repo[r]
460 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
460 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
461 if f not in found:
461 if f not in found:
462 found.add(f)
462 found.add(f)
463 result.append(f)
463 result.append(f)
464 return result
464 return result
465
465
466 @predicate('subrepo([pattern])')
466 @predicate('subrepo([pattern])')
467 def subrepo(mctx, x):
467 def subrepo(mctx, x):
468 """Subrepositories whose paths match the given pattern.
468 """Subrepositories whose paths match the given pattern.
469 """
469 """
470 # i18n: "subrepo" is a keyword
470 # i18n: "subrepo" is a keyword
471 getargs(x, 0, 1, _("subrepo takes at most one argument"))
471 getargs(x, 0, 1, _("subrepo takes at most one argument"))
472 ctx = mctx.ctx
472 ctx = mctx.ctx
473 sstate = sorted(ctx.substate)
473 sstate = sorted(ctx.substate)
474 if x:
474 if x:
475 # i18n: "subrepo" is a keyword
475 # i18n: "subrepo" is a keyword
476 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
476 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
477
477
478 from . import match as matchmod # avoid circular import issues
478 from . import match as matchmod # avoid circular import issues
479 fast = not matchmod.patkind(pat)
479 fast = not matchmod.patkind(pat)
480 if fast:
480 if fast:
481 def m(s):
481 def m(s):
482 return (s == pat)
482 return (s == pat)
483 else:
483 else:
484 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
484 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
485 return [sub for sub in sstate if m(sub)]
485 return [sub for sub in sstate if m(sub)]
486 else:
486 else:
487 return [sub for sub in sstate]
487 return [sub for sub in sstate]
488
488
489 methods = {
489 methods = {
490 'string': stringset,
490 'string': stringset,
491 'symbol': stringset,
491 'symbol': stringset,
492 'and': andset,
492 'and': andset,
493 'or': orset,
493 'or': orset,
494 'minus': minusset,
494 'minus': minusset,
495 'list': listset,
495 'list': listset,
496 'group': getset,
496 'group': getset,
497 'not': notset,
497 'not': notset,
498 'func': func,
498 'func': func,
499 }
499 }
500
500
501 class matchctx(object):
501 class matchctx(object):
502 def __init__(self, ctx, subset, status=None):
502 def __init__(self, ctx, subset, status=None):
503 self.ctx = ctx
503 self.ctx = ctx
504 self.subset = subset
504 self.subset = subset
505 self._status = status
505 self._status = status
506 self._existingenabled = False
506 self._existingenabled = False
507 def status(self):
507 def status(self):
508 return self._status
508 return self._status
509 def matcher(self, patterns):
509 def matcher(self, patterns):
510 return self.ctx.match(patterns)
510 return self.ctx.match(patterns)
511 def filter(self, files):
511 def filter(self, files):
512 return [f for f in files if f in self.subset]
512 return [f for f in files if f in self.subset]
513 def existing(self):
513 def existing(self):
514 assert self._existingenabled, 'unexpected existing() invocation'
514 assert self._existingenabled, 'unexpected existing() invocation'
515 if self._status is not None:
515 if self._status is not None:
516 removed = set(self._status[3])
516 removed = set(self._status[3])
517 unknown = set(self._status[4] + self._status[5])
517 unknown = set(self._status[4] + self._status[5])
518 else:
518 else:
519 removed = set()
519 removed = set()
520 unknown = set()
520 unknown = set()
521 return (f for f in self.subset
521 return (f for f in self.subset
522 if (f in self.ctx and f not in removed) or f in unknown)
522 if (f in self.ctx and f not in removed) or f in unknown)
523 def narrow(self, files):
523 def narrow(self, files):
524 return matchctx(self.ctx, self.filter(files), self._status)
524 return matchctx(self.ctx, self.filter(files), self._status)
525 def switch(self, ctx, status=None):
525 def switch(self, ctx, status=None):
526 subset = self.filter(_buildsubset(ctx, status))
526 subset = self.filter(_buildsubset(ctx, status))
527 return matchctx(ctx, subset, status)
527 return matchctx(ctx, subset, status)
528
528
529 class fullmatchctx(matchctx):
529 class fullmatchctx(matchctx):
530 """A match context where any files in any revisions should be valid"""
530 """A match context where any files in any revisions should be valid"""
531
531
532 def __init__(self, ctx, status=None):
532 def __init__(self, ctx, status=None):
533 subset = _buildsubset(ctx, status)
533 subset = _buildsubset(ctx, status)
534 super(fullmatchctx, self).__init__(ctx, subset, status)
534 super(fullmatchctx, self).__init__(ctx, subset, status)
535 def switch(self, ctx, status=None):
535 def switch(self, ctx, status=None):
536 return fullmatchctx(ctx, status)
536 return fullmatchctx(ctx, status)
537
537
538 # filesets using matchctx.switch()
538 # filesets using matchctx.switch()
539 _switchcallers = [
539 _switchcallers = [
540 'revs',
540 'revs',
541 ]
541 ]
542
542
543 def _intree(funcs, tree):
543 def _intree(funcs, tree):
544 if isinstance(tree, tuple):
544 if isinstance(tree, tuple):
545 if tree[0] == 'func' and tree[1][0] == 'symbol':
545 if tree[0] == 'func' and tree[1][0] == 'symbol':
546 if tree[1][1] in funcs:
546 if tree[1][1] in funcs:
547 return True
547 return True
548 if tree[1][1] in _switchcallers:
548 if tree[1][1] in _switchcallers:
549 # arguments won't be evaluated in the current context
549 # arguments won't be evaluated in the current context
550 return False
550 return False
551 for s in tree[1:]:
551 for s in tree[1:]:
552 if _intree(funcs, s):
552 if _intree(funcs, s):
553 return True
553 return True
554 return False
554 return False
555
555
556 def _buildsubset(ctx, status):
556 def _buildsubset(ctx, status):
557 if status:
557 if status:
558 subset = []
558 subset = []
559 for c in status:
559 for c in status:
560 subset.extend(c)
560 subset.extend(c)
561 return subset
561 return subset
562 else:
562 else:
563 return list(ctx.walk(ctx.match([])))
563 return list(ctx.walk(ctx.match([])))
564
564
565 def getfileset(ctx, expr):
565 def getfileset(ctx, expr):
566 tree = parse(expr)
566 tree = parse(expr)
567 return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
567 return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
568
568
569 def _buildstatus(ctx, tree):
569 def _buildstatus(ctx, tree, basectx=None):
570 # do we need status info?
570 # do we need status info?
571
572 # temporaty boolean to simplify the next conditional
573 purewdir = ctx.rev() is None and basectx is None
574
571 if (_intree(_statuscallers, tree) or
575 if (_intree(_statuscallers, tree) or
572 # Using matchctx.existing() on a workingctx requires us to check
576 # Using matchctx.existing() on a workingctx requires us to check
573 # for deleted files.
577 # for deleted files.
574 (ctx.rev() is None and _intree(_existingcallers, tree))):
578 (purewdir and _intree(_existingcallers, tree))):
575 unknown = _intree(['unknown'], tree)
579 unknown = _intree(['unknown'], tree)
576 ignored = _intree(['ignored'], tree)
580 ignored = _intree(['ignored'], tree)
577
581
578 r = ctx.repo()
582 r = ctx.repo()
579 return r.status(ctx.p1(), ctx,
583 if basectx is None:
584 basectx = ctx.p1()
585 return r.status(basectx, ctx,
580 unknown=unknown, ignored=ignored, clean=True)
586 unknown=unknown, ignored=ignored, clean=True)
581 else:
587 else:
582 return None
588 return None
583
589
584 def prettyformat(tree):
590 def prettyformat(tree):
585 return parser.prettyformat(tree, ('string', 'symbol'))
591 return parser.prettyformat(tree, ('string', 'symbol'))
586
592
587 def loadpredicate(ui, extname, registrarobj):
593 def loadpredicate(ui, extname, registrarobj):
588 """Load fileset predicates from specified registrarobj
594 """Load fileset predicates from specified registrarobj
589 """
595 """
590 for name, func in registrarobj._table.iteritems():
596 for name, func in registrarobj._table.iteritems():
591 symbols[name] = func
597 symbols[name] = func
592 if func._callstatus:
598 if func._callstatus:
593 _statuscallers.add(name)
599 _statuscallers.add(name)
594 if func._callexisting:
600 if func._callexisting:
595 _existingcallers.add(name)
601 _existingcallers.add(name)
596
602
597 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
603 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
598 loadpredicate(None, None, predicate)
604 loadpredicate(None, None, predicate)
599
605
600 # tell hggettext to extract docstrings from these functions:
606 # tell hggettext to extract docstrings from these functions:
601 i18nfunctions = symbols.values()
607 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now