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