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