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