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