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