##// END OF EJS Templates
fileset: fix generator vs list bug in fast path...
Patrick Mezard -
r17371:1310489e stable
parent child Browse files
Show More
@@ -1,493 +1,493 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 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 copied(mctx, x):
356 def copied(mctx, x):
357 """``copied()``
357 """``copied()``
358 File that is recorded as being copied.
358 File that is recorded as being copied.
359 """
359 """
360 # i18n: "copied" is a keyword
360 # i18n: "copied" is a keyword
361 getargs(x, 0, 0, _("copied takes no arguments"))
361 getargs(x, 0, 0, _("copied takes no arguments"))
362 s = []
362 s = []
363 for f in mctx.subset:
363 for f in mctx.subset:
364 p = mctx.ctx[f].parents()
364 p = mctx.ctx[f].parents()
365 if p and p[0].path() != f:
365 if p and p[0].path() != f:
366 s.append(f)
366 s.append(f)
367 return s
367 return s
368
368
369 def subrepo(mctx, x):
369 def subrepo(mctx, x):
370 """``subrepo([pattern])``
370 """``subrepo([pattern])``
371 Subrepositories whose paths match the given pattern.
371 Subrepositories whose paths match the given pattern.
372 """
372 """
373 # i18n: "subrepo" is a keyword
373 # i18n: "subrepo" is a keyword
374 getargs(x, 0, 1, _("subrepo takes at most one argument"))
374 getargs(x, 0, 1, _("subrepo takes at most one argument"))
375 ctx = mctx.ctx
375 ctx = mctx.ctx
376 sstate = ctx.substate
376 sstate = ctx.substate
377 if x:
377 if x:
378 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
378 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
379
379
380 import match as matchmod # avoid circular import issues
380 import match as matchmod # avoid circular import issues
381 fast = not matchmod.patkind(pat)
381 fast = not matchmod.patkind(pat)
382 if fast:
382 if fast:
383 def m(s):
383 def m(s):
384 return (s == pat)
384 return (s == pat)
385 else:
385 else:
386 m = matchmod.match(ctx._repo.root, '', [pat], ctx=ctx)
386 m = matchmod.match(ctx._repo.root, '', [pat], ctx=ctx)
387 return [sub for sub in sstate if m(sub)]
387 return [sub for sub in sstate if m(sub)]
388 else:
388 else:
389 return [sub for sub in sstate]
389 return [sub for sub in sstate]
390
390
391 symbols = {
391 symbols = {
392 'added': added,
392 'added': added,
393 'binary': binary,
393 'binary': binary,
394 'clean': clean,
394 'clean': clean,
395 'copied': copied,
395 'copied': copied,
396 'deleted': deleted,
396 'deleted': deleted,
397 'encoding': encoding,
397 'encoding': encoding,
398 'exec': exec_,
398 'exec': exec_,
399 'grep': grep,
399 'grep': grep,
400 'ignored': ignored,
400 'ignored': ignored,
401 'hgignore': hgignore,
401 'hgignore': hgignore,
402 'modified': modified,
402 'modified': modified,
403 'removed': removed,
403 'removed': removed,
404 'resolved': resolved,
404 'resolved': resolved,
405 'size': size,
405 'size': size,
406 'symlink': symlink,
406 'symlink': symlink,
407 'unknown': unknown,
407 'unknown': unknown,
408 'unresolved': unresolved,
408 'unresolved': unresolved,
409 'subrepo': subrepo,
409 'subrepo': subrepo,
410 }
410 }
411
411
412 methods = {
412 methods = {
413 'string': stringset,
413 'string': stringset,
414 'symbol': stringset,
414 'symbol': stringset,
415 'and': andset,
415 'and': andset,
416 'or': orset,
416 'or': orset,
417 'minus': minusset,
417 'minus': minusset,
418 'list': listset,
418 'list': listset,
419 'group': getset,
419 'group': getset,
420 'not': notset,
420 'not': notset,
421 'func': func,
421 'func': func,
422 }
422 }
423
423
424 class matchctx(object):
424 class matchctx(object):
425 def __init__(self, ctx, subset=None, status=None):
425 def __init__(self, ctx, subset=None, status=None):
426 self.ctx = ctx
426 self.ctx = ctx
427 self.subset = subset
427 self.subset = subset
428 self._status = status
428 self._status = status
429 def status(self):
429 def status(self):
430 return self._status
430 return self._status
431 def matcher(self, patterns):
431 def matcher(self, patterns):
432 return self.ctx.match(patterns)
432 return self.ctx.match(patterns)
433 def filter(self, files):
433 def filter(self, files):
434 return [f for f in files if f in self.subset]
434 return [f for f in files if f in self.subset]
435 def existing(self):
435 def existing(self):
436 if self._status is not None:
436 if self._status is not None:
437 removed = set(self._status[3])
437 removed = set(self._status[3])
438 unknown = set(self._status[4] + self._status[5])
438 unknown = set(self._status[4] + self._status[5])
439 else:
439 else:
440 removed = set()
440 removed = set()
441 unknown = set()
441 unknown = set()
442 return (f for f in self.subset
442 return (f for f in self.subset
443 if (f in self.ctx and f not in removed) or f in unknown)
443 if (f in self.ctx and f not in removed) or f in unknown)
444 def narrow(self, files):
444 def narrow(self, files):
445 return matchctx(self.ctx, self.filter(files), self._status)
445 return matchctx(self.ctx, self.filter(files), self._status)
446
446
447 def _intree(funcs, tree):
447 def _intree(funcs, tree):
448 if isinstance(tree, tuple):
448 if isinstance(tree, tuple):
449 if tree[0] == 'func' and tree[1][0] == 'symbol':
449 if tree[0] == 'func' and tree[1][0] == 'symbol':
450 if tree[1][1] in funcs:
450 if tree[1][1] in funcs:
451 return True
451 return True
452 for s in tree[1:]:
452 for s in tree[1:]:
453 if _intree(funcs, s):
453 if _intree(funcs, s):
454 return True
454 return True
455 return False
455 return False
456
456
457 # filesets using matchctx.existing()
457 # filesets using matchctx.existing()
458 _existingcallers = [
458 _existingcallers = [
459 'binary',
459 'binary',
460 'exec',
460 'exec',
461 'grep',
461 'grep',
462 'size',
462 'size',
463 'symlink',
463 'symlink',
464 ]
464 ]
465
465
466 def getfileset(ctx, expr):
466 def getfileset(ctx, expr):
467 tree, pos = parse(expr)
467 tree, pos = parse(expr)
468 if (pos != len(expr)):
468 if (pos != len(expr)):
469 raise error.ParseError(_("invalid token"), pos)
469 raise error.ParseError(_("invalid token"), pos)
470
470
471 # do we need status info?
471 # do we need status info?
472 if (_intree(['modified', 'added', 'removed', 'deleted',
472 if (_intree(['modified', 'added', 'removed', 'deleted',
473 'unknown', 'ignored', 'clean'], tree) or
473 'unknown', 'ignored', 'clean'], tree) or
474 # Using matchctx.existing() on a workingctx requires us to check
474 # Using matchctx.existing() on a workingctx requires us to check
475 # for deleted files.
475 # for deleted files.
476 (ctx.rev() is None and _intree(_existingcallers, tree))):
476 (ctx.rev() is None and _intree(_existingcallers, tree))):
477 unknown = _intree(['unknown'], tree)
477 unknown = _intree(['unknown'], tree)
478 ignored = _intree(['ignored'], tree)
478 ignored = _intree(['ignored'], tree)
479
479
480 r = ctx._repo
480 r = ctx._repo
481 status = r.status(ctx.p1(), ctx,
481 status = r.status(ctx.p1(), ctx,
482 unknown=unknown, ignored=ignored, clean=True)
482 unknown=unknown, ignored=ignored, clean=True)
483 subset = []
483 subset = []
484 for c in status:
484 for c in status:
485 subset.extend(c)
485 subset.extend(c)
486 else:
486 else:
487 status = None
487 status = None
488 subset = ctx.walk(ctx.match([]))
488 subset = list(ctx.walk(ctx.match([])))
489
489
490 return getset(matchctx(ctx, subset, status), tree)
490 return getset(matchctx(ctx, subset, status), tree)
491
491
492 # tell hggettext to extract docstrings from these functions:
492 # tell hggettext to extract docstrings from these functions:
493 i18nfunctions = symbols.values()
493 i18nfunctions = symbols.values()
@@ -1,23 +1,34 b''
1 $ hg init
1 $ hg init
2 $ echo 0 > a
2 $ echo 0 > a
3 $ echo 0 > b
3 $ echo 0 > b
4 $ hg ci -A -m m
4 $ hg ci -A -m m
5 adding a
5 adding a
6 adding b
6 adding b
7 $ hg rm a
7 $ hg rm a
8 $ hg cat a
8 $ hg cat a
9 0
9 0
10 $ hg cat --decode a # more tests in test-encode
10 $ hg cat --decode a # more tests in test-encode
11 0
11 0
12 $ echo 1 > b
12 $ echo 1 > b
13 $ hg ci -m m
13 $ hg ci -m m
14 $ echo 2 > b
14 $ echo 2 > b
15 $ hg cat -r 0 a
15 $ hg cat -r 0 a
16 0
16 0
17 $ hg cat -r 0 b
17 $ hg cat -r 0 b
18 0
18 0
19 $ hg cat -r 1 a
19 $ hg cat -r 1 a
20 a: no such file in rev 7040230c159c
20 a: no such file in rev 7040230c159c
21 [1]
21 [1]
22 $ hg cat -r 1 b
22 $ hg cat -r 1 b
23 1
23 1
24
25 Test fileset
26
27 $ echo 3 > c
28 $ hg ci -Am addmore c
29 $ hg cat 'set:not(b) or a'
30 3
31 $ hg cat 'set:c or b'
32 1
33 3
34
@@ -1,225 +1,228 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
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'
226 b2
227 c1
225
228
General Comments 0
You need to be logged in to leave comments. Login now