##// END OF EJS Templates
filesets: add eol predicate
Matt Mackall -
r18842:3ce3f2b0 default
parent child Browse files
Show More
@@ -1,493 +1,517
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 import parser, error, util, merge, re
8 import parser, error, util, merge, re
9 from i18n import _
9 from i18n import _
10
10
11 elements = {
11 elements = {
12 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
12 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
13 "-": (5, ("negate", 19), ("minus", 5)),
13 "-": (5, ("negate", 19), ("minus", 5)),
14 "not": (10, ("not", 10)),
14 "not": (10, ("not", 10)),
15 "!": (10, ("not", 10)),
15 "!": (10, ("not", 10)),
16 "and": (5, None, ("and", 5)),
16 "and": (5, None, ("and", 5)),
17 "&": (5, None, ("and", 5)),
17 "&": (5, None, ("and", 5)),
18 "or": (4, None, ("or", 4)),
18 "or": (4, None, ("or", 4)),
19 "|": (4, None, ("or", 4)),
19 "|": (4, None, ("or", 4)),
20 "+": (4, None, ("or", 4)),
20 "+": (4, None, ("or", 4)),
21 ",": (2, None, ("list", 2)),
21 ",": (2, None, ("list", 2)),
22 ")": (0, None, None),
22 ")": (0, None, None),
23 "symbol": (0, ("symbol",), None),
23 "symbol": (0, ("symbol",), None),
24 "string": (0, ("string",), None),
24 "string": (0, ("string",), None),
25 "end": (0, None, None),
25 "end": (0, None, None),
26 }
26 }
27
27
28 keywords = set(['and', 'or', 'not'])
28 keywords = set(['and', 'or', 'not'])
29
29
30 globchars = ".*{}[]?/\\"
30 globchars = ".*{}[]?/\\"
31
31
32 def tokenize(program):
32 def tokenize(program):
33 pos, l = 0, len(program)
33 pos, l = 0, len(program)
34 while pos < l:
34 while pos < l:
35 c = program[pos]
35 c = program[pos]
36 if c.isspace(): # skip inter-token whitespace
36 if c.isspace(): # skip inter-token whitespace
37 pass
37 pass
38 elif c in "(),-|&+!": # handle simple operators
38 elif c in "(),-|&+!": # handle simple operators
39 yield (c, None, pos)
39 yield (c, None, pos)
40 elif (c in '"\'' or c == 'r' and
40 elif (c in '"\'' or c == 'r' and
41 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
41 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
42 if c == 'r':
42 if c == 'r':
43 pos += 1
43 pos += 1
44 c = program[pos]
44 c = program[pos]
45 decode = lambda x: x
45 decode = lambda x: x
46 else:
46 else:
47 decode = lambda x: x.decode('string-escape')
47 decode = lambda x: x.decode('string-escape')
48 pos += 1
48 pos += 1
49 s = pos
49 s = pos
50 while pos < l: # find closing quote
50 while pos < l: # find closing quote
51 d = program[pos]
51 d = program[pos]
52 if d == '\\': # skip over escaped characters
52 if d == '\\': # skip over escaped characters
53 pos += 2
53 pos += 2
54 continue
54 continue
55 if d == c:
55 if d == c:
56 yield ('string', decode(program[s:pos]), s)
56 yield ('string', decode(program[s:pos]), s)
57 break
57 break
58 pos += 1
58 pos += 1
59 else:
59 else:
60 raise error.ParseError(_("unterminated string"), s)
60 raise error.ParseError(_("unterminated string"), s)
61 elif c.isalnum() or c in globchars or ord(c) > 127:
61 elif c.isalnum() or c in globchars or ord(c) > 127:
62 # gather up a symbol/keyword
62 # gather up a symbol/keyword
63 s = pos
63 s = pos
64 pos += 1
64 pos += 1
65 while pos < l: # find end of symbol
65 while pos < l: # find end of symbol
66 d = program[pos]
66 d = program[pos]
67 if not (d.isalnum() or d in globchars or ord(d) > 127):
67 if not (d.isalnum() or d in globchars or ord(d) > 127):
68 break
68 break
69 pos += 1
69 pos += 1
70 sym = program[s:pos]
70 sym = program[s:pos]
71 if sym in keywords: # operator keywords
71 if sym in keywords: # operator keywords
72 yield (sym, None, s)
72 yield (sym, None, s)
73 else:
73 else:
74 yield ('symbol', sym, s)
74 yield ('symbol', sym, s)
75 pos -= 1
75 pos -= 1
76 else:
76 else:
77 raise error.ParseError(_("syntax error"), pos)
77 raise error.ParseError(_("syntax error"), pos)
78 pos += 1
78 pos += 1
79 yield ('end', None, pos)
79 yield ('end', None, pos)
80
80
81 parse = parser.parser(tokenize, elements).parse
81 parse = parser.parser(tokenize, elements).parse
82
82
83 def getstring(x, err):
83 def getstring(x, err):
84 if x and (x[0] == 'string' or x[0] == 'symbol'):
84 if x and (x[0] == 'string' or x[0] == 'symbol'):
85 return x[1]
85 return x[1]
86 raise error.ParseError(err)
86 raise error.ParseError(err)
87
87
88 def getset(mctx, x):
88 def getset(mctx, x):
89 if not x:
89 if not x:
90 raise error.ParseError(_("missing argument"))
90 raise error.ParseError(_("missing argument"))
91 return methods[x[0]](mctx, *x[1:])
91 return methods[x[0]](mctx, *x[1:])
92
92
93 def stringset(mctx, x):
93 def stringset(mctx, x):
94 m = mctx.matcher([x])
94 m = mctx.matcher([x])
95 return [f for f in mctx.subset if m(f)]
95 return [f for f in mctx.subset if m(f)]
96
96
97 def andset(mctx, x, y):
97 def andset(mctx, x, y):
98 return getset(mctx.narrow(getset(mctx, x)), y)
98 return getset(mctx.narrow(getset(mctx, x)), y)
99
99
100 def orset(mctx, x, y):
100 def orset(mctx, x, y):
101 # needs optimizing
101 # needs optimizing
102 xl = getset(mctx, x)
102 xl = getset(mctx, x)
103 yl = getset(mctx, y)
103 yl = getset(mctx, y)
104 return xl + [f for f in yl if f not in xl]
104 return xl + [f for f in yl if f not in xl]
105
105
106 def notset(mctx, x):
106 def notset(mctx, x):
107 s = set(getset(mctx, x))
107 s = set(getset(mctx, x))
108 return [r for r in mctx.subset if r not in s]
108 return [r for r in mctx.subset if r not in s]
109
109
110 def minusset(mctx, x, y):
110 def minusset(mctx, x, y):
111 xl = getset(mctx, x)
111 xl = getset(mctx, x)
112 yl = set(getset(mctx, y))
112 yl = set(getset(mctx, y))
113 return [f for f in xl if f not in yl]
113 return [f for f in xl if f not in yl]
114
114
115 def listset(mctx, a, b):
115 def listset(mctx, a, b):
116 raise error.ParseError(_("can't use a list in this context"))
116 raise error.ParseError(_("can't use a list in this context"))
117
117
118 def modified(mctx, x):
118 def modified(mctx, x):
119 """``modified()``
119 """``modified()``
120 File that is modified according to status.
120 File that is modified according to status.
121 """
121 """
122 # i18n: "modified" is a keyword
122 # i18n: "modified" is a keyword
123 getargs(x, 0, 0, _("modified takes no arguments"))
123 getargs(x, 0, 0, _("modified takes no arguments"))
124 s = mctx.status()[0]
124 s = mctx.status()[0]
125 return [f for f in mctx.subset if f in s]
125 return [f for f in mctx.subset if f in s]
126
126
127 def added(mctx, x):
127 def added(mctx, x):
128 """``added()``
128 """``added()``
129 File that is added according to status.
129 File that is added according to status.
130 """
130 """
131 # i18n: "added" is a keyword
131 # i18n: "added" is a keyword
132 getargs(x, 0, 0, _("added takes no arguments"))
132 getargs(x, 0, 0, _("added takes no arguments"))
133 s = mctx.status()[1]
133 s = mctx.status()[1]
134 return [f for f in mctx.subset if f in s]
134 return [f for f in mctx.subset if f in s]
135
135
136 def removed(mctx, x):
136 def removed(mctx, x):
137 """``removed()``
137 """``removed()``
138 File that is removed according to status.
138 File that is removed according to status.
139 """
139 """
140 # i18n: "removed" is a keyword
140 # i18n: "removed" is a keyword
141 getargs(x, 0, 0, _("removed takes no arguments"))
141 getargs(x, 0, 0, _("removed takes no arguments"))
142 s = mctx.status()[2]
142 s = mctx.status()[2]
143 return [f for f in mctx.subset if f in s]
143 return [f for f in mctx.subset if f in s]
144
144
145 def deleted(mctx, x):
145 def deleted(mctx, x):
146 """``deleted()``
146 """``deleted()``
147 File that is deleted according to status.
147 File that is deleted according to status.
148 """
148 """
149 # i18n: "deleted" is a keyword
149 # i18n: "deleted" is a keyword
150 getargs(x, 0, 0, _("deleted takes no arguments"))
150 getargs(x, 0, 0, _("deleted takes no arguments"))
151 s = mctx.status()[3]
151 s = mctx.status()[3]
152 return [f for f in mctx.subset if f in s]
152 return [f for f in mctx.subset if f in s]
153
153
154 def unknown(mctx, x):
154 def unknown(mctx, x):
155 """``unknown()``
155 """``unknown()``
156 File that is unknown according to status. These files will only be
156 File that is unknown according to status. These files will only be
157 considered if this predicate is used.
157 considered if this predicate is used.
158 """
158 """
159 # i18n: "unknown" is a keyword
159 # i18n: "unknown" is a keyword
160 getargs(x, 0, 0, _("unknown takes no arguments"))
160 getargs(x, 0, 0, _("unknown takes no arguments"))
161 s = mctx.status()[4]
161 s = mctx.status()[4]
162 return [f for f in mctx.subset if f in s]
162 return [f for f in mctx.subset if f in s]
163
163
164 def ignored(mctx, x):
164 def ignored(mctx, x):
165 """``ignored()``
165 """``ignored()``
166 File that is ignored according to status. These files will only be
166 File that is ignored according to status. These files will only be
167 considered if this predicate is used.
167 considered if this predicate is used.
168 """
168 """
169 # i18n: "ignored" is a keyword
169 # i18n: "ignored" is a keyword
170 getargs(x, 0, 0, _("ignored takes no arguments"))
170 getargs(x, 0, 0, _("ignored takes no arguments"))
171 s = mctx.status()[5]
171 s = mctx.status()[5]
172 return [f for f in mctx.subset if f in s]
172 return [f for f in mctx.subset if f in s]
173
173
174 def clean(mctx, x):
174 def clean(mctx, x):
175 """``clean()``
175 """``clean()``
176 File that is clean according to status.
176 File that is clean according to status.
177 """
177 """
178 # i18n: "clean" is a keyword
178 # i18n: "clean" is a keyword
179 getargs(x, 0, 0, _("clean takes no arguments"))
179 getargs(x, 0, 0, _("clean takes no arguments"))
180 s = mctx.status()[6]
180 s = mctx.status()[6]
181 return [f for f in mctx.subset if f in s]
181 return [f for f in mctx.subset if f in s]
182
182
183 def func(mctx, a, b):
183 def func(mctx, a, b):
184 if a[0] == 'symbol' and a[1] in symbols:
184 if a[0] == 'symbol' and a[1] in symbols:
185 return symbols[a[1]](mctx, b)
185 return symbols[a[1]](mctx, b)
186 raise error.ParseError(_("not a function: %s") % a[1])
186 raise error.ParseError(_("not a function: %s") % a[1])
187
187
188 def getlist(x):
188 def getlist(x):
189 if not x:
189 if not x:
190 return []
190 return []
191 if x[0] == 'list':
191 if x[0] == 'list':
192 return getlist(x[1]) + [x[2]]
192 return getlist(x[1]) + [x[2]]
193 return [x]
193 return [x]
194
194
195 def getargs(x, min, max, err):
195 def getargs(x, min, max, err):
196 l = getlist(x)
196 l = getlist(x)
197 if len(l) < min or len(l) > max:
197 if len(l) < min or len(l) > max:
198 raise error.ParseError(err)
198 raise error.ParseError(err)
199 return l
199 return l
200
200
201 def binary(mctx, x):
201 def binary(mctx, x):
202 """``binary()``
202 """``binary()``
203 File that appears to be binary (contains NUL bytes).
203 File that appears to be binary (contains NUL bytes).
204 """
204 """
205 # i18n: "binary" is a keyword
205 # i18n: "binary" is a keyword
206 getargs(x, 0, 0, _("binary takes no arguments"))
206 getargs(x, 0, 0, _("binary takes no arguments"))
207 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
207 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
208
208
209 def exec_(mctx, x):
209 def exec_(mctx, x):
210 """``exec()``
210 """``exec()``
211 File that is marked as executable.
211 File that is marked as executable.
212 """
212 """
213 # i18n: "exec" is a keyword
213 # i18n: "exec" is a keyword
214 getargs(x, 0, 0, _("exec takes no arguments"))
214 getargs(x, 0, 0, _("exec takes no arguments"))
215 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
215 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
216
216
217 def symlink(mctx, x):
217 def symlink(mctx, x):
218 """``symlink()``
218 """``symlink()``
219 File that is marked as a symlink.
219 File that is marked as a symlink.
220 """
220 """
221 # i18n: "symlink" is a keyword
221 # i18n: "symlink" is a keyword
222 getargs(x, 0, 0, _("symlink takes no arguments"))
222 getargs(x, 0, 0, _("symlink takes no arguments"))
223 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
223 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
224
224
225 def resolved(mctx, x):
225 def resolved(mctx, x):
226 """``resolved()``
226 """``resolved()``
227 File that is marked resolved according to the resolve state.
227 File that is marked resolved according to the resolve state.
228 """
228 """
229 # i18n: "resolved" is a keyword
229 # i18n: "resolved" is a keyword
230 getargs(x, 0, 0, _("resolved takes no arguments"))
230 getargs(x, 0, 0, _("resolved takes no arguments"))
231 if mctx.ctx.rev() is not None:
231 if mctx.ctx.rev() is not None:
232 return []
232 return []
233 ms = merge.mergestate(mctx.ctx._repo)
233 ms = merge.mergestate(mctx.ctx._repo)
234 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
234 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
235
235
236 def unresolved(mctx, x):
236 def unresolved(mctx, x):
237 """``unresolved()``
237 """``unresolved()``
238 File that is marked unresolved according to the resolve state.
238 File that is marked unresolved according to the resolve state.
239 """
239 """
240 # i18n: "unresolved" is a keyword
240 # i18n: "unresolved" is a keyword
241 getargs(x, 0, 0, _("unresolved takes no arguments"))
241 getargs(x, 0, 0, _("unresolved takes no arguments"))
242 if mctx.ctx.rev() is not None:
242 if mctx.ctx.rev() is not None:
243 return []
243 return []
244 ms = merge.mergestate(mctx.ctx._repo)
244 ms = merge.mergestate(mctx.ctx._repo)
245 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
245 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
246
246
247 def hgignore(mctx, x):
247 def hgignore(mctx, x):
248 """``hgignore()``
248 """``hgignore()``
249 File that matches the active .hgignore pattern.
249 File that matches the active .hgignore pattern.
250 """
250 """
251 getargs(x, 0, 0, _("hgignore takes no arguments"))
251 getargs(x, 0, 0, _("hgignore takes no arguments"))
252 ignore = mctx.ctx._repo.dirstate._ignore
252 ignore = mctx.ctx._repo.dirstate._ignore
253 return [f for f in mctx.subset if ignore(f)]
253 return [f for f in mctx.subset if ignore(f)]
254
254
255 def grep(mctx, x):
255 def grep(mctx, x):
256 """``grep(regex)``
256 """``grep(regex)``
257 File contains the given regular expression.
257 File contains the given regular expression.
258 """
258 """
259 try:
259 try:
260 # i18n: "grep" is a keyword
260 # i18n: "grep" is a keyword
261 r = re.compile(getstring(x, _("grep requires a pattern")))
261 r = re.compile(getstring(x, _("grep requires a pattern")))
262 except re.error, e:
262 except re.error, e:
263 raise error.ParseError(_('invalid match pattern: %s') % e)
263 raise error.ParseError(_('invalid match pattern: %s') % e)
264 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
264 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
265
265
266 _units = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
266 _units = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
267 M=2**20, MB=2**20, G=2**30, GB=2**30)
267 M=2**20, MB=2**20, G=2**30, GB=2**30)
268
268
269 def _sizetoint(s):
269 def _sizetoint(s):
270 try:
270 try:
271 s = s.strip()
271 s = s.strip()
272 for k, v in _units.items():
272 for k, v in _units.items():
273 if s.endswith(k):
273 if s.endswith(k):
274 return int(float(s[:-len(k)]) * v)
274 return int(float(s[:-len(k)]) * v)
275 return int(s)
275 return int(s)
276 except ValueError:
276 except ValueError:
277 raise error.ParseError(_("couldn't parse size: %s") % s)
277 raise error.ParseError(_("couldn't parse size: %s") % s)
278
278
279 def _sizetomax(s):
279 def _sizetomax(s):
280 try:
280 try:
281 s = s.strip()
281 s = s.strip()
282 for k, v in _units.items():
282 for k, v in _units.items():
283 if s.endswith(k):
283 if s.endswith(k):
284 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
284 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
285 n = s[:-len(k)]
285 n = s[:-len(k)]
286 inc = 1.0
286 inc = 1.0
287 if "." in n:
287 if "." in n:
288 inc /= 10 ** len(n.split(".")[1])
288 inc /= 10 ** len(n.split(".")[1])
289 return int((float(n) + inc) * v) - 1
289 return int((float(n) + inc) * v) - 1
290 # no extension, this is a precise value
290 # no extension, this is a precise value
291 return int(s)
291 return int(s)
292 except ValueError:
292 except ValueError:
293 raise error.ParseError(_("couldn't parse size: %s") % s)
293 raise error.ParseError(_("couldn't parse size: %s") % s)
294
294
295 def size(mctx, x):
295 def size(mctx, x):
296 """``size(expression)``
296 """``size(expression)``
297 File size matches the given expression. Examples:
297 File size matches the given expression. Examples:
298
298
299 - 1k (files from 1024 to 2047 bytes)
299 - 1k (files from 1024 to 2047 bytes)
300 - < 20k (files less than 20480 bytes)
300 - < 20k (files less than 20480 bytes)
301 - >= .5MB (files at least 524288 bytes)
301 - >= .5MB (files at least 524288 bytes)
302 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
302 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
303 """
303 """
304
304
305 # i18n: "size" is a keyword
305 # i18n: "size" is a keyword
306 expr = getstring(x, _("size requires an expression")).strip()
306 expr = getstring(x, _("size requires an expression")).strip()
307 if '-' in expr: # do we have a range?
307 if '-' in expr: # do we have a range?
308 a, b = expr.split('-', 1)
308 a, b = expr.split('-', 1)
309 a = _sizetoint(a)
309 a = _sizetoint(a)
310 b = _sizetoint(b)
310 b = _sizetoint(b)
311 m = lambda x: x >= a and x <= b
311 m = lambda x: x >= a and x <= b
312 elif expr.startswith("<="):
312 elif expr.startswith("<="):
313 a = _sizetoint(expr[2:])
313 a = _sizetoint(expr[2:])
314 m = lambda x: x <= a
314 m = lambda x: x <= a
315 elif expr.startswith("<"):
315 elif expr.startswith("<"):
316 a = _sizetoint(expr[1:])
316 a = _sizetoint(expr[1:])
317 m = lambda x: x < a
317 m = lambda x: x < a
318 elif expr.startswith(">="):
318 elif expr.startswith(">="):
319 a = _sizetoint(expr[2:])
319 a = _sizetoint(expr[2:])
320 m = lambda x: x >= a
320 m = lambda x: x >= a
321 elif expr.startswith(">"):
321 elif expr.startswith(">"):
322 a = _sizetoint(expr[1:])
322 a = _sizetoint(expr[1:])
323 m = lambda x: x > a
323 m = lambda x: x > a
324 elif expr[0].isdigit or expr[0] == '.':
324 elif expr[0].isdigit or expr[0] == '.':
325 a = _sizetoint(expr)
325 a = _sizetoint(expr)
326 b = _sizetomax(expr)
326 b = _sizetomax(expr)
327 m = lambda x: x >= a and x <= b
327 m = lambda x: x >= a and x <= b
328 else:
328 else:
329 raise error.ParseError(_("couldn't parse size: %s") % expr)
329 raise error.ParseError(_("couldn't parse size: %s") % expr)
330
330
331 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
331 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
332
332
333 def encoding(mctx, x):
333 def encoding(mctx, x):
334 """``encoding(name)``
334 """``encoding(name)``
335 File can be successfully decoded with the given character
335 File can be successfully decoded with the given character
336 encoding. May not be useful for encodings other than ASCII and
336 encoding. May not be useful for encodings other than ASCII and
337 UTF-8.
337 UTF-8.
338 """
338 """
339
339
340 # i18n: "encoding" is a keyword
340 # i18n: "encoding" is a keyword
341 enc = getstring(x, _("encoding requires an encoding name"))
341 enc = getstring(x, _("encoding requires an encoding name"))
342
342
343 s = []
343 s = []
344 for f in mctx.existing():
344 for f in mctx.existing():
345 d = mctx.ctx[f].data()
345 d = mctx.ctx[f].data()
346 try:
346 try:
347 d.decode(enc)
347 d.decode(enc)
348 except LookupError:
348 except LookupError:
349 raise util.Abort(_("unknown encoding '%s'") % enc)
349 raise util.Abort(_("unknown encoding '%s'") % enc)
350 except UnicodeDecodeError:
350 except UnicodeDecodeError:
351 continue
351 continue
352 s.append(f)
352 s.append(f)
353
353
354 return s
354 return s
355
355
356 def eol(mctx, x):
357 """``eol(style)``
358 File contains newlines of the given style (dos, unix, mac). Binary
359 files are excluded, files with mixed line endings match multiple
360 styles.
361 """
362
363 # i18n: "encoding" is a keyword
364 enc = getstring(x, _("encoding requires an encoding name"))
365
366 s = []
367 for f in mctx.existing():
368 d = mctx.ctx[f].data()
369 if util.binary(d):
370 continue
371 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
372 s.append(f)
373 elif enc == 'unix' and re.search('(?<!\r)\n', d):
374 s.append(f)
375 elif enc == 'mac' and re.search('\r(?!\n)', d):
376 s.append(f)
377 return s
378
356 def copied(mctx, x):
379 def copied(mctx, x):
357 """``copied()``
380 """``copied()``
358 File that is recorded as being copied.
381 File that is recorded as being copied.
359 """
382 """
360 # i18n: "copied" is a keyword
383 # i18n: "copied" is a keyword
361 getargs(x, 0, 0, _("copied takes no arguments"))
384 getargs(x, 0, 0, _("copied takes no arguments"))
362 s = []
385 s = []
363 for f in mctx.subset:
386 for f in mctx.subset:
364 p = mctx.ctx[f].parents()
387 p = mctx.ctx[f].parents()
365 if p and p[0].path() != f:
388 if p and p[0].path() != f:
366 s.append(f)
389 s.append(f)
367 return s
390 return s
368
391
369 def subrepo(mctx, x):
392 def subrepo(mctx, x):
370 """``subrepo([pattern])``
393 """``subrepo([pattern])``
371 Subrepositories whose paths match the given pattern.
394 Subrepositories whose paths match the given pattern.
372 """
395 """
373 # i18n: "subrepo" is a keyword
396 # i18n: "subrepo" is a keyword
374 getargs(x, 0, 1, _("subrepo takes at most one argument"))
397 getargs(x, 0, 1, _("subrepo takes at most one argument"))
375 ctx = mctx.ctx
398 ctx = mctx.ctx
376 sstate = sorted(ctx.substate)
399 sstate = sorted(ctx.substate)
377 if x:
400 if x:
378 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
401 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
379
402
380 import match as matchmod # avoid circular import issues
403 import match as matchmod # avoid circular import issues
381 fast = not matchmod.patkind(pat)
404 fast = not matchmod.patkind(pat)
382 if fast:
405 if fast:
383 def m(s):
406 def m(s):
384 return (s == pat)
407 return (s == pat)
385 else:
408 else:
386 m = matchmod.match(ctx._repo.root, '', [pat], ctx=ctx)
409 m = matchmod.match(ctx._repo.root, '', [pat], ctx=ctx)
387 return [sub for sub in sstate if m(sub)]
410 return [sub for sub in sstate if m(sub)]
388 else:
411 else:
389 return [sub for sub in sstate]
412 return [sub for sub in sstate]
390
413
391 symbols = {
414 symbols = {
392 'added': added,
415 'added': added,
393 'binary': binary,
416 'binary': binary,
394 'clean': clean,
417 'clean': clean,
395 'copied': copied,
418 'copied': copied,
396 'deleted': deleted,
419 'deleted': deleted,
397 'encoding': encoding,
420 'encoding': encoding,
421 'eol': eol,
398 'exec': exec_,
422 'exec': exec_,
399 'grep': grep,
423 'grep': grep,
400 'ignored': ignored,
424 'ignored': ignored,
401 'hgignore': hgignore,
425 'hgignore': hgignore,
402 'modified': modified,
426 'modified': modified,
403 'removed': removed,
427 'removed': removed,
404 'resolved': resolved,
428 'resolved': resolved,
405 'size': size,
429 'size': size,
406 'symlink': symlink,
430 'symlink': symlink,
407 'unknown': unknown,
431 'unknown': unknown,
408 'unresolved': unresolved,
432 'unresolved': unresolved,
409 'subrepo': subrepo,
433 'subrepo': subrepo,
410 }
434 }
411
435
412 methods = {
436 methods = {
413 'string': stringset,
437 'string': stringset,
414 'symbol': stringset,
438 'symbol': stringset,
415 'and': andset,
439 'and': andset,
416 'or': orset,
440 'or': orset,
417 'minus': minusset,
441 'minus': minusset,
418 'list': listset,
442 'list': listset,
419 'group': getset,
443 'group': getset,
420 'not': notset,
444 'not': notset,
421 'func': func,
445 'func': func,
422 }
446 }
423
447
424 class matchctx(object):
448 class matchctx(object):
425 def __init__(self, ctx, subset=None, status=None):
449 def __init__(self, ctx, subset=None, status=None):
426 self.ctx = ctx
450 self.ctx = ctx
427 self.subset = subset
451 self.subset = subset
428 self._status = status
452 self._status = status
429 def status(self):
453 def status(self):
430 return self._status
454 return self._status
431 def matcher(self, patterns):
455 def matcher(self, patterns):
432 return self.ctx.match(patterns)
456 return self.ctx.match(patterns)
433 def filter(self, files):
457 def filter(self, files):
434 return [f for f in files if f in self.subset]
458 return [f for f in files if f in self.subset]
435 def existing(self):
459 def existing(self):
436 if self._status is not None:
460 if self._status is not None:
437 removed = set(self._status[3])
461 removed = set(self._status[3])
438 unknown = set(self._status[4] + self._status[5])
462 unknown = set(self._status[4] + self._status[5])
439 else:
463 else:
440 removed = set()
464 removed = set()
441 unknown = set()
465 unknown = set()
442 return (f for f in self.subset
466 return (f for f in self.subset
443 if (f in self.ctx and f not in removed) or f in unknown)
467 if (f in self.ctx and f not in removed) or f in unknown)
444 def narrow(self, files):
468 def narrow(self, files):
445 return matchctx(self.ctx, self.filter(files), self._status)
469 return matchctx(self.ctx, self.filter(files), self._status)
446
470
447 def _intree(funcs, tree):
471 def _intree(funcs, tree):
448 if isinstance(tree, tuple):
472 if isinstance(tree, tuple):
449 if tree[0] == 'func' and tree[1][0] == 'symbol':
473 if tree[0] == 'func' and tree[1][0] == 'symbol':
450 if tree[1][1] in funcs:
474 if tree[1][1] in funcs:
451 return True
475 return True
452 for s in tree[1:]:
476 for s in tree[1:]:
453 if _intree(funcs, s):
477 if _intree(funcs, s):
454 return True
478 return True
455 return False
479 return False
456
480
457 # filesets using matchctx.existing()
481 # filesets using matchctx.existing()
458 _existingcallers = [
482 _existingcallers = [
459 'binary',
483 'binary',
460 'exec',
484 'exec',
461 'grep',
485 'grep',
462 'size',
486 'size',
463 'symlink',
487 'symlink',
464 ]
488 ]
465
489
466 def getfileset(ctx, expr):
490 def getfileset(ctx, expr):
467 tree, pos = parse(expr)
491 tree, pos = parse(expr)
468 if (pos != len(expr)):
492 if (pos != len(expr)):
469 raise error.ParseError(_("invalid token"), pos)
493 raise error.ParseError(_("invalid token"), pos)
470
494
471 # do we need status info?
495 # do we need status info?
472 if (_intree(['modified', 'added', 'removed', 'deleted',
496 if (_intree(['modified', 'added', 'removed', 'deleted',
473 'unknown', 'ignored', 'clean'], tree) or
497 'unknown', 'ignored', 'clean'], tree) or
474 # Using matchctx.existing() on a workingctx requires us to check
498 # Using matchctx.existing() on a workingctx requires us to check
475 # for deleted files.
499 # for deleted files.
476 (ctx.rev() is None and _intree(_existingcallers, tree))):
500 (ctx.rev() is None and _intree(_existingcallers, tree))):
477 unknown = _intree(['unknown'], tree)
501 unknown = _intree(['unknown'], tree)
478 ignored = _intree(['ignored'], tree)
502 ignored = _intree(['ignored'], tree)
479
503
480 r = ctx._repo
504 r = ctx._repo
481 status = r.status(ctx.p1(), ctx,
505 status = r.status(ctx.p1(), ctx,
482 unknown=unknown, ignored=ignored, clean=True)
506 unknown=unknown, ignored=ignored, clean=True)
483 subset = []
507 subset = []
484 for c in status:
508 for c in status:
485 subset.extend(c)
509 subset.extend(c)
486 else:
510 else:
487 status = None
511 status = None
488 subset = list(ctx.walk(ctx.match([])))
512 subset = list(ctx.walk(ctx.match([])))
489
513
490 return getset(matchctx(ctx, subset, status), tree)
514 return getset(matchctx(ctx, subset, status), tree)
491
515
492 # tell hggettext to extract docstrings from these functions:
516 # tell hggettext to extract docstrings from these functions:
493 i18nfunctions = symbols.values()
517 i18nfunctions = symbols.values()
@@ -1,228 +1,246
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 a1
19 $ fileset a1
20 a1
20 a1
21 $ fileset 'a*'
21 $ fileset 'a*'
22 a1
22 a1
23 a2
23 a2
24 $ fileset '"re:a\d"'
24 $ fileset '"re:a\d"'
25 a1
25 a1
26 a2
26 a2
27 $ fileset 'a1 or a2'
27 $ fileset 'a1 or a2'
28 a1
28 a1
29 a2
29 a2
30 $ fileset 'a1 | a2'
30 $ fileset 'a1 | a2'
31 a1
31 a1
32 a2
32 a2
33 $ fileset 'a* and "*1"'
33 $ fileset 'a* and "*1"'
34 a1
34 a1
35 $ fileset 'a* & "*1"'
35 $ fileset 'a* & "*1"'
36 a1
36 a1
37 $ fileset 'not (r"a*")'
37 $ fileset 'not (r"a*")'
38 b1
38 b1
39 b2
39 b2
40 $ fileset '! ("a*")'
40 $ fileset '! ("a*")'
41 b1
41 b1
42 b2
42 b2
43 $ fileset 'a* - a1'
43 $ fileset 'a* - a1'
44 a2
44 a2
45
45
46 Test files status
46 Test files status
47
47
48 $ rm a1
48 $ rm a1
49 $ hg rm a2
49 $ hg rm a2
50 $ echo b >> b2
50 $ echo b >> b2
51 $ hg cp b1 c1
51 $ hg cp b1 c1
52 $ echo c > c2
52 $ echo c > c2
53 $ echo c > c3
53 $ echo c > c3
54 $ cat > .hgignore <<EOF
54 $ cat > .hgignore <<EOF
55 > \.hgignore
55 > \.hgignore
56 > 2$
56 > 2$
57 > EOF
57 > EOF
58 $ fileset 'modified()'
58 $ fileset 'modified()'
59 b2
59 b2
60 $ fileset 'added()'
60 $ fileset 'added()'
61 c1
61 c1
62 $ fileset 'removed()'
62 $ fileset 'removed()'
63 a2
63 a2
64 $ fileset 'deleted()'
64 $ fileset 'deleted()'
65 a1
65 a1
66 $ fileset 'unknown()'
66 $ fileset 'unknown()'
67 c3
67 c3
68 $ fileset 'ignored()'
68 $ fileset 'ignored()'
69 .hgignore
69 .hgignore
70 c2
70 c2
71 $ fileset 'hgignore()'
71 $ fileset 'hgignore()'
72 a2
72 a2
73 b2
73 b2
74 $ fileset 'clean()'
74 $ fileset 'clean()'
75 b1
75 b1
76 $ fileset 'copied()'
76 $ fileset 'copied()'
77 c1
77 c1
78
78
79 Test files properties
79 Test files properties
80
80
81 >>> file('bin', 'wb').write('\0a')
81 >>> file('bin', 'wb').write('\0a')
82 $ fileset 'binary()'
82 $ fileset 'binary()'
83 $ fileset 'binary() and unknown()'
83 $ fileset 'binary() and unknown()'
84 bin
84 bin
85 $ echo '^bin$' >> .hgignore
85 $ echo '^bin$' >> .hgignore
86 $ fileset 'binary() and ignored()'
86 $ fileset 'binary() and ignored()'
87 bin
87 bin
88 $ hg add bin
88 $ hg add bin
89 $ fileset 'binary()'
89 $ fileset 'binary()'
90 bin
90 bin
91
91
92 $ fileset 'grep("b{1}")'
92 $ fileset 'grep("b{1}")'
93 b2
93 b2
94 c1
94 c1
95 b1
95 b1
96 $ fileset 'grep("missingparens(")'
96 $ fileset 'grep("missingparens(")'
97 hg: parse error: invalid match pattern: unbalanced parenthesis
97 hg: parse error: invalid match pattern: unbalanced parenthesis
98 [255]
98 [255]
99
99
100 #if execbit
100 #if execbit
101 $ chmod +x b2
101 $ chmod +x b2
102 $ fileset 'exec()'
102 $ fileset 'exec()'
103 b2
103 b2
104 #endif
104 #endif
105
105
106 #if symlink
106 #if symlink
107 $ ln -s b2 b2link
107 $ ln -s b2 b2link
108 $ fileset 'symlink() and unknown()'
108 $ fileset 'symlink() and unknown()'
109 b2link
109 b2link
110 $ hg add b2link
110 $ hg add b2link
111 #endif
111 #endif
112
112
113 >>> file('1k', 'wb').write(' '*1024)
113 >>> file('1k', 'wb').write(' '*1024)
114 >>> file('2k', 'wb').write(' '*2048)
114 >>> file('2k', 'wb').write(' '*2048)
115 $ hg add 1k 2k
115 $ hg add 1k 2k
116 $ fileset 'size("bar")'
116 $ fileset 'size("bar")'
117 hg: parse error: couldn't parse size: bar
117 hg: parse error: couldn't parse size: bar
118 [255]
118 [255]
119 $ fileset 'size(1k)'
119 $ fileset 'size(1k)'
120 1k
120 1k
121 $ fileset '(1k or 2k) and size("< 2k")'
121 $ fileset '(1k or 2k) and size("< 2k")'
122 1k
122 1k
123 $ fileset '(1k or 2k) and size("<=2k")'
123 $ fileset '(1k or 2k) and size("<=2k")'
124 1k
124 1k
125 2k
125 2k
126 $ fileset '(1k or 2k) and size("> 1k")'
126 $ fileset '(1k or 2k) and size("> 1k")'
127 2k
127 2k
128 $ fileset '(1k or 2k) and size(">=1K")'
128 $ fileset '(1k or 2k) and size(">=1K")'
129 1k
129 1k
130 2k
130 2k
131 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
131 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
132 1k
132 1k
133
133
134 Test merge states
134 Test merge states
135
135
136 $ hg ci -m manychanges
136 $ hg ci -m manychanges
137 $ hg up -C 0
137 $ hg up -C 0
138 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
138 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
139 $ echo c >> b2
139 $ echo c >> b2
140 $ hg ci -m diverging b2
140 $ hg ci -m diverging b2
141 created new head
141 created new head
142 $ fileset 'resolved()'
142 $ fileset 'resolved()'
143 $ fileset 'unresolved()'
143 $ fileset 'unresolved()'
144 $ hg merge
144 $ hg merge
145 merging b2
145 merging b2
146 warning: conflicts during merge.
146 warning: conflicts during merge.
147 merging b2 incomplete! (edit conflicts, then use 'hg resolve --mark')
147 merging b2 incomplete! (edit conflicts, then use 'hg resolve --mark')
148 * files updated, 0 files merged, * files removed, 1 files unresolved (glob)
148 * files updated, 0 files merged, * files removed, 1 files unresolved (glob)
149 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
149 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
150 [1]
150 [1]
151 $ fileset 'resolved()'
151 $ fileset 'resolved()'
152 $ fileset 'unresolved()'
152 $ fileset 'unresolved()'
153 b2
153 b2
154 $ echo e > b2
154 $ echo e > b2
155 $ hg resolve -m b2
155 $ hg resolve -m b2
156 $ fileset 'resolved()'
156 $ fileset 'resolved()'
157 b2
157 b2
158 $ fileset 'unresolved()'
158 $ fileset 'unresolved()'
159 $ hg ci -m merge
159 $ hg ci -m merge
160
160
161 Test subrepo predicate
161 Test subrepo predicate
162
162
163 $ hg init sub
163 $ hg init sub
164 $ echo a > sub/suba
164 $ echo a > sub/suba
165 $ hg -R sub add sub/suba
165 $ hg -R sub add sub/suba
166 $ hg -R sub ci -m sub
166 $ hg -R sub ci -m sub
167 $ echo 'sub = sub' > .hgsub
167 $ echo 'sub = sub' > .hgsub
168 $ fileset 'subrepo()'
168 $ fileset 'subrepo()'
169 $ hg add .hgsub
169 $ hg add .hgsub
170 $ fileset 'subrepo()'
170 $ fileset 'subrepo()'
171 sub
171 sub
172 $ fileset 'subrepo("sub")'
172 $ fileset 'subrepo("sub")'
173 sub
173 sub
174 $ fileset 'subrepo("glob:*")'
174 $ fileset 'subrepo("glob:*")'
175 sub
175 sub
176 $ hg ci -m subrepo
176 $ hg ci -m subrepo
177
177
178 Test with a revision
178 Test with a revision
179
179
180 $ hg log -G --template '{rev} {desc}\n'
180 $ hg log -G --template '{rev} {desc}\n'
181 @ 4 subrepo
181 @ 4 subrepo
182 |
182 |
183 o 3 merge
183 o 3 merge
184 |\
184 |\
185 | o 2 diverging
185 | o 2 diverging
186 | |
186 | |
187 o | 1 manychanges
187 o | 1 manychanges
188 |/
188 |/
189 o 0 addfiles
189 o 0 addfiles
190
190
191 $ echo unknown > unknown
191 $ echo unknown > unknown
192 $ fileset -r1 'modified()'
192 $ fileset -r1 'modified()'
193 b2
193 b2
194 $ fileset -r1 'added() and c1'
194 $ fileset -r1 'added() and c1'
195 c1
195 c1
196 $ fileset -r1 'removed()'
196 $ fileset -r1 'removed()'
197 a2
197 a2
198 $ fileset -r1 'deleted()'
198 $ fileset -r1 'deleted()'
199 $ fileset -r1 'unknown()'
199 $ fileset -r1 'unknown()'
200 $ fileset -r1 'ignored()'
200 $ fileset -r1 'ignored()'
201 $ fileset -r1 'hgignore()'
201 $ fileset -r1 'hgignore()'
202 b2
202 b2
203 bin
203 bin
204 $ fileset -r1 'binary()'
204 $ fileset -r1 'binary()'
205 bin
205 bin
206 $ fileset -r1 'size(1k)'
206 $ fileset -r1 'size(1k)'
207 1k
207 1k
208 $ fileset -r3 'resolved()'
208 $ fileset -r3 'resolved()'
209 $ fileset -r3 'unresolved()'
209 $ fileset -r3 'unresolved()'
210
210
211 #if execbit
211 #if execbit
212 $ fileset -r1 'exec()'
212 $ fileset -r1 'exec()'
213 b2
213 b2
214 #endif
214 #endif
215
215
216 #if symlink
216 #if symlink
217 $ fileset -r1 'symlink()'
217 $ fileset -r1 'symlink()'
218 b2link
218 b2link
219 #endif
219 #endif
220
220
221 $ fileset -r4 'subrepo("re:su.*")'
221 $ fileset -r4 'subrepo("re:su.*")'
222 sub
222 sub
223 $ fileset -r4 'subrepo("sub")'
223 $ fileset -r4 'subrepo("sub")'
224 sub
224 sub
225 $ fileset -r4 'b2 or c1'
225 $ fileset -r4 'b2 or c1'
226 b2
226 b2
227 c1
227 c1
228
228
229 >>> open('dos', 'wb').write("dos\r\n")
230 >>> open('mixed', 'wb').write("dos\r\nunix\n")
231 >>> open('mac', 'wb').write("mac\r")
232 $ hg add dos mixed mac
233
234 $ fileset 'eol(dos)'
235 dos
236 mixed
237 $ fileset 'eol(unix)'
238 .hgsub
239 .hgsubstate
240 a1
241 b1
242 b2
243 c1
244 mixed
245 $ fileset 'eol(mac)'
246 mac
General Comments 0
You need to be logged in to leave comments. Login now