##// END OF EJS Templates
revset: add a utility for obtaining the source of a given rev...
Matt Harbison -
r17003:42c47287 default
parent child Browse files
Show More
@@ -1,1707 +1,1717 b''
1 # revset.py - revision set queries for mercurial
1 # revset.py - revision 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, util, error, discovery, hbisect, phases
9 import parser, util, error, discovery, hbisect, phases
10 import node
10 import node
11 import bookmarks as bookmarksmod
11 import bookmarks as bookmarksmod
12 import match as matchmod
12 import match as matchmod
13 from i18n import _
13 from i18n import _
14 import encoding
14 import encoding
15
15
16 def _revancestors(repo, revs, followfirst):
16 def _revancestors(repo, revs, followfirst):
17 """Like revlog.ancestors(), but supports followfirst."""
17 """Like revlog.ancestors(), but supports followfirst."""
18 cut = followfirst and 1 or None
18 cut = followfirst and 1 or None
19 cl = repo.changelog
19 cl = repo.changelog
20 visit = util.deque(revs)
20 visit = util.deque(revs)
21 seen = set([node.nullrev])
21 seen = set([node.nullrev])
22 while visit:
22 while visit:
23 for parent in cl.parentrevs(visit.popleft())[:cut]:
23 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 if parent not in seen:
24 if parent not in seen:
25 visit.append(parent)
25 visit.append(parent)
26 seen.add(parent)
26 seen.add(parent)
27 yield parent
27 yield parent
28
28
29 def _revdescendants(repo, revs, followfirst):
29 def _revdescendants(repo, revs, followfirst):
30 """Like revlog.descendants() but supports followfirst."""
30 """Like revlog.descendants() but supports followfirst."""
31 cut = followfirst and 1 or None
31 cut = followfirst and 1 or None
32 cl = repo.changelog
32 cl = repo.changelog
33 first = min(revs)
33 first = min(revs)
34 nullrev = node.nullrev
34 nullrev = node.nullrev
35 if first == nullrev:
35 if first == nullrev:
36 # Are there nodes with a null first parent and a non-null
36 # Are there nodes with a null first parent and a non-null
37 # second one? Maybe. Do we care? Probably not.
37 # second one? Maybe. Do we care? Probably not.
38 for i in cl:
38 for i in cl:
39 yield i
39 yield i
40 return
40 return
41
41
42 seen = set(revs)
42 seen = set(revs)
43 for i in xrange(first + 1, len(cl)):
43 for i in xrange(first + 1, len(cl)):
44 for x in cl.parentrevs(i)[:cut]:
44 for x in cl.parentrevs(i)[:cut]:
45 if x != nullrev and x in seen:
45 if x != nullrev and x in seen:
46 seen.add(i)
46 seen.add(i)
47 yield i
47 yield i
48 break
48 break
49
49
50 def _revsbetween(repo, roots, heads):
50 def _revsbetween(repo, roots, heads):
51 """Return all paths between roots and heads, inclusive of both endpoint
51 """Return all paths between roots and heads, inclusive of both endpoint
52 sets."""
52 sets."""
53 if not roots:
53 if not roots:
54 return []
54 return []
55 parentrevs = repo.changelog.parentrevs
55 parentrevs = repo.changelog.parentrevs
56 visit = heads[:]
56 visit = heads[:]
57 reachable = set()
57 reachable = set()
58 seen = {}
58 seen = {}
59 minroot = min(roots)
59 minroot = min(roots)
60 roots = set(roots)
60 roots = set(roots)
61 # open-code the post-order traversal due to the tiny size of
61 # open-code the post-order traversal due to the tiny size of
62 # sys.getrecursionlimit()
62 # sys.getrecursionlimit()
63 while visit:
63 while visit:
64 rev = visit.pop()
64 rev = visit.pop()
65 if rev in roots:
65 if rev in roots:
66 reachable.add(rev)
66 reachable.add(rev)
67 parents = parentrevs(rev)
67 parents = parentrevs(rev)
68 seen[rev] = parents
68 seen[rev] = parents
69 for parent in parents:
69 for parent in parents:
70 if parent >= minroot and parent not in seen:
70 if parent >= minroot and parent not in seen:
71 visit.append(parent)
71 visit.append(parent)
72 if not reachable:
72 if not reachable:
73 return []
73 return []
74 for rev in sorted(seen):
74 for rev in sorted(seen):
75 for parent in seen[rev]:
75 for parent in seen[rev]:
76 if parent in reachable:
76 if parent in reachable:
77 reachable.add(rev)
77 reachable.add(rev)
78 return sorted(reachable)
78 return sorted(reachable)
79
79
80 elements = {
80 elements = {
81 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
81 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
82 "~": (18, None, ("ancestor", 18)),
82 "~": (18, None, ("ancestor", 18)),
83 "^": (18, None, ("parent", 18), ("parentpost", 18)),
83 "^": (18, None, ("parent", 18), ("parentpost", 18)),
84 "-": (5, ("negate", 19), ("minus", 5)),
84 "-": (5, ("negate", 19), ("minus", 5)),
85 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
85 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
86 ("dagrangepost", 17)),
86 ("dagrangepost", 17)),
87 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
87 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
88 ("dagrangepost", 17)),
88 ("dagrangepost", 17)),
89 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
89 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
90 "not": (10, ("not", 10)),
90 "not": (10, ("not", 10)),
91 "!": (10, ("not", 10)),
91 "!": (10, ("not", 10)),
92 "and": (5, None, ("and", 5)),
92 "and": (5, None, ("and", 5)),
93 "&": (5, None, ("and", 5)),
93 "&": (5, None, ("and", 5)),
94 "or": (4, None, ("or", 4)),
94 "or": (4, None, ("or", 4)),
95 "|": (4, None, ("or", 4)),
95 "|": (4, None, ("or", 4)),
96 "+": (4, None, ("or", 4)),
96 "+": (4, None, ("or", 4)),
97 ",": (2, None, ("list", 2)),
97 ",": (2, None, ("list", 2)),
98 ")": (0, None, None),
98 ")": (0, None, None),
99 "symbol": (0, ("symbol",), None),
99 "symbol": (0, ("symbol",), None),
100 "string": (0, ("string",), None),
100 "string": (0, ("string",), None),
101 "end": (0, None, None),
101 "end": (0, None, None),
102 }
102 }
103
103
104 keywords = set(['and', 'or', 'not'])
104 keywords = set(['and', 'or', 'not'])
105
105
106 def tokenize(program):
106 def tokenize(program):
107 pos, l = 0, len(program)
107 pos, l = 0, len(program)
108 while pos < l:
108 while pos < l:
109 c = program[pos]
109 c = program[pos]
110 if c.isspace(): # skip inter-token whitespace
110 if c.isspace(): # skip inter-token whitespace
111 pass
111 pass
112 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
112 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
113 yield ('::', None, pos)
113 yield ('::', None, pos)
114 pos += 1 # skip ahead
114 pos += 1 # skip ahead
115 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
115 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
116 yield ('..', None, pos)
116 yield ('..', None, pos)
117 pos += 1 # skip ahead
117 pos += 1 # skip ahead
118 elif c in "():,-|&+!~^": # handle simple operators
118 elif c in "():,-|&+!~^": # handle simple operators
119 yield (c, None, pos)
119 yield (c, None, pos)
120 elif (c in '"\'' or c == 'r' and
120 elif (c in '"\'' or c == 'r' and
121 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
121 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
122 if c == 'r':
122 if c == 'r':
123 pos += 1
123 pos += 1
124 c = program[pos]
124 c = program[pos]
125 decode = lambda x: x
125 decode = lambda x: x
126 else:
126 else:
127 decode = lambda x: x.decode('string-escape')
127 decode = lambda x: x.decode('string-escape')
128 pos += 1
128 pos += 1
129 s = pos
129 s = pos
130 while pos < l: # find closing quote
130 while pos < l: # find closing quote
131 d = program[pos]
131 d = program[pos]
132 if d == '\\': # skip over escaped characters
132 if d == '\\': # skip over escaped characters
133 pos += 2
133 pos += 2
134 continue
134 continue
135 if d == c:
135 if d == c:
136 yield ('string', decode(program[s:pos]), s)
136 yield ('string', decode(program[s:pos]), s)
137 break
137 break
138 pos += 1
138 pos += 1
139 else:
139 else:
140 raise error.ParseError(_("unterminated string"), s)
140 raise error.ParseError(_("unterminated string"), s)
141 # gather up a symbol/keyword
141 # gather up a symbol/keyword
142 elif c.isalnum() or c in '._' or ord(c) > 127:
142 elif c.isalnum() or c in '._' or ord(c) > 127:
143 s = pos
143 s = pos
144 pos += 1
144 pos += 1
145 while pos < l: # find end of symbol
145 while pos < l: # find end of symbol
146 d = program[pos]
146 d = program[pos]
147 if not (d.isalnum() or d in "._/" or ord(d) > 127):
147 if not (d.isalnum() or d in "._/" or ord(d) > 127):
148 break
148 break
149 if d == '.' and program[pos - 1] == '.': # special case for ..
149 if d == '.' and program[pos - 1] == '.': # special case for ..
150 pos -= 1
150 pos -= 1
151 break
151 break
152 pos += 1
152 pos += 1
153 sym = program[s:pos]
153 sym = program[s:pos]
154 if sym in keywords: # operator keywords
154 if sym in keywords: # operator keywords
155 yield (sym, None, s)
155 yield (sym, None, s)
156 else:
156 else:
157 yield ('symbol', sym, s)
157 yield ('symbol', sym, s)
158 pos -= 1
158 pos -= 1
159 else:
159 else:
160 raise error.ParseError(_("syntax error"), pos)
160 raise error.ParseError(_("syntax error"), pos)
161 pos += 1
161 pos += 1
162 yield ('end', None, pos)
162 yield ('end', None, pos)
163
163
164 # helpers
164 # helpers
165
165
166 def getstring(x, err):
166 def getstring(x, err):
167 if x and (x[0] == 'string' or x[0] == 'symbol'):
167 if x and (x[0] == 'string' or x[0] == 'symbol'):
168 return x[1]
168 return x[1]
169 raise error.ParseError(err)
169 raise error.ParseError(err)
170
170
171 def getlist(x):
171 def getlist(x):
172 if not x:
172 if not x:
173 return []
173 return []
174 if x[0] == 'list':
174 if x[0] == 'list':
175 return getlist(x[1]) + [x[2]]
175 return getlist(x[1]) + [x[2]]
176 return [x]
176 return [x]
177
177
178 def getargs(x, min, max, err):
178 def getargs(x, min, max, err):
179 l = getlist(x)
179 l = getlist(x)
180 if len(l) < min or (max >= 0 and len(l) > max):
180 if len(l) < min or (max >= 0 and len(l) > max):
181 raise error.ParseError(err)
181 raise error.ParseError(err)
182 return l
182 return l
183
183
184 def getset(repo, subset, x):
184 def getset(repo, subset, x):
185 if not x:
185 if not x:
186 raise error.ParseError(_("missing argument"))
186 raise error.ParseError(_("missing argument"))
187 return methods[x[0]](repo, subset, *x[1:])
187 return methods[x[0]](repo, subset, *x[1:])
188
188
189 def _getrevsource(repo, r):
190 extra = repo[r].extra()
191 for label in ('source', 'transplant_source', 'rebase_source'):
192 if label in extra:
193 try:
194 return repo[extra[label]].rev()
195 except error.RepoLookupError:
196 pass
197 return None
198
189 # operator methods
199 # operator methods
190
200
191 def stringset(repo, subset, x):
201 def stringset(repo, subset, x):
192 x = repo[x].rev()
202 x = repo[x].rev()
193 if x == -1 and len(subset) == len(repo):
203 if x == -1 and len(subset) == len(repo):
194 return [-1]
204 return [-1]
195 if len(subset) == len(repo) or x in subset:
205 if len(subset) == len(repo) or x in subset:
196 return [x]
206 return [x]
197 return []
207 return []
198
208
199 def symbolset(repo, subset, x):
209 def symbolset(repo, subset, x):
200 if x in symbols:
210 if x in symbols:
201 raise error.ParseError(_("can't use %s here") % x)
211 raise error.ParseError(_("can't use %s here") % x)
202 return stringset(repo, subset, x)
212 return stringset(repo, subset, x)
203
213
204 def rangeset(repo, subset, x, y):
214 def rangeset(repo, subset, x, y):
205 m = getset(repo, subset, x)
215 m = getset(repo, subset, x)
206 if not m:
216 if not m:
207 m = getset(repo, range(len(repo)), x)
217 m = getset(repo, range(len(repo)), x)
208
218
209 n = getset(repo, subset, y)
219 n = getset(repo, subset, y)
210 if not n:
220 if not n:
211 n = getset(repo, range(len(repo)), y)
221 n = getset(repo, range(len(repo)), y)
212
222
213 if not m or not n:
223 if not m or not n:
214 return []
224 return []
215 m, n = m[0], n[-1]
225 m, n = m[0], n[-1]
216
226
217 if m < n:
227 if m < n:
218 r = range(m, n + 1)
228 r = range(m, n + 1)
219 else:
229 else:
220 r = range(m, n - 1, -1)
230 r = range(m, n - 1, -1)
221 s = set(subset)
231 s = set(subset)
222 return [x for x in r if x in s]
232 return [x for x in r if x in s]
223
233
224 def dagrange(repo, subset, x, y):
234 def dagrange(repo, subset, x, y):
225 if subset:
235 if subset:
226 r = range(len(repo))
236 r = range(len(repo))
227 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
237 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
228 s = set(subset)
238 s = set(subset)
229 return [r for r in xs if r in s]
239 return [r for r in xs if r in s]
230 return []
240 return []
231
241
232 def andset(repo, subset, x, y):
242 def andset(repo, subset, x, y):
233 return getset(repo, getset(repo, subset, x), y)
243 return getset(repo, getset(repo, subset, x), y)
234
244
235 def orset(repo, subset, x, y):
245 def orset(repo, subset, x, y):
236 xl = getset(repo, subset, x)
246 xl = getset(repo, subset, x)
237 s = set(xl)
247 s = set(xl)
238 yl = getset(repo, [r for r in subset if r not in s], y)
248 yl = getset(repo, [r for r in subset if r not in s], y)
239 return xl + yl
249 return xl + yl
240
250
241 def notset(repo, subset, x):
251 def notset(repo, subset, x):
242 s = set(getset(repo, subset, x))
252 s = set(getset(repo, subset, x))
243 return [r for r in subset if r not in s]
253 return [r for r in subset if r not in s]
244
254
245 def listset(repo, subset, a, b):
255 def listset(repo, subset, a, b):
246 raise error.ParseError(_("can't use a list in this context"))
256 raise error.ParseError(_("can't use a list in this context"))
247
257
248 def func(repo, subset, a, b):
258 def func(repo, subset, a, b):
249 if a[0] == 'symbol' and a[1] in symbols:
259 if a[0] == 'symbol' and a[1] in symbols:
250 return symbols[a[1]](repo, subset, b)
260 return symbols[a[1]](repo, subset, b)
251 raise error.ParseError(_("not a function: %s") % a[1])
261 raise error.ParseError(_("not a function: %s") % a[1])
252
262
253 # functions
263 # functions
254
264
255 def adds(repo, subset, x):
265 def adds(repo, subset, x):
256 """``adds(pattern)``
266 """``adds(pattern)``
257 Changesets that add a file matching pattern.
267 Changesets that add a file matching pattern.
258 """
268 """
259 # i18n: "adds" is a keyword
269 # i18n: "adds" is a keyword
260 pat = getstring(x, _("adds requires a pattern"))
270 pat = getstring(x, _("adds requires a pattern"))
261 return checkstatus(repo, subset, pat, 1)
271 return checkstatus(repo, subset, pat, 1)
262
272
263 def ancestor(repo, subset, x):
273 def ancestor(repo, subset, x):
264 """``ancestor(single, single)``
274 """``ancestor(single, single)``
265 Greatest common ancestor of the two changesets.
275 Greatest common ancestor of the two changesets.
266 """
276 """
267 # i18n: "ancestor" is a keyword
277 # i18n: "ancestor" is a keyword
268 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
278 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
269 r = range(len(repo))
279 r = range(len(repo))
270 a = getset(repo, r, l[0])
280 a = getset(repo, r, l[0])
271 b = getset(repo, r, l[1])
281 b = getset(repo, r, l[1])
272 if len(a) != 1 or len(b) != 1:
282 if len(a) != 1 or len(b) != 1:
273 # i18n: "ancestor" is a keyword
283 # i18n: "ancestor" is a keyword
274 raise error.ParseError(_("ancestor arguments must be single revisions"))
284 raise error.ParseError(_("ancestor arguments must be single revisions"))
275 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
285 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
276
286
277 return [r for r in an if r in subset]
287 return [r for r in an if r in subset]
278
288
279 def _ancestors(repo, subset, x, followfirst=False):
289 def _ancestors(repo, subset, x, followfirst=False):
280 args = getset(repo, range(len(repo)), x)
290 args = getset(repo, range(len(repo)), x)
281 if not args:
291 if not args:
282 return []
292 return []
283 s = set(_revancestors(repo, args, followfirst)) | set(args)
293 s = set(_revancestors(repo, args, followfirst)) | set(args)
284 return [r for r in subset if r in s]
294 return [r for r in subset if r in s]
285
295
286 def ancestors(repo, subset, x):
296 def ancestors(repo, subset, x):
287 """``ancestors(set)``
297 """``ancestors(set)``
288 Changesets that are ancestors of a changeset in set.
298 Changesets that are ancestors of a changeset in set.
289 """
299 """
290 return _ancestors(repo, subset, x)
300 return _ancestors(repo, subset, x)
291
301
292 def _firstancestors(repo, subset, x):
302 def _firstancestors(repo, subset, x):
293 # ``_firstancestors(set)``
303 # ``_firstancestors(set)``
294 # Like ``ancestors(set)`` but follows only the first parents.
304 # Like ``ancestors(set)`` but follows only the first parents.
295 return _ancestors(repo, subset, x, followfirst=True)
305 return _ancestors(repo, subset, x, followfirst=True)
296
306
297 def ancestorspec(repo, subset, x, n):
307 def ancestorspec(repo, subset, x, n):
298 """``set~n``
308 """``set~n``
299 Changesets that are the Nth ancestor (first parents only) of a changeset
309 Changesets that are the Nth ancestor (first parents only) of a changeset
300 in set.
310 in set.
301 """
311 """
302 try:
312 try:
303 n = int(n[1])
313 n = int(n[1])
304 except (TypeError, ValueError):
314 except (TypeError, ValueError):
305 raise error.ParseError(_("~ expects a number"))
315 raise error.ParseError(_("~ expects a number"))
306 ps = set()
316 ps = set()
307 cl = repo.changelog
317 cl = repo.changelog
308 for r in getset(repo, subset, x):
318 for r in getset(repo, subset, x):
309 for i in range(n):
319 for i in range(n):
310 r = cl.parentrevs(r)[0]
320 r = cl.parentrevs(r)[0]
311 ps.add(r)
321 ps.add(r)
312 return [r for r in subset if r in ps]
322 return [r for r in subset if r in ps]
313
323
314 def author(repo, subset, x):
324 def author(repo, subset, x):
315 """``author(string)``
325 """``author(string)``
316 Alias for ``user(string)``.
326 Alias for ``user(string)``.
317 """
327 """
318 # i18n: "author" is a keyword
328 # i18n: "author" is a keyword
319 n = encoding.lower(getstring(x, _("author requires a string")))
329 n = encoding.lower(getstring(x, _("author requires a string")))
320 kind, pattern, matcher = _substringmatcher(n)
330 kind, pattern, matcher = _substringmatcher(n)
321 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
331 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
322
332
323 def bisect(repo, subset, x):
333 def bisect(repo, subset, x):
324 """``bisect(string)``
334 """``bisect(string)``
325 Changesets marked in the specified bisect status:
335 Changesets marked in the specified bisect status:
326
336
327 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
337 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
328 - ``goods``, ``bads`` : csets topologicaly good/bad
338 - ``goods``, ``bads`` : csets topologicaly good/bad
329 - ``range`` : csets taking part in the bisection
339 - ``range`` : csets taking part in the bisection
330 - ``pruned`` : csets that are goods, bads or skipped
340 - ``pruned`` : csets that are goods, bads or skipped
331 - ``untested`` : csets whose fate is yet unknown
341 - ``untested`` : csets whose fate is yet unknown
332 - ``ignored`` : csets ignored due to DAG topology
342 - ``ignored`` : csets ignored due to DAG topology
333 - ``current`` : the cset currently being bisected
343 - ``current`` : the cset currently being bisected
334 """
344 """
335 status = getstring(x, _("bisect requires a string")).lower()
345 status = getstring(x, _("bisect requires a string")).lower()
336 state = set(hbisect.get(repo, status))
346 state = set(hbisect.get(repo, status))
337 return [r for r in subset if r in state]
347 return [r for r in subset if r in state]
338
348
339 # Backward-compatibility
349 # Backward-compatibility
340 # - no help entry so that we do not advertise it any more
350 # - no help entry so that we do not advertise it any more
341 def bisected(repo, subset, x):
351 def bisected(repo, subset, x):
342 return bisect(repo, subset, x)
352 return bisect(repo, subset, x)
343
353
344 def bookmark(repo, subset, x):
354 def bookmark(repo, subset, x):
345 """``bookmark([name])``
355 """``bookmark([name])``
346 The named bookmark or all bookmarks.
356 The named bookmark or all bookmarks.
347
357
348 If `name` starts with `re:`, the remainder of the name is treated as
358 If `name` starts with `re:`, the remainder of the name is treated as
349 a regular expression. To match a bookmark that actually starts with `re:`,
359 a regular expression. To match a bookmark that actually starts with `re:`,
350 use the prefix `literal:`.
360 use the prefix `literal:`.
351 """
361 """
352 # i18n: "bookmark" is a keyword
362 # i18n: "bookmark" is a keyword
353 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
363 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
354 if args:
364 if args:
355 bm = getstring(args[0],
365 bm = getstring(args[0],
356 # i18n: "bookmark" is a keyword
366 # i18n: "bookmark" is a keyword
357 _('the argument to bookmark must be a string'))
367 _('the argument to bookmark must be a string'))
358 kind, pattern, matcher = _stringmatcher(bm)
368 kind, pattern, matcher = _stringmatcher(bm)
359 if kind == 'literal':
369 if kind == 'literal':
360 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
370 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
361 if not bmrev:
371 if not bmrev:
362 raise util.Abort(_("bookmark '%s' does not exist") % bm)
372 raise util.Abort(_("bookmark '%s' does not exist") % bm)
363 bmrev = repo[bmrev].rev()
373 bmrev = repo[bmrev].rev()
364 return [r for r in subset if r == bmrev]
374 return [r for r in subset if r == bmrev]
365 else:
375 else:
366 matchrevs = set()
376 matchrevs = set()
367 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
377 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
368 if matcher(name):
378 if matcher(name):
369 matchrevs.add(bmrev)
379 matchrevs.add(bmrev)
370 if not matchrevs:
380 if not matchrevs:
371 raise util.Abort(_("no bookmarks exist that match '%s'")
381 raise util.Abort(_("no bookmarks exist that match '%s'")
372 % pattern)
382 % pattern)
373 bmrevs = set()
383 bmrevs = set()
374 for bmrev in matchrevs:
384 for bmrev in matchrevs:
375 bmrevs.add(repo[bmrev].rev())
385 bmrevs.add(repo[bmrev].rev())
376 return [r for r in subset if r in bmrevs]
386 return [r for r in subset if r in bmrevs]
377
387
378 bms = set([repo[r].rev()
388 bms = set([repo[r].rev()
379 for r in bookmarksmod.listbookmarks(repo).values()])
389 for r in bookmarksmod.listbookmarks(repo).values()])
380 return [r for r in subset if r in bms]
390 return [r for r in subset if r in bms]
381
391
382 def branch(repo, subset, x):
392 def branch(repo, subset, x):
383 """``branch(string or set)``
393 """``branch(string or set)``
384 All changesets belonging to the given branch or the branches of the given
394 All changesets belonging to the given branch or the branches of the given
385 changesets.
395 changesets.
386
396
387 If `string` starts with `re:`, the remainder of the name is treated as
397 If `string` starts with `re:`, the remainder of the name is treated as
388 a regular expression. To match a branch that actually starts with `re:`,
398 a regular expression. To match a branch that actually starts with `re:`,
389 use the prefix `literal:`.
399 use the prefix `literal:`.
390 """
400 """
391 try:
401 try:
392 b = getstring(x, '')
402 b = getstring(x, '')
393 except error.ParseError:
403 except error.ParseError:
394 # not a string, but another revspec, e.g. tip()
404 # not a string, but another revspec, e.g. tip()
395 pass
405 pass
396 else:
406 else:
397 kind, pattern, matcher = _stringmatcher(b)
407 kind, pattern, matcher = _stringmatcher(b)
398 if kind == 'literal':
408 if kind == 'literal':
399 # note: falls through to the revspec case if no branch with
409 # note: falls through to the revspec case if no branch with
400 # this name exists
410 # this name exists
401 if pattern in repo.branchmap():
411 if pattern in repo.branchmap():
402 return [r for r in subset if matcher(repo[r].branch())]
412 return [r for r in subset if matcher(repo[r].branch())]
403 else:
413 else:
404 return [r for r in subset if matcher(repo[r].branch())]
414 return [r for r in subset if matcher(repo[r].branch())]
405
415
406 s = getset(repo, range(len(repo)), x)
416 s = getset(repo, range(len(repo)), x)
407 b = set()
417 b = set()
408 for r in s:
418 for r in s:
409 b.add(repo[r].branch())
419 b.add(repo[r].branch())
410 s = set(s)
420 s = set(s)
411 return [r for r in subset if r in s or repo[r].branch() in b]
421 return [r for r in subset if r in s or repo[r].branch() in b]
412
422
413 def checkstatus(repo, subset, pat, field):
423 def checkstatus(repo, subset, pat, field):
414 m = None
424 m = None
415 s = []
425 s = []
416 hasset = matchmod.patkind(pat) == 'set'
426 hasset = matchmod.patkind(pat) == 'set'
417 fname = None
427 fname = None
418 for r in subset:
428 for r in subset:
419 c = repo[r]
429 c = repo[r]
420 if not m or hasset:
430 if not m or hasset:
421 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
431 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
422 if not m.anypats() and len(m.files()) == 1:
432 if not m.anypats() and len(m.files()) == 1:
423 fname = m.files()[0]
433 fname = m.files()[0]
424 if fname is not None:
434 if fname is not None:
425 if fname not in c.files():
435 if fname not in c.files():
426 continue
436 continue
427 else:
437 else:
428 for f in c.files():
438 for f in c.files():
429 if m(f):
439 if m(f):
430 break
440 break
431 else:
441 else:
432 continue
442 continue
433 files = repo.status(c.p1().node(), c.node())[field]
443 files = repo.status(c.p1().node(), c.node())[field]
434 if fname is not None:
444 if fname is not None:
435 if fname in files:
445 if fname in files:
436 s.append(r)
446 s.append(r)
437 else:
447 else:
438 for f in files:
448 for f in files:
439 if m(f):
449 if m(f):
440 s.append(r)
450 s.append(r)
441 break
451 break
442 return s
452 return s
443
453
444 def _children(repo, narrow, parentset):
454 def _children(repo, narrow, parentset):
445 cs = set()
455 cs = set()
446 pr = repo.changelog.parentrevs
456 pr = repo.changelog.parentrevs
447 for r in narrow:
457 for r in narrow:
448 for p in pr(r):
458 for p in pr(r):
449 if p in parentset:
459 if p in parentset:
450 cs.add(r)
460 cs.add(r)
451 return cs
461 return cs
452
462
453 def children(repo, subset, x):
463 def children(repo, subset, x):
454 """``children(set)``
464 """``children(set)``
455 Child changesets of changesets in set.
465 Child changesets of changesets in set.
456 """
466 """
457 s = set(getset(repo, range(len(repo)), x))
467 s = set(getset(repo, range(len(repo)), x))
458 cs = _children(repo, subset, s)
468 cs = _children(repo, subset, s)
459 return [r for r in subset if r in cs]
469 return [r for r in subset if r in cs]
460
470
461 def closed(repo, subset, x):
471 def closed(repo, subset, x):
462 """``closed()``
472 """``closed()``
463 Changeset is closed.
473 Changeset is closed.
464 """
474 """
465 # i18n: "closed" is a keyword
475 # i18n: "closed" is a keyword
466 getargs(x, 0, 0, _("closed takes no arguments"))
476 getargs(x, 0, 0, _("closed takes no arguments"))
467 return [r for r in subset if repo[r].closesbranch()]
477 return [r for r in subset if repo[r].closesbranch()]
468
478
469 def contains(repo, subset, x):
479 def contains(repo, subset, x):
470 """``contains(pattern)``
480 """``contains(pattern)``
471 Revision contains a file matching pattern. See :hg:`help patterns`
481 Revision contains a file matching pattern. See :hg:`help patterns`
472 for information about file patterns.
482 for information about file patterns.
473 """
483 """
474 # i18n: "contains" is a keyword
484 # i18n: "contains" is a keyword
475 pat = getstring(x, _("contains requires a pattern"))
485 pat = getstring(x, _("contains requires a pattern"))
476 m = None
486 m = None
477 s = []
487 s = []
478 if not matchmod.patkind(pat):
488 if not matchmod.patkind(pat):
479 for r in subset:
489 for r in subset:
480 if pat in repo[r]:
490 if pat in repo[r]:
481 s.append(r)
491 s.append(r)
482 else:
492 else:
483 for r in subset:
493 for r in subset:
484 c = repo[r]
494 c = repo[r]
485 if not m or matchmod.patkind(pat) == 'set':
495 if not m or matchmod.patkind(pat) == 'set':
486 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
496 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
487 for f in c.manifest():
497 for f in c.manifest():
488 if m(f):
498 if m(f):
489 s.append(r)
499 s.append(r)
490 break
500 break
491 return s
501 return s
492
502
493 def converted(repo, subset, x):
503 def converted(repo, subset, x):
494 """``converted([id])``
504 """``converted([id])``
495 Changesets converted from the given identifier in the old repository if
505 Changesets converted from the given identifier in the old repository if
496 present, or all converted changesets if no identifier is specified.
506 present, or all converted changesets if no identifier is specified.
497 """
507 """
498
508
499 # There is exactly no chance of resolving the revision, so do a simple
509 # There is exactly no chance of resolving the revision, so do a simple
500 # string compare and hope for the best
510 # string compare and hope for the best
501
511
502 # i18n: "converted" is a keyword
512 # i18n: "converted" is a keyword
503 rev = None
513 rev = None
504 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
514 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
505 if l:
515 if l:
506 rev = getstring(l[0], _('converted requires a revision'))
516 rev = getstring(l[0], _('converted requires a revision'))
507
517
508 def _matchvalue(r):
518 def _matchvalue(r):
509 source = repo[r].extra().get('convert_revision', None)
519 source = repo[r].extra().get('convert_revision', None)
510 return source is not None and (rev is None or source.startswith(rev))
520 return source is not None and (rev is None or source.startswith(rev))
511
521
512 return [r for r in subset if _matchvalue(r)]
522 return [r for r in subset if _matchvalue(r)]
513
523
514 def date(repo, subset, x):
524 def date(repo, subset, x):
515 """``date(interval)``
525 """``date(interval)``
516 Changesets within the interval, see :hg:`help dates`.
526 Changesets within the interval, see :hg:`help dates`.
517 """
527 """
518 # i18n: "date" is a keyword
528 # i18n: "date" is a keyword
519 ds = getstring(x, _("date requires a string"))
529 ds = getstring(x, _("date requires a string"))
520 dm = util.matchdate(ds)
530 dm = util.matchdate(ds)
521 return [r for r in subset if dm(repo[r].date()[0])]
531 return [r for r in subset if dm(repo[r].date()[0])]
522
532
523 def desc(repo, subset, x):
533 def desc(repo, subset, x):
524 """``desc(string)``
534 """``desc(string)``
525 Search commit message for string. The match is case-insensitive.
535 Search commit message for string. The match is case-insensitive.
526 """
536 """
527 # i18n: "desc" is a keyword
537 # i18n: "desc" is a keyword
528 ds = encoding.lower(getstring(x, _("desc requires a string")))
538 ds = encoding.lower(getstring(x, _("desc requires a string")))
529 l = []
539 l = []
530 for r in subset:
540 for r in subset:
531 c = repo[r]
541 c = repo[r]
532 if ds in encoding.lower(c.description()):
542 if ds in encoding.lower(c.description()):
533 l.append(r)
543 l.append(r)
534 return l
544 return l
535
545
536 def _descendants(repo, subset, x, followfirst=False):
546 def _descendants(repo, subset, x, followfirst=False):
537 args = getset(repo, range(len(repo)), x)
547 args = getset(repo, range(len(repo)), x)
538 if not args:
548 if not args:
539 return []
549 return []
540 s = set(_revdescendants(repo, args, followfirst)) | set(args)
550 s = set(_revdescendants(repo, args, followfirst)) | set(args)
541 return [r for r in subset if r in s]
551 return [r for r in subset if r in s]
542
552
543 def descendants(repo, subset, x):
553 def descendants(repo, subset, x):
544 """``descendants(set)``
554 """``descendants(set)``
545 Changesets which are descendants of changesets in set.
555 Changesets which are descendants of changesets in set.
546 """
556 """
547 return _descendants(repo, subset, x)
557 return _descendants(repo, subset, x)
548
558
549 def _firstdescendants(repo, subset, x):
559 def _firstdescendants(repo, subset, x):
550 # ``_firstdescendants(set)``
560 # ``_firstdescendants(set)``
551 # Like ``descendants(set)`` but follows only the first parents.
561 # Like ``descendants(set)`` but follows only the first parents.
552 return _descendants(repo, subset, x, followfirst=True)
562 return _descendants(repo, subset, x, followfirst=True)
553
563
554 def draft(repo, subset, x):
564 def draft(repo, subset, x):
555 """``draft()``
565 """``draft()``
556 Changeset in draft phase."""
566 Changeset in draft phase."""
557 getargs(x, 0, 0, _("draft takes no arguments"))
567 getargs(x, 0, 0, _("draft takes no arguments"))
558 pc = repo._phasecache
568 pc = repo._phasecache
559 return [r for r in subset if pc.phase(repo, r) == phases.draft]
569 return [r for r in subset if pc.phase(repo, r) == phases.draft]
560
570
561 def extra(repo, subset, x):
571 def extra(repo, subset, x):
562 """``extra(label, [value])``
572 """``extra(label, [value])``
563 Changesets with the given label in the extra metadata, with the given
573 Changesets with the given label in the extra metadata, with the given
564 optional value.
574 optional value.
565
575
566 If `value` starts with `re:`, the remainder of the value is treated as
576 If `value` starts with `re:`, the remainder of the value is treated as
567 a regular expression. To match a value that actually starts with `re:`,
577 a regular expression. To match a value that actually starts with `re:`,
568 use the prefix `literal:`.
578 use the prefix `literal:`.
569 """
579 """
570
580
571 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
581 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
572 label = getstring(l[0], _('first argument to extra must be a string'))
582 label = getstring(l[0], _('first argument to extra must be a string'))
573 value = None
583 value = None
574
584
575 if len(l) > 1:
585 if len(l) > 1:
576 value = getstring(l[1], _('second argument to extra must be a string'))
586 value = getstring(l[1], _('second argument to extra must be a string'))
577 kind, value, matcher = _stringmatcher(value)
587 kind, value, matcher = _stringmatcher(value)
578
588
579 def _matchvalue(r):
589 def _matchvalue(r):
580 extra = repo[r].extra()
590 extra = repo[r].extra()
581 return label in extra and (value is None or matcher(extra[label]))
591 return label in extra and (value is None or matcher(extra[label]))
582
592
583 return [r for r in subset if _matchvalue(r)]
593 return [r for r in subset if _matchvalue(r)]
584
594
585 def filelog(repo, subset, x):
595 def filelog(repo, subset, x):
586 """``filelog(pattern)``
596 """``filelog(pattern)``
587 Changesets connected to the specified filelog.
597 Changesets connected to the specified filelog.
588 """
598 """
589
599
590 pat = getstring(x, _("filelog requires a pattern"))
600 pat = getstring(x, _("filelog requires a pattern"))
591 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
601 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
592 ctx=repo[None])
602 ctx=repo[None])
593 s = set()
603 s = set()
594
604
595 if not matchmod.patkind(pat):
605 if not matchmod.patkind(pat):
596 for f in m.files():
606 for f in m.files():
597 fl = repo.file(f)
607 fl = repo.file(f)
598 for fr in fl:
608 for fr in fl:
599 s.add(fl.linkrev(fr))
609 s.add(fl.linkrev(fr))
600 else:
610 else:
601 for f in repo[None]:
611 for f in repo[None]:
602 if m(f):
612 if m(f):
603 fl = repo.file(f)
613 fl = repo.file(f)
604 for fr in fl:
614 for fr in fl:
605 s.add(fl.linkrev(fr))
615 s.add(fl.linkrev(fr))
606
616
607 return [r for r in subset if r in s]
617 return [r for r in subset if r in s]
608
618
609 def first(repo, subset, x):
619 def first(repo, subset, x):
610 """``first(set, [n])``
620 """``first(set, [n])``
611 An alias for limit().
621 An alias for limit().
612 """
622 """
613 return limit(repo, subset, x)
623 return limit(repo, subset, x)
614
624
615 def _follow(repo, subset, x, name, followfirst=False):
625 def _follow(repo, subset, x, name, followfirst=False):
616 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
626 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
617 c = repo['.']
627 c = repo['.']
618 if l:
628 if l:
619 x = getstring(l[0], _("%s expected a filename") % name)
629 x = getstring(l[0], _("%s expected a filename") % name)
620 if x in c:
630 if x in c:
621 cx = c[x]
631 cx = c[x]
622 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
632 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
623 # include the revision responsible for the most recent version
633 # include the revision responsible for the most recent version
624 s.add(cx.linkrev())
634 s.add(cx.linkrev())
625 else:
635 else:
626 return []
636 return []
627 else:
637 else:
628 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
638 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
629
639
630 return [r for r in subset if r in s]
640 return [r for r in subset if r in s]
631
641
632 def follow(repo, subset, x):
642 def follow(repo, subset, x):
633 """``follow([file])``
643 """``follow([file])``
634 An alias for ``::.`` (ancestors of the working copy's first parent).
644 An alias for ``::.`` (ancestors of the working copy's first parent).
635 If a filename is specified, the history of the given file is followed,
645 If a filename is specified, the history of the given file is followed,
636 including copies.
646 including copies.
637 """
647 """
638 return _follow(repo, subset, x, 'follow')
648 return _follow(repo, subset, x, 'follow')
639
649
640 def _followfirst(repo, subset, x):
650 def _followfirst(repo, subset, x):
641 # ``followfirst([file])``
651 # ``followfirst([file])``
642 # Like ``follow([file])`` but follows only the first parent of
652 # Like ``follow([file])`` but follows only the first parent of
643 # every revision or file revision.
653 # every revision or file revision.
644 return _follow(repo, subset, x, '_followfirst', followfirst=True)
654 return _follow(repo, subset, x, '_followfirst', followfirst=True)
645
655
646 def getall(repo, subset, x):
656 def getall(repo, subset, x):
647 """``all()``
657 """``all()``
648 All changesets, the same as ``0:tip``.
658 All changesets, the same as ``0:tip``.
649 """
659 """
650 # i18n: "all" is a keyword
660 # i18n: "all" is a keyword
651 getargs(x, 0, 0, _("all takes no arguments"))
661 getargs(x, 0, 0, _("all takes no arguments"))
652 return subset
662 return subset
653
663
654 def grep(repo, subset, x):
664 def grep(repo, subset, x):
655 """``grep(regex)``
665 """``grep(regex)``
656 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
666 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
657 to ensure special escape characters are handled correctly. Unlike
667 to ensure special escape characters are handled correctly. Unlike
658 ``keyword(string)``, the match is case-sensitive.
668 ``keyword(string)``, the match is case-sensitive.
659 """
669 """
660 try:
670 try:
661 # i18n: "grep" is a keyword
671 # i18n: "grep" is a keyword
662 gr = re.compile(getstring(x, _("grep requires a string")))
672 gr = re.compile(getstring(x, _("grep requires a string")))
663 except re.error, e:
673 except re.error, e:
664 raise error.ParseError(_('invalid match pattern: %s') % e)
674 raise error.ParseError(_('invalid match pattern: %s') % e)
665 l = []
675 l = []
666 for r in subset:
676 for r in subset:
667 c = repo[r]
677 c = repo[r]
668 for e in c.files() + [c.user(), c.description()]:
678 for e in c.files() + [c.user(), c.description()]:
669 if gr.search(e):
679 if gr.search(e):
670 l.append(r)
680 l.append(r)
671 break
681 break
672 return l
682 return l
673
683
674 def _matchfiles(repo, subset, x):
684 def _matchfiles(repo, subset, x):
675 # _matchfiles takes a revset list of prefixed arguments:
685 # _matchfiles takes a revset list of prefixed arguments:
676 #
686 #
677 # [p:foo, i:bar, x:baz]
687 # [p:foo, i:bar, x:baz]
678 #
688 #
679 # builds a match object from them and filters subset. Allowed
689 # builds a match object from them and filters subset. Allowed
680 # prefixes are 'p:' for regular patterns, 'i:' for include
690 # prefixes are 'p:' for regular patterns, 'i:' for include
681 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
691 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
682 # a revision identifier, or the empty string to reference the
692 # a revision identifier, or the empty string to reference the
683 # working directory, from which the match object is
693 # working directory, from which the match object is
684 # initialized. Use 'd:' to set the default matching mode, default
694 # initialized. Use 'd:' to set the default matching mode, default
685 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
695 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
686
696
687 # i18n: "_matchfiles" is a keyword
697 # i18n: "_matchfiles" is a keyword
688 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
698 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
689 pats, inc, exc = [], [], []
699 pats, inc, exc = [], [], []
690 hasset = False
700 hasset = False
691 rev, default = None, None
701 rev, default = None, None
692 for arg in l:
702 for arg in l:
693 s = getstring(arg, _("_matchfiles requires string arguments"))
703 s = getstring(arg, _("_matchfiles requires string arguments"))
694 prefix, value = s[:2], s[2:]
704 prefix, value = s[:2], s[2:]
695 if prefix == 'p:':
705 if prefix == 'p:':
696 pats.append(value)
706 pats.append(value)
697 elif prefix == 'i:':
707 elif prefix == 'i:':
698 inc.append(value)
708 inc.append(value)
699 elif prefix == 'x:':
709 elif prefix == 'x:':
700 exc.append(value)
710 exc.append(value)
701 elif prefix == 'r:':
711 elif prefix == 'r:':
702 if rev is not None:
712 if rev is not None:
703 raise error.ParseError(_('_matchfiles expected at most one '
713 raise error.ParseError(_('_matchfiles expected at most one '
704 'revision'))
714 'revision'))
705 rev = value
715 rev = value
706 elif prefix == 'd:':
716 elif prefix == 'd:':
707 if default is not None:
717 if default is not None:
708 raise error.ParseError(_('_matchfiles expected at most one '
718 raise error.ParseError(_('_matchfiles expected at most one '
709 'default mode'))
719 'default mode'))
710 default = value
720 default = value
711 else:
721 else:
712 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
722 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
713 if not hasset and matchmod.patkind(value) == 'set':
723 if not hasset and matchmod.patkind(value) == 'set':
714 hasset = True
724 hasset = True
715 if not default:
725 if not default:
716 default = 'glob'
726 default = 'glob'
717 m = None
727 m = None
718 s = []
728 s = []
719 for r in subset:
729 for r in subset:
720 c = repo[r]
730 c = repo[r]
721 if not m or (hasset and rev is None):
731 if not m or (hasset and rev is None):
722 ctx = c
732 ctx = c
723 if rev is not None:
733 if rev is not None:
724 ctx = repo[rev or None]
734 ctx = repo[rev or None]
725 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
735 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
726 exclude=exc, ctx=ctx, default=default)
736 exclude=exc, ctx=ctx, default=default)
727 for f in c.files():
737 for f in c.files():
728 if m(f):
738 if m(f):
729 s.append(r)
739 s.append(r)
730 break
740 break
731 return s
741 return s
732
742
733 def hasfile(repo, subset, x):
743 def hasfile(repo, subset, x):
734 """``file(pattern)``
744 """``file(pattern)``
735 Changesets affecting files matched by pattern.
745 Changesets affecting files matched by pattern.
736 """
746 """
737 # i18n: "file" is a keyword
747 # i18n: "file" is a keyword
738 pat = getstring(x, _("file requires a pattern"))
748 pat = getstring(x, _("file requires a pattern"))
739 return _matchfiles(repo, subset, ('string', 'p:' + pat))
749 return _matchfiles(repo, subset, ('string', 'p:' + pat))
740
750
741 def head(repo, subset, x):
751 def head(repo, subset, x):
742 """``head()``
752 """``head()``
743 Changeset is a named branch head.
753 Changeset is a named branch head.
744 """
754 """
745 # i18n: "head" is a keyword
755 # i18n: "head" is a keyword
746 getargs(x, 0, 0, _("head takes no arguments"))
756 getargs(x, 0, 0, _("head takes no arguments"))
747 hs = set()
757 hs = set()
748 for b, ls in repo.branchmap().iteritems():
758 for b, ls in repo.branchmap().iteritems():
749 hs.update(repo[h].rev() for h in ls)
759 hs.update(repo[h].rev() for h in ls)
750 return [r for r in subset if r in hs]
760 return [r for r in subset if r in hs]
751
761
752 def heads(repo, subset, x):
762 def heads(repo, subset, x):
753 """``heads(set)``
763 """``heads(set)``
754 Members of set with no children in set.
764 Members of set with no children in set.
755 """
765 """
756 s = getset(repo, subset, x)
766 s = getset(repo, subset, x)
757 ps = set(parents(repo, subset, x))
767 ps = set(parents(repo, subset, x))
758 return [r for r in s if r not in ps]
768 return [r for r in s if r not in ps]
759
769
760 def keyword(repo, subset, x):
770 def keyword(repo, subset, x):
761 """``keyword(string)``
771 """``keyword(string)``
762 Search commit message, user name, and names of changed files for
772 Search commit message, user name, and names of changed files for
763 string. The match is case-insensitive.
773 string. The match is case-insensitive.
764 """
774 """
765 # i18n: "keyword" is a keyword
775 # i18n: "keyword" is a keyword
766 kw = encoding.lower(getstring(x, _("keyword requires a string")))
776 kw = encoding.lower(getstring(x, _("keyword requires a string")))
767 l = []
777 l = []
768 for r in subset:
778 for r in subset:
769 c = repo[r]
779 c = repo[r]
770 t = " ".join(c.files() + [c.user(), c.description()])
780 t = " ".join(c.files() + [c.user(), c.description()])
771 if kw in encoding.lower(t):
781 if kw in encoding.lower(t):
772 l.append(r)
782 l.append(r)
773 return l
783 return l
774
784
775 def limit(repo, subset, x):
785 def limit(repo, subset, x):
776 """``limit(set, [n])``
786 """``limit(set, [n])``
777 First n members of set, defaulting to 1.
787 First n members of set, defaulting to 1.
778 """
788 """
779 # i18n: "limit" is a keyword
789 # i18n: "limit" is a keyword
780 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
790 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
781 try:
791 try:
782 lim = 1
792 lim = 1
783 if len(l) == 2:
793 if len(l) == 2:
784 # i18n: "limit" is a keyword
794 # i18n: "limit" is a keyword
785 lim = int(getstring(l[1], _("limit requires a number")))
795 lim = int(getstring(l[1], _("limit requires a number")))
786 except (TypeError, ValueError):
796 except (TypeError, ValueError):
787 # i18n: "limit" is a keyword
797 # i18n: "limit" is a keyword
788 raise error.ParseError(_("limit expects a number"))
798 raise error.ParseError(_("limit expects a number"))
789 ss = set(subset)
799 ss = set(subset)
790 os = getset(repo, range(len(repo)), l[0])[:lim]
800 os = getset(repo, range(len(repo)), l[0])[:lim]
791 return [r for r in os if r in ss]
801 return [r for r in os if r in ss]
792
802
793 def last(repo, subset, x):
803 def last(repo, subset, x):
794 """``last(set, [n])``
804 """``last(set, [n])``
795 Last n members of set, defaulting to 1.
805 Last n members of set, defaulting to 1.
796 """
806 """
797 # i18n: "last" is a keyword
807 # i18n: "last" is a keyword
798 l = getargs(x, 1, 2, _("last requires one or two arguments"))
808 l = getargs(x, 1, 2, _("last requires one or two arguments"))
799 try:
809 try:
800 lim = 1
810 lim = 1
801 if len(l) == 2:
811 if len(l) == 2:
802 # i18n: "last" is a keyword
812 # i18n: "last" is a keyword
803 lim = int(getstring(l[1], _("last requires a number")))
813 lim = int(getstring(l[1], _("last requires a number")))
804 except (TypeError, ValueError):
814 except (TypeError, ValueError):
805 # i18n: "last" is a keyword
815 # i18n: "last" is a keyword
806 raise error.ParseError(_("last expects a number"))
816 raise error.ParseError(_("last expects a number"))
807 ss = set(subset)
817 ss = set(subset)
808 os = getset(repo, range(len(repo)), l[0])[-lim:]
818 os = getset(repo, range(len(repo)), l[0])[-lim:]
809 return [r for r in os if r in ss]
819 return [r for r in os if r in ss]
810
820
811 def maxrev(repo, subset, x):
821 def maxrev(repo, subset, x):
812 """``max(set)``
822 """``max(set)``
813 Changeset with highest revision number in set.
823 Changeset with highest revision number in set.
814 """
824 """
815 os = getset(repo, range(len(repo)), x)
825 os = getset(repo, range(len(repo)), x)
816 if os:
826 if os:
817 m = max(os)
827 m = max(os)
818 if m in subset:
828 if m in subset:
819 return [m]
829 return [m]
820 return []
830 return []
821
831
822 def merge(repo, subset, x):
832 def merge(repo, subset, x):
823 """``merge()``
833 """``merge()``
824 Changeset is a merge changeset.
834 Changeset is a merge changeset.
825 """
835 """
826 # i18n: "merge" is a keyword
836 # i18n: "merge" is a keyword
827 getargs(x, 0, 0, _("merge takes no arguments"))
837 getargs(x, 0, 0, _("merge takes no arguments"))
828 cl = repo.changelog
838 cl = repo.changelog
829 return [r for r in subset if cl.parentrevs(r)[1] != -1]
839 return [r for r in subset if cl.parentrevs(r)[1] != -1]
830
840
831 def minrev(repo, subset, x):
841 def minrev(repo, subset, x):
832 """``min(set)``
842 """``min(set)``
833 Changeset with lowest revision number in set.
843 Changeset with lowest revision number in set.
834 """
844 """
835 os = getset(repo, range(len(repo)), x)
845 os = getset(repo, range(len(repo)), x)
836 if os:
846 if os:
837 m = min(os)
847 m = min(os)
838 if m in subset:
848 if m in subset:
839 return [m]
849 return [m]
840 return []
850 return []
841
851
842 def modifies(repo, subset, x):
852 def modifies(repo, subset, x):
843 """``modifies(pattern)``
853 """``modifies(pattern)``
844 Changesets modifying files matched by pattern.
854 Changesets modifying files matched by pattern.
845 """
855 """
846 # i18n: "modifies" is a keyword
856 # i18n: "modifies" is a keyword
847 pat = getstring(x, _("modifies requires a pattern"))
857 pat = getstring(x, _("modifies requires a pattern"))
848 return checkstatus(repo, subset, pat, 0)
858 return checkstatus(repo, subset, pat, 0)
849
859
850 def node_(repo, subset, x):
860 def node_(repo, subset, x):
851 """``id(string)``
861 """``id(string)``
852 Revision non-ambiguously specified by the given hex string prefix.
862 Revision non-ambiguously specified by the given hex string prefix.
853 """
863 """
854 # i18n: "id" is a keyword
864 # i18n: "id" is a keyword
855 l = getargs(x, 1, 1, _("id requires one argument"))
865 l = getargs(x, 1, 1, _("id requires one argument"))
856 # i18n: "id" is a keyword
866 # i18n: "id" is a keyword
857 n = getstring(l[0], _("id requires a string"))
867 n = getstring(l[0], _("id requires a string"))
858 if len(n) == 40:
868 if len(n) == 40:
859 rn = repo[n].rev()
869 rn = repo[n].rev()
860 else:
870 else:
861 rn = None
871 rn = None
862 pm = repo.changelog._partialmatch(n)
872 pm = repo.changelog._partialmatch(n)
863 if pm is not None:
873 if pm is not None:
864 rn = repo.changelog.rev(pm)
874 rn = repo.changelog.rev(pm)
865
875
866 return [r for r in subset if r == rn]
876 return [r for r in subset if r == rn]
867
877
868 def outgoing(repo, subset, x):
878 def outgoing(repo, subset, x):
869 """``outgoing([path])``
879 """``outgoing([path])``
870 Changesets not found in the specified destination repository, or the
880 Changesets not found in the specified destination repository, or the
871 default push location.
881 default push location.
872 """
882 """
873 import hg # avoid start-up nasties
883 import hg # avoid start-up nasties
874 # i18n: "outgoing" is a keyword
884 # i18n: "outgoing" is a keyword
875 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
885 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
876 # i18n: "outgoing" is a keyword
886 # i18n: "outgoing" is a keyword
877 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
887 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
878 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
888 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
879 dest, branches = hg.parseurl(dest)
889 dest, branches = hg.parseurl(dest)
880 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
890 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
881 if revs:
891 if revs:
882 revs = [repo.lookup(rev) for rev in revs]
892 revs = [repo.lookup(rev) for rev in revs]
883 other = hg.peer(repo, {}, dest)
893 other = hg.peer(repo, {}, dest)
884 repo.ui.pushbuffer()
894 repo.ui.pushbuffer()
885 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
895 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
886 repo.ui.popbuffer()
896 repo.ui.popbuffer()
887 cl = repo.changelog
897 cl = repo.changelog
888 o = set([cl.rev(r) for r in outgoing.missing])
898 o = set([cl.rev(r) for r in outgoing.missing])
889 return [r for r in subset if r in o]
899 return [r for r in subset if r in o]
890
900
891 def p1(repo, subset, x):
901 def p1(repo, subset, x):
892 """``p1([set])``
902 """``p1([set])``
893 First parent of changesets in set, or the working directory.
903 First parent of changesets in set, or the working directory.
894 """
904 """
895 if x is None:
905 if x is None:
896 p = repo[x].p1().rev()
906 p = repo[x].p1().rev()
897 return [r for r in subset if r == p]
907 return [r for r in subset if r == p]
898
908
899 ps = set()
909 ps = set()
900 cl = repo.changelog
910 cl = repo.changelog
901 for r in getset(repo, range(len(repo)), x):
911 for r in getset(repo, range(len(repo)), x):
902 ps.add(cl.parentrevs(r)[0])
912 ps.add(cl.parentrevs(r)[0])
903 return [r for r in subset if r in ps]
913 return [r for r in subset if r in ps]
904
914
905 def p2(repo, subset, x):
915 def p2(repo, subset, x):
906 """``p2([set])``
916 """``p2([set])``
907 Second parent of changesets in set, or the working directory.
917 Second parent of changesets in set, or the working directory.
908 """
918 """
909 if x is None:
919 if x is None:
910 ps = repo[x].parents()
920 ps = repo[x].parents()
911 try:
921 try:
912 p = ps[1].rev()
922 p = ps[1].rev()
913 return [r for r in subset if r == p]
923 return [r for r in subset if r == p]
914 except IndexError:
924 except IndexError:
915 return []
925 return []
916
926
917 ps = set()
927 ps = set()
918 cl = repo.changelog
928 cl = repo.changelog
919 for r in getset(repo, range(len(repo)), x):
929 for r in getset(repo, range(len(repo)), x):
920 ps.add(cl.parentrevs(r)[1])
930 ps.add(cl.parentrevs(r)[1])
921 return [r for r in subset if r in ps]
931 return [r for r in subset if r in ps]
922
932
923 def parents(repo, subset, x):
933 def parents(repo, subset, x):
924 """``parents([set])``
934 """``parents([set])``
925 The set of all parents for all changesets in set, or the working directory.
935 The set of all parents for all changesets in set, or the working directory.
926 """
936 """
927 if x is None:
937 if x is None:
928 ps = tuple(p.rev() for p in repo[x].parents())
938 ps = tuple(p.rev() for p in repo[x].parents())
929 return [r for r in subset if r in ps]
939 return [r for r in subset if r in ps]
930
940
931 ps = set()
941 ps = set()
932 cl = repo.changelog
942 cl = repo.changelog
933 for r in getset(repo, range(len(repo)), x):
943 for r in getset(repo, range(len(repo)), x):
934 ps.update(cl.parentrevs(r))
944 ps.update(cl.parentrevs(r))
935 return [r for r in subset if r in ps]
945 return [r for r in subset if r in ps]
936
946
937 def parentspec(repo, subset, x, n):
947 def parentspec(repo, subset, x, n):
938 """``set^0``
948 """``set^0``
939 The set.
949 The set.
940 ``set^1`` (or ``set^``), ``set^2``
950 ``set^1`` (or ``set^``), ``set^2``
941 First or second parent, respectively, of all changesets in set.
951 First or second parent, respectively, of all changesets in set.
942 """
952 """
943 try:
953 try:
944 n = int(n[1])
954 n = int(n[1])
945 if n not in (0, 1, 2):
955 if n not in (0, 1, 2):
946 raise ValueError
956 raise ValueError
947 except (TypeError, ValueError):
957 except (TypeError, ValueError):
948 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
958 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
949 ps = set()
959 ps = set()
950 cl = repo.changelog
960 cl = repo.changelog
951 for r in getset(repo, subset, x):
961 for r in getset(repo, subset, x):
952 if n == 0:
962 if n == 0:
953 ps.add(r)
963 ps.add(r)
954 elif n == 1:
964 elif n == 1:
955 ps.add(cl.parentrevs(r)[0])
965 ps.add(cl.parentrevs(r)[0])
956 elif n == 2:
966 elif n == 2:
957 parents = cl.parentrevs(r)
967 parents = cl.parentrevs(r)
958 if len(parents) > 1:
968 if len(parents) > 1:
959 ps.add(parents[1])
969 ps.add(parents[1])
960 return [r for r in subset if r in ps]
970 return [r for r in subset if r in ps]
961
971
962 def present(repo, subset, x):
972 def present(repo, subset, x):
963 """``present(set)``
973 """``present(set)``
964 An empty set, if any revision in set isn't found; otherwise,
974 An empty set, if any revision in set isn't found; otherwise,
965 all revisions in set.
975 all revisions in set.
966
976
967 If any of specified revisions is not present in the local repository,
977 If any of specified revisions is not present in the local repository,
968 the query is normally aborted. But this predicate allows the query
978 the query is normally aborted. But this predicate allows the query
969 to continue even in such cases.
979 to continue even in such cases.
970 """
980 """
971 try:
981 try:
972 return getset(repo, subset, x)
982 return getset(repo, subset, x)
973 except error.RepoLookupError:
983 except error.RepoLookupError:
974 return []
984 return []
975
985
976 def public(repo, subset, x):
986 def public(repo, subset, x):
977 """``public()``
987 """``public()``
978 Changeset in public phase."""
988 Changeset in public phase."""
979 getargs(x, 0, 0, _("public takes no arguments"))
989 getargs(x, 0, 0, _("public takes no arguments"))
980 pc = repo._phasecache
990 pc = repo._phasecache
981 return [r for r in subset if pc.phase(repo, r) == phases.public]
991 return [r for r in subset if pc.phase(repo, r) == phases.public]
982
992
983 def remote(repo, subset, x):
993 def remote(repo, subset, x):
984 """``remote([id [,path]])``
994 """``remote([id [,path]])``
985 Local revision that corresponds to the given identifier in a
995 Local revision that corresponds to the given identifier in a
986 remote repository, if present. Here, the '.' identifier is a
996 remote repository, if present. Here, the '.' identifier is a
987 synonym for the current local branch.
997 synonym for the current local branch.
988 """
998 """
989
999
990 import hg # avoid start-up nasties
1000 import hg # avoid start-up nasties
991 # i18n: "remote" is a keyword
1001 # i18n: "remote" is a keyword
992 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1002 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
993
1003
994 q = '.'
1004 q = '.'
995 if len(l) > 0:
1005 if len(l) > 0:
996 # i18n: "remote" is a keyword
1006 # i18n: "remote" is a keyword
997 q = getstring(l[0], _("remote requires a string id"))
1007 q = getstring(l[0], _("remote requires a string id"))
998 if q == '.':
1008 if q == '.':
999 q = repo['.'].branch()
1009 q = repo['.'].branch()
1000
1010
1001 dest = ''
1011 dest = ''
1002 if len(l) > 1:
1012 if len(l) > 1:
1003 # i18n: "remote" is a keyword
1013 # i18n: "remote" is a keyword
1004 dest = getstring(l[1], _("remote requires a repository path"))
1014 dest = getstring(l[1], _("remote requires a repository path"))
1005 dest = repo.ui.expandpath(dest or 'default')
1015 dest = repo.ui.expandpath(dest or 'default')
1006 dest, branches = hg.parseurl(dest)
1016 dest, branches = hg.parseurl(dest)
1007 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1017 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1008 if revs:
1018 if revs:
1009 revs = [repo.lookup(rev) for rev in revs]
1019 revs = [repo.lookup(rev) for rev in revs]
1010 other = hg.peer(repo, {}, dest)
1020 other = hg.peer(repo, {}, dest)
1011 n = other.lookup(q)
1021 n = other.lookup(q)
1012 if n in repo:
1022 if n in repo:
1013 r = repo[n].rev()
1023 r = repo[n].rev()
1014 if r in subset:
1024 if r in subset:
1015 return [r]
1025 return [r]
1016 return []
1026 return []
1017
1027
1018 def removes(repo, subset, x):
1028 def removes(repo, subset, x):
1019 """``removes(pattern)``
1029 """``removes(pattern)``
1020 Changesets which remove files matching pattern.
1030 Changesets which remove files matching pattern.
1021 """
1031 """
1022 # i18n: "removes" is a keyword
1032 # i18n: "removes" is a keyword
1023 pat = getstring(x, _("removes requires a pattern"))
1033 pat = getstring(x, _("removes requires a pattern"))
1024 return checkstatus(repo, subset, pat, 2)
1034 return checkstatus(repo, subset, pat, 2)
1025
1035
1026 def rev(repo, subset, x):
1036 def rev(repo, subset, x):
1027 """``rev(number)``
1037 """``rev(number)``
1028 Revision with the given numeric identifier.
1038 Revision with the given numeric identifier.
1029 """
1039 """
1030 # i18n: "rev" is a keyword
1040 # i18n: "rev" is a keyword
1031 l = getargs(x, 1, 1, _("rev requires one argument"))
1041 l = getargs(x, 1, 1, _("rev requires one argument"))
1032 try:
1042 try:
1033 # i18n: "rev" is a keyword
1043 # i18n: "rev" is a keyword
1034 l = int(getstring(l[0], _("rev requires a number")))
1044 l = int(getstring(l[0], _("rev requires a number")))
1035 except (TypeError, ValueError):
1045 except (TypeError, ValueError):
1036 # i18n: "rev" is a keyword
1046 # i18n: "rev" is a keyword
1037 raise error.ParseError(_("rev expects a number"))
1047 raise error.ParseError(_("rev expects a number"))
1038 return [r for r in subset if r == l]
1048 return [r for r in subset if r == l]
1039
1049
1040 def matching(repo, subset, x):
1050 def matching(repo, subset, x):
1041 """``matching(revision [, field])``
1051 """``matching(revision [, field])``
1042 Changesets in which a given set of fields match the set of fields in the
1052 Changesets in which a given set of fields match the set of fields in the
1043 selected revision or set.
1053 selected revision or set.
1044
1054
1045 To match more than one field pass the list of fields to match separated
1055 To match more than one field pass the list of fields to match separated
1046 by spaces (e.g. ``author description``).
1056 by spaces (e.g. ``author description``).
1047
1057
1048 Valid fields are most regular revision fields and some special fields.
1058 Valid fields are most regular revision fields and some special fields.
1049
1059
1050 Regular revision fields are ``description``, ``author``, ``branch``,
1060 Regular revision fields are ``description``, ``author``, ``branch``,
1051 ``date``, ``files``, ``phase``, ``parents``, ``substate`` and ``user``.
1061 ``date``, ``files``, ``phase``, ``parents``, ``substate`` and ``user``.
1052 Note that ``author`` and ``user`` are synonyms.
1062 Note that ``author`` and ``user`` are synonyms.
1053
1063
1054 Special fields are ``summary`` and ``metadata``:
1064 Special fields are ``summary`` and ``metadata``:
1055 ``summary`` matches the first line of the description.
1065 ``summary`` matches the first line of the description.
1056 ``metadata`` is equivalent to matching ``description user date``
1066 ``metadata`` is equivalent to matching ``description user date``
1057 (i.e. it matches the main metadata fields).
1067 (i.e. it matches the main metadata fields).
1058
1068
1059 ``metadata`` is the default field which is used when no fields are
1069 ``metadata`` is the default field which is used when no fields are
1060 specified. You can match more than one field at a time.
1070 specified. You can match more than one field at a time.
1061 """
1071 """
1062 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1072 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1063
1073
1064 revs = getset(repo, xrange(len(repo)), l[0])
1074 revs = getset(repo, xrange(len(repo)), l[0])
1065
1075
1066 fieldlist = ['metadata']
1076 fieldlist = ['metadata']
1067 if len(l) > 1:
1077 if len(l) > 1:
1068 fieldlist = getstring(l[1],
1078 fieldlist = getstring(l[1],
1069 _("matching requires a string "
1079 _("matching requires a string "
1070 "as its second argument")).split()
1080 "as its second argument")).split()
1071
1081
1072 # Make sure that there are no repeated fields, and expand the
1082 # Make sure that there are no repeated fields, and expand the
1073 # 'special' 'metadata' field type
1083 # 'special' 'metadata' field type
1074 fields = []
1084 fields = []
1075 for field in fieldlist:
1085 for field in fieldlist:
1076 if field == 'metadata':
1086 if field == 'metadata':
1077 fields += ['user', 'description', 'date']
1087 fields += ['user', 'description', 'date']
1078 else:
1088 else:
1079 if field == 'author':
1089 if field == 'author':
1080 field = 'user'
1090 field = 'user'
1081 fields.append(field)
1091 fields.append(field)
1082 fields = set(fields)
1092 fields = set(fields)
1083 if 'summary' in fields and 'description' in fields:
1093 if 'summary' in fields and 'description' in fields:
1084 # If a revision matches its description it also matches its summary
1094 # If a revision matches its description it also matches its summary
1085 fields.discard('summary')
1095 fields.discard('summary')
1086
1096
1087 # We may want to match more than one field
1097 # We may want to match more than one field
1088 # Not all fields take the same amount of time to be matched
1098 # Not all fields take the same amount of time to be matched
1089 # Sort the selected fields in order of increasing matching cost
1099 # Sort the selected fields in order of increasing matching cost
1090 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1100 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1091 'files', 'description', 'substate']
1101 'files', 'description', 'substate']
1092 def fieldkeyfunc(f):
1102 def fieldkeyfunc(f):
1093 try:
1103 try:
1094 return fieldorder.index(f)
1104 return fieldorder.index(f)
1095 except ValueError:
1105 except ValueError:
1096 # assume an unknown field is very costly
1106 # assume an unknown field is very costly
1097 return len(fieldorder)
1107 return len(fieldorder)
1098 fields = list(fields)
1108 fields = list(fields)
1099 fields.sort(key=fieldkeyfunc)
1109 fields.sort(key=fieldkeyfunc)
1100
1110
1101 # Each field will be matched with its own "getfield" function
1111 # Each field will be matched with its own "getfield" function
1102 # which will be added to the getfieldfuncs array of functions
1112 # which will be added to the getfieldfuncs array of functions
1103 getfieldfuncs = []
1113 getfieldfuncs = []
1104 _funcs = {
1114 _funcs = {
1105 'user': lambda r: repo[r].user(),
1115 'user': lambda r: repo[r].user(),
1106 'branch': lambda r: repo[r].branch(),
1116 'branch': lambda r: repo[r].branch(),
1107 'date': lambda r: repo[r].date(),
1117 'date': lambda r: repo[r].date(),
1108 'description': lambda r: repo[r].description(),
1118 'description': lambda r: repo[r].description(),
1109 'files': lambda r: repo[r].files(),
1119 'files': lambda r: repo[r].files(),
1110 'parents': lambda r: repo[r].parents(),
1120 'parents': lambda r: repo[r].parents(),
1111 'phase': lambda r: repo[r].phase(),
1121 'phase': lambda r: repo[r].phase(),
1112 'substate': lambda r: repo[r].substate,
1122 'substate': lambda r: repo[r].substate,
1113 'summary': lambda r: repo[r].description().splitlines()[0],
1123 'summary': lambda r: repo[r].description().splitlines()[0],
1114 }
1124 }
1115 for info in fields:
1125 for info in fields:
1116 getfield = _funcs.get(info, None)
1126 getfield = _funcs.get(info, None)
1117 if getfield is None:
1127 if getfield is None:
1118 raise error.ParseError(
1128 raise error.ParseError(
1119 _("unexpected field name passed to matching: %s") % info)
1129 _("unexpected field name passed to matching: %s") % info)
1120 getfieldfuncs.append(getfield)
1130 getfieldfuncs.append(getfield)
1121 # convert the getfield array of functions into a "getinfo" function
1131 # convert the getfield array of functions into a "getinfo" function
1122 # which returns an array of field values (or a single value if there
1132 # which returns an array of field values (or a single value if there
1123 # is only one field to match)
1133 # is only one field to match)
1124 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1134 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1125
1135
1126 matches = set()
1136 matches = set()
1127 for rev in revs:
1137 for rev in revs:
1128 target = getinfo(rev)
1138 target = getinfo(rev)
1129 for r in subset:
1139 for r in subset:
1130 match = True
1140 match = True
1131 for n, f in enumerate(getfieldfuncs):
1141 for n, f in enumerate(getfieldfuncs):
1132 if target[n] != f(r):
1142 if target[n] != f(r):
1133 match = False
1143 match = False
1134 break
1144 break
1135 if match:
1145 if match:
1136 matches.add(r)
1146 matches.add(r)
1137 return [r for r in subset if r in matches]
1147 return [r for r in subset if r in matches]
1138
1148
1139 def reverse(repo, subset, x):
1149 def reverse(repo, subset, x):
1140 """``reverse(set)``
1150 """``reverse(set)``
1141 Reverse order of set.
1151 Reverse order of set.
1142 """
1152 """
1143 l = getset(repo, subset, x)
1153 l = getset(repo, subset, x)
1144 l.reverse()
1154 l.reverse()
1145 return l
1155 return l
1146
1156
1147 def roots(repo, subset, x):
1157 def roots(repo, subset, x):
1148 """``roots(set)``
1158 """``roots(set)``
1149 Changesets in set with no parent changeset in set.
1159 Changesets in set with no parent changeset in set.
1150 """
1160 """
1151 s = set(getset(repo, xrange(len(repo)), x))
1161 s = set(getset(repo, xrange(len(repo)), x))
1152 subset = [r for r in subset if r in s]
1162 subset = [r for r in subset if r in s]
1153 cs = _children(repo, subset, s)
1163 cs = _children(repo, subset, s)
1154 return [r for r in subset if r not in cs]
1164 return [r for r in subset if r not in cs]
1155
1165
1156 def secret(repo, subset, x):
1166 def secret(repo, subset, x):
1157 """``secret()``
1167 """``secret()``
1158 Changeset in secret phase."""
1168 Changeset in secret phase."""
1159 getargs(x, 0, 0, _("secret takes no arguments"))
1169 getargs(x, 0, 0, _("secret takes no arguments"))
1160 pc = repo._phasecache
1170 pc = repo._phasecache
1161 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1171 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1162
1172
1163 def sort(repo, subset, x):
1173 def sort(repo, subset, x):
1164 """``sort(set[, [-]key...])``
1174 """``sort(set[, [-]key...])``
1165 Sort set by keys. The default sort order is ascending, specify a key
1175 Sort set by keys. The default sort order is ascending, specify a key
1166 as ``-key`` to sort in descending order.
1176 as ``-key`` to sort in descending order.
1167
1177
1168 The keys can be:
1178 The keys can be:
1169
1179
1170 - ``rev`` for the revision number,
1180 - ``rev`` for the revision number,
1171 - ``branch`` for the branch name,
1181 - ``branch`` for the branch name,
1172 - ``desc`` for the commit message (description),
1182 - ``desc`` for the commit message (description),
1173 - ``user`` for user name (``author`` can be used as an alias),
1183 - ``user`` for user name (``author`` can be used as an alias),
1174 - ``date`` for the commit date
1184 - ``date`` for the commit date
1175 """
1185 """
1176 # i18n: "sort" is a keyword
1186 # i18n: "sort" is a keyword
1177 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1187 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1178 keys = "rev"
1188 keys = "rev"
1179 if len(l) == 2:
1189 if len(l) == 2:
1180 keys = getstring(l[1], _("sort spec must be a string"))
1190 keys = getstring(l[1], _("sort spec must be a string"))
1181
1191
1182 s = l[0]
1192 s = l[0]
1183 keys = keys.split()
1193 keys = keys.split()
1184 l = []
1194 l = []
1185 def invert(s):
1195 def invert(s):
1186 return "".join(chr(255 - ord(c)) for c in s)
1196 return "".join(chr(255 - ord(c)) for c in s)
1187 for r in getset(repo, subset, s):
1197 for r in getset(repo, subset, s):
1188 c = repo[r]
1198 c = repo[r]
1189 e = []
1199 e = []
1190 for k in keys:
1200 for k in keys:
1191 if k == 'rev':
1201 if k == 'rev':
1192 e.append(r)
1202 e.append(r)
1193 elif k == '-rev':
1203 elif k == '-rev':
1194 e.append(-r)
1204 e.append(-r)
1195 elif k == 'branch':
1205 elif k == 'branch':
1196 e.append(c.branch())
1206 e.append(c.branch())
1197 elif k == '-branch':
1207 elif k == '-branch':
1198 e.append(invert(c.branch()))
1208 e.append(invert(c.branch()))
1199 elif k == 'desc':
1209 elif k == 'desc':
1200 e.append(c.description())
1210 e.append(c.description())
1201 elif k == '-desc':
1211 elif k == '-desc':
1202 e.append(invert(c.description()))
1212 e.append(invert(c.description()))
1203 elif k in 'user author':
1213 elif k in 'user author':
1204 e.append(c.user())
1214 e.append(c.user())
1205 elif k in '-user -author':
1215 elif k in '-user -author':
1206 e.append(invert(c.user()))
1216 e.append(invert(c.user()))
1207 elif k == 'date':
1217 elif k == 'date':
1208 e.append(c.date()[0])
1218 e.append(c.date()[0])
1209 elif k == '-date':
1219 elif k == '-date':
1210 e.append(-c.date()[0])
1220 e.append(-c.date()[0])
1211 else:
1221 else:
1212 raise error.ParseError(_("unknown sort key %r") % k)
1222 raise error.ParseError(_("unknown sort key %r") % k)
1213 e.append(r)
1223 e.append(r)
1214 l.append(e)
1224 l.append(e)
1215 l.sort()
1225 l.sort()
1216 return [e[-1] for e in l]
1226 return [e[-1] for e in l]
1217
1227
1218 def _stringmatcher(pattern):
1228 def _stringmatcher(pattern):
1219 """
1229 """
1220 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1230 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1221 returns the matcher name, pattern, and matcher function.
1231 returns the matcher name, pattern, and matcher function.
1222 missing or unknown prefixes are treated as literal matches.
1232 missing or unknown prefixes are treated as literal matches.
1223
1233
1224 helper for tests:
1234 helper for tests:
1225 >>> def test(pattern, *tests):
1235 >>> def test(pattern, *tests):
1226 ... kind, pattern, matcher = _stringmatcher(pattern)
1236 ... kind, pattern, matcher = _stringmatcher(pattern)
1227 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1237 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1228
1238
1229 exact matching (no prefix):
1239 exact matching (no prefix):
1230 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1240 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1231 ('literal', 'abcdefg', [False, False, True])
1241 ('literal', 'abcdefg', [False, False, True])
1232
1242
1233 regex matching ('re:' prefix)
1243 regex matching ('re:' prefix)
1234 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1244 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1235 ('re', 'a.+b', [False, False, True])
1245 ('re', 'a.+b', [False, False, True])
1236
1246
1237 force exact matches ('literal:' prefix)
1247 force exact matches ('literal:' prefix)
1238 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1248 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1239 ('literal', 're:foobar', [False, True])
1249 ('literal', 're:foobar', [False, True])
1240
1250
1241 unknown prefixes are ignored and treated as literals
1251 unknown prefixes are ignored and treated as literals
1242 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1252 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1243 ('literal', 'foo:bar', [False, False, True])
1253 ('literal', 'foo:bar', [False, False, True])
1244 """
1254 """
1245 if pattern.startswith('re:'):
1255 if pattern.startswith('re:'):
1246 pattern = pattern[3:]
1256 pattern = pattern[3:]
1247 try:
1257 try:
1248 regex = re.compile(pattern)
1258 regex = re.compile(pattern)
1249 except re.error, e:
1259 except re.error, e:
1250 raise error.ParseError(_('invalid regular expression: %s')
1260 raise error.ParseError(_('invalid regular expression: %s')
1251 % e)
1261 % e)
1252 return 're', pattern, regex.search
1262 return 're', pattern, regex.search
1253 elif pattern.startswith('literal:'):
1263 elif pattern.startswith('literal:'):
1254 pattern = pattern[8:]
1264 pattern = pattern[8:]
1255 return 'literal', pattern, pattern.__eq__
1265 return 'literal', pattern, pattern.__eq__
1256
1266
1257 def _substringmatcher(pattern):
1267 def _substringmatcher(pattern):
1258 kind, pattern, matcher = _stringmatcher(pattern)
1268 kind, pattern, matcher = _stringmatcher(pattern)
1259 if kind == 'literal':
1269 if kind == 'literal':
1260 matcher = lambda s: pattern in s
1270 matcher = lambda s: pattern in s
1261 return kind, pattern, matcher
1271 return kind, pattern, matcher
1262
1272
1263 def tag(repo, subset, x):
1273 def tag(repo, subset, x):
1264 """``tag([name])``
1274 """``tag([name])``
1265 The specified tag by name, or all tagged revisions if no name is given.
1275 The specified tag by name, or all tagged revisions if no name is given.
1266 """
1276 """
1267 # i18n: "tag" is a keyword
1277 # i18n: "tag" is a keyword
1268 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1278 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1269 cl = repo.changelog
1279 cl = repo.changelog
1270 if args:
1280 if args:
1271 pattern = getstring(args[0],
1281 pattern = getstring(args[0],
1272 # i18n: "tag" is a keyword
1282 # i18n: "tag" is a keyword
1273 _('the argument to tag must be a string'))
1283 _('the argument to tag must be a string'))
1274 kind, pattern, matcher = _stringmatcher(pattern)
1284 kind, pattern, matcher = _stringmatcher(pattern)
1275 if kind == 'literal':
1285 if kind == 'literal':
1276 # avoid resolving all tags
1286 # avoid resolving all tags
1277 tn = repo._tagscache.tags.get(pattern, None)
1287 tn = repo._tagscache.tags.get(pattern, None)
1278 if tn is None:
1288 if tn is None:
1279 raise util.Abort(_("tag '%s' does not exist") % pattern)
1289 raise util.Abort(_("tag '%s' does not exist") % pattern)
1280 s = set([repo[tn].rev()])
1290 s = set([repo[tn].rev()])
1281 else:
1291 else:
1282 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1292 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1283 if not s:
1293 if not s:
1284 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1294 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1285 else:
1295 else:
1286 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1296 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1287 return [r for r in subset if r in s]
1297 return [r for r in subset if r in s]
1288
1298
1289 def tagged(repo, subset, x):
1299 def tagged(repo, subset, x):
1290 return tag(repo, subset, x)
1300 return tag(repo, subset, x)
1291
1301
1292 def user(repo, subset, x):
1302 def user(repo, subset, x):
1293 """``user(string)``
1303 """``user(string)``
1294 User name contains string. The match is case-insensitive.
1304 User name contains string. The match is case-insensitive.
1295
1305
1296 If `string` starts with `re:`, the remainder of the string is treated as
1306 If `string` starts with `re:`, the remainder of the string is treated as
1297 a regular expression. To match a user that actually contains `re:`, use
1307 a regular expression. To match a user that actually contains `re:`, use
1298 the prefix `literal:`.
1308 the prefix `literal:`.
1299 """
1309 """
1300 return author(repo, subset, x)
1310 return author(repo, subset, x)
1301
1311
1302 # for internal use
1312 # for internal use
1303 def _list(repo, subset, x):
1313 def _list(repo, subset, x):
1304 s = getstring(x, "internal error")
1314 s = getstring(x, "internal error")
1305 if not s:
1315 if not s:
1306 return []
1316 return []
1307 if not isinstance(subset, set):
1317 if not isinstance(subset, set):
1308 subset = set(subset)
1318 subset = set(subset)
1309 ls = [repo[r].rev() for r in s.split('\0')]
1319 ls = [repo[r].rev() for r in s.split('\0')]
1310 return [r for r in ls if r in subset]
1320 return [r for r in ls if r in subset]
1311
1321
1312 symbols = {
1322 symbols = {
1313 "adds": adds,
1323 "adds": adds,
1314 "all": getall,
1324 "all": getall,
1315 "ancestor": ancestor,
1325 "ancestor": ancestor,
1316 "ancestors": ancestors,
1326 "ancestors": ancestors,
1317 "_firstancestors": _firstancestors,
1327 "_firstancestors": _firstancestors,
1318 "author": author,
1328 "author": author,
1319 "bisect": bisect,
1329 "bisect": bisect,
1320 "bisected": bisected,
1330 "bisected": bisected,
1321 "bookmark": bookmark,
1331 "bookmark": bookmark,
1322 "branch": branch,
1332 "branch": branch,
1323 "children": children,
1333 "children": children,
1324 "closed": closed,
1334 "closed": closed,
1325 "contains": contains,
1335 "contains": contains,
1326 "converted": converted,
1336 "converted": converted,
1327 "date": date,
1337 "date": date,
1328 "desc": desc,
1338 "desc": desc,
1329 "descendants": descendants,
1339 "descendants": descendants,
1330 "_firstdescendants": _firstdescendants,
1340 "_firstdescendants": _firstdescendants,
1331 "draft": draft,
1341 "draft": draft,
1332 "extra": extra,
1342 "extra": extra,
1333 "file": hasfile,
1343 "file": hasfile,
1334 "filelog": filelog,
1344 "filelog": filelog,
1335 "first": first,
1345 "first": first,
1336 "follow": follow,
1346 "follow": follow,
1337 "_followfirst": _followfirst,
1347 "_followfirst": _followfirst,
1338 "grep": grep,
1348 "grep": grep,
1339 "head": head,
1349 "head": head,
1340 "heads": heads,
1350 "heads": heads,
1341 "id": node_,
1351 "id": node_,
1342 "keyword": keyword,
1352 "keyword": keyword,
1343 "last": last,
1353 "last": last,
1344 "limit": limit,
1354 "limit": limit,
1345 "_matchfiles": _matchfiles,
1355 "_matchfiles": _matchfiles,
1346 "max": maxrev,
1356 "max": maxrev,
1347 "merge": merge,
1357 "merge": merge,
1348 "min": minrev,
1358 "min": minrev,
1349 "modifies": modifies,
1359 "modifies": modifies,
1350 "outgoing": outgoing,
1360 "outgoing": outgoing,
1351 "p1": p1,
1361 "p1": p1,
1352 "p2": p2,
1362 "p2": p2,
1353 "parents": parents,
1363 "parents": parents,
1354 "present": present,
1364 "present": present,
1355 "public": public,
1365 "public": public,
1356 "remote": remote,
1366 "remote": remote,
1357 "removes": removes,
1367 "removes": removes,
1358 "rev": rev,
1368 "rev": rev,
1359 "reverse": reverse,
1369 "reverse": reverse,
1360 "roots": roots,
1370 "roots": roots,
1361 "sort": sort,
1371 "sort": sort,
1362 "secret": secret,
1372 "secret": secret,
1363 "matching": matching,
1373 "matching": matching,
1364 "tag": tag,
1374 "tag": tag,
1365 "tagged": tagged,
1375 "tagged": tagged,
1366 "user": user,
1376 "user": user,
1367 "_list": _list,
1377 "_list": _list,
1368 }
1378 }
1369
1379
1370 methods = {
1380 methods = {
1371 "range": rangeset,
1381 "range": rangeset,
1372 "dagrange": dagrange,
1382 "dagrange": dagrange,
1373 "string": stringset,
1383 "string": stringset,
1374 "symbol": symbolset,
1384 "symbol": symbolset,
1375 "and": andset,
1385 "and": andset,
1376 "or": orset,
1386 "or": orset,
1377 "not": notset,
1387 "not": notset,
1378 "list": listset,
1388 "list": listset,
1379 "func": func,
1389 "func": func,
1380 "ancestor": ancestorspec,
1390 "ancestor": ancestorspec,
1381 "parent": parentspec,
1391 "parent": parentspec,
1382 "parentpost": p1,
1392 "parentpost": p1,
1383 }
1393 }
1384
1394
1385 def optimize(x, small):
1395 def optimize(x, small):
1386 if x is None:
1396 if x is None:
1387 return 0, x
1397 return 0, x
1388
1398
1389 smallbonus = 1
1399 smallbonus = 1
1390 if small:
1400 if small:
1391 smallbonus = .5
1401 smallbonus = .5
1392
1402
1393 op = x[0]
1403 op = x[0]
1394 if op == 'minus':
1404 if op == 'minus':
1395 return optimize(('and', x[1], ('not', x[2])), small)
1405 return optimize(('and', x[1], ('not', x[2])), small)
1396 elif op == 'dagrangepre':
1406 elif op == 'dagrangepre':
1397 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1407 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1398 elif op == 'dagrangepost':
1408 elif op == 'dagrangepost':
1399 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1409 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1400 elif op == 'rangepre':
1410 elif op == 'rangepre':
1401 return optimize(('range', ('string', '0'), x[1]), small)
1411 return optimize(('range', ('string', '0'), x[1]), small)
1402 elif op == 'rangepost':
1412 elif op == 'rangepost':
1403 return optimize(('range', x[1], ('string', 'tip')), small)
1413 return optimize(('range', x[1], ('string', 'tip')), small)
1404 elif op == 'negate':
1414 elif op == 'negate':
1405 return optimize(('string',
1415 return optimize(('string',
1406 '-' + getstring(x[1], _("can't negate that"))), small)
1416 '-' + getstring(x[1], _("can't negate that"))), small)
1407 elif op in 'string symbol negate':
1417 elif op in 'string symbol negate':
1408 return smallbonus, x # single revisions are small
1418 return smallbonus, x # single revisions are small
1409 elif op == 'and':
1419 elif op == 'and':
1410 wa, ta = optimize(x[1], True)
1420 wa, ta = optimize(x[1], True)
1411 wb, tb = optimize(x[2], True)
1421 wb, tb = optimize(x[2], True)
1412 w = min(wa, wb)
1422 w = min(wa, wb)
1413 if wa > wb:
1423 if wa > wb:
1414 return w, (op, tb, ta)
1424 return w, (op, tb, ta)
1415 return w, (op, ta, tb)
1425 return w, (op, ta, tb)
1416 elif op == 'or':
1426 elif op == 'or':
1417 wa, ta = optimize(x[1], False)
1427 wa, ta = optimize(x[1], False)
1418 wb, tb = optimize(x[2], False)
1428 wb, tb = optimize(x[2], False)
1419 if wb < wa:
1429 if wb < wa:
1420 wb, wa = wa, wb
1430 wb, wa = wa, wb
1421 return max(wa, wb), (op, ta, tb)
1431 return max(wa, wb), (op, ta, tb)
1422 elif op == 'not':
1432 elif op == 'not':
1423 o = optimize(x[1], not small)
1433 o = optimize(x[1], not small)
1424 return o[0], (op, o[1])
1434 return o[0], (op, o[1])
1425 elif op == 'parentpost':
1435 elif op == 'parentpost':
1426 o = optimize(x[1], small)
1436 o = optimize(x[1], small)
1427 return o[0], (op, o[1])
1437 return o[0], (op, o[1])
1428 elif op == 'group':
1438 elif op == 'group':
1429 return optimize(x[1], small)
1439 return optimize(x[1], small)
1430 elif op in 'dagrange range list parent ancestorspec':
1440 elif op in 'dagrange range list parent ancestorspec':
1431 if op == 'parent':
1441 if op == 'parent':
1432 # x^:y means (x^) : y, not x ^ (:y)
1442 # x^:y means (x^) : y, not x ^ (:y)
1433 post = ('parentpost', x[1])
1443 post = ('parentpost', x[1])
1434 if x[2][0] == 'dagrangepre':
1444 if x[2][0] == 'dagrangepre':
1435 return optimize(('dagrange', post, x[2][1]), small)
1445 return optimize(('dagrange', post, x[2][1]), small)
1436 elif x[2][0] == 'rangepre':
1446 elif x[2][0] == 'rangepre':
1437 return optimize(('range', post, x[2][1]), small)
1447 return optimize(('range', post, x[2][1]), small)
1438
1448
1439 wa, ta = optimize(x[1], small)
1449 wa, ta = optimize(x[1], small)
1440 wb, tb = optimize(x[2], small)
1450 wb, tb = optimize(x[2], small)
1441 return wa + wb, (op, ta, tb)
1451 return wa + wb, (op, ta, tb)
1442 elif op == 'func':
1452 elif op == 'func':
1443 f = getstring(x[1], _("not a symbol"))
1453 f = getstring(x[1], _("not a symbol"))
1444 wa, ta = optimize(x[2], small)
1454 wa, ta = optimize(x[2], small)
1445 if f in ("author branch closed date desc file grep keyword "
1455 if f in ("author branch closed date desc file grep keyword "
1446 "outgoing user"):
1456 "outgoing user"):
1447 w = 10 # slow
1457 w = 10 # slow
1448 elif f in "modifies adds removes":
1458 elif f in "modifies adds removes":
1449 w = 30 # slower
1459 w = 30 # slower
1450 elif f == "contains":
1460 elif f == "contains":
1451 w = 100 # very slow
1461 w = 100 # very slow
1452 elif f == "ancestor":
1462 elif f == "ancestor":
1453 w = 1 * smallbonus
1463 w = 1 * smallbonus
1454 elif f in "reverse limit first":
1464 elif f in "reverse limit first":
1455 w = 0
1465 w = 0
1456 elif f in "sort":
1466 elif f in "sort":
1457 w = 10 # assume most sorts look at changelog
1467 w = 10 # assume most sorts look at changelog
1458 else:
1468 else:
1459 w = 1
1469 w = 1
1460 return w + wa, (op, x[1], ta)
1470 return w + wa, (op, x[1], ta)
1461 return 1, x
1471 return 1, x
1462
1472
1463 _aliasarg = ('func', ('symbol', '_aliasarg'))
1473 _aliasarg = ('func', ('symbol', '_aliasarg'))
1464 def _getaliasarg(tree):
1474 def _getaliasarg(tree):
1465 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1475 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1466 return X, None otherwise.
1476 return X, None otherwise.
1467 """
1477 """
1468 if (len(tree) == 3 and tree[:2] == _aliasarg
1478 if (len(tree) == 3 and tree[:2] == _aliasarg
1469 and tree[2][0] == 'string'):
1479 and tree[2][0] == 'string'):
1470 return tree[2][1]
1480 return tree[2][1]
1471 return None
1481 return None
1472
1482
1473 def _checkaliasarg(tree, known=None):
1483 def _checkaliasarg(tree, known=None):
1474 """Check tree contains no _aliasarg construct or only ones which
1484 """Check tree contains no _aliasarg construct or only ones which
1475 value is in known. Used to avoid alias placeholders injection.
1485 value is in known. Used to avoid alias placeholders injection.
1476 """
1486 """
1477 if isinstance(tree, tuple):
1487 if isinstance(tree, tuple):
1478 arg = _getaliasarg(tree)
1488 arg = _getaliasarg(tree)
1479 if arg is not None and (not known or arg not in known):
1489 if arg is not None and (not known or arg not in known):
1480 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1490 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1481 for t in tree:
1491 for t in tree:
1482 _checkaliasarg(t, known)
1492 _checkaliasarg(t, known)
1483
1493
1484 class revsetalias(object):
1494 class revsetalias(object):
1485 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1495 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1486 args = None
1496 args = None
1487
1497
1488 def __init__(self, name, value):
1498 def __init__(self, name, value):
1489 '''Aliases like:
1499 '''Aliases like:
1490
1500
1491 h = heads(default)
1501 h = heads(default)
1492 b($1) = ancestors($1) - ancestors(default)
1502 b($1) = ancestors($1) - ancestors(default)
1493 '''
1503 '''
1494 m = self.funcre.search(name)
1504 m = self.funcre.search(name)
1495 if m:
1505 if m:
1496 self.name = m.group(1)
1506 self.name = m.group(1)
1497 self.tree = ('func', ('symbol', m.group(1)))
1507 self.tree = ('func', ('symbol', m.group(1)))
1498 self.args = [x.strip() for x in m.group(2).split(',')]
1508 self.args = [x.strip() for x in m.group(2).split(',')]
1499 for arg in self.args:
1509 for arg in self.args:
1500 # _aliasarg() is an unknown symbol only used separate
1510 # _aliasarg() is an unknown symbol only used separate
1501 # alias argument placeholders from regular strings.
1511 # alias argument placeholders from regular strings.
1502 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1512 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1503 else:
1513 else:
1504 self.name = name
1514 self.name = name
1505 self.tree = ('symbol', name)
1515 self.tree = ('symbol', name)
1506
1516
1507 self.replacement, pos = parse(value)
1517 self.replacement, pos = parse(value)
1508 if pos != len(value):
1518 if pos != len(value):
1509 raise error.ParseError(_('invalid token'), pos)
1519 raise error.ParseError(_('invalid token'), pos)
1510 # Check for placeholder injection
1520 # Check for placeholder injection
1511 _checkaliasarg(self.replacement, self.args)
1521 _checkaliasarg(self.replacement, self.args)
1512
1522
1513 def _getalias(aliases, tree):
1523 def _getalias(aliases, tree):
1514 """If tree looks like an unexpanded alias, return it. Return None
1524 """If tree looks like an unexpanded alias, return it. Return None
1515 otherwise.
1525 otherwise.
1516 """
1526 """
1517 if isinstance(tree, tuple) and tree:
1527 if isinstance(tree, tuple) and tree:
1518 if tree[0] == 'symbol' and len(tree) == 2:
1528 if tree[0] == 'symbol' and len(tree) == 2:
1519 name = tree[1]
1529 name = tree[1]
1520 alias = aliases.get(name)
1530 alias = aliases.get(name)
1521 if alias and alias.args is None and alias.tree == tree:
1531 if alias and alias.args is None and alias.tree == tree:
1522 return alias
1532 return alias
1523 if tree[0] == 'func' and len(tree) > 1:
1533 if tree[0] == 'func' and len(tree) > 1:
1524 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1534 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1525 name = tree[1][1]
1535 name = tree[1][1]
1526 alias = aliases.get(name)
1536 alias = aliases.get(name)
1527 if alias and alias.args is not None and alias.tree == tree[:2]:
1537 if alias and alias.args is not None and alias.tree == tree[:2]:
1528 return alias
1538 return alias
1529 return None
1539 return None
1530
1540
1531 def _expandargs(tree, args):
1541 def _expandargs(tree, args):
1532 """Replace _aliasarg instances with the substitution value of the
1542 """Replace _aliasarg instances with the substitution value of the
1533 same name in args, recursively.
1543 same name in args, recursively.
1534 """
1544 """
1535 if not tree or not isinstance(tree, tuple):
1545 if not tree or not isinstance(tree, tuple):
1536 return tree
1546 return tree
1537 arg = _getaliasarg(tree)
1547 arg = _getaliasarg(tree)
1538 if arg is not None:
1548 if arg is not None:
1539 return args[arg]
1549 return args[arg]
1540 return tuple(_expandargs(t, args) for t in tree)
1550 return tuple(_expandargs(t, args) for t in tree)
1541
1551
1542 def _expandaliases(aliases, tree, expanding, cache):
1552 def _expandaliases(aliases, tree, expanding, cache):
1543 """Expand aliases in tree, recursively.
1553 """Expand aliases in tree, recursively.
1544
1554
1545 'aliases' is a dictionary mapping user defined aliases to
1555 'aliases' is a dictionary mapping user defined aliases to
1546 revsetalias objects.
1556 revsetalias objects.
1547 """
1557 """
1548 if not isinstance(tree, tuple):
1558 if not isinstance(tree, tuple):
1549 # Do not expand raw strings
1559 # Do not expand raw strings
1550 return tree
1560 return tree
1551 alias = _getalias(aliases, tree)
1561 alias = _getalias(aliases, tree)
1552 if alias is not None:
1562 if alias is not None:
1553 if alias in expanding:
1563 if alias in expanding:
1554 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1564 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1555 'detected') % alias.name)
1565 'detected') % alias.name)
1556 expanding.append(alias)
1566 expanding.append(alias)
1557 if alias.name not in cache:
1567 if alias.name not in cache:
1558 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1568 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1559 expanding, cache)
1569 expanding, cache)
1560 result = cache[alias.name]
1570 result = cache[alias.name]
1561 expanding.pop()
1571 expanding.pop()
1562 if alias.args is not None:
1572 if alias.args is not None:
1563 l = getlist(tree[2])
1573 l = getlist(tree[2])
1564 if len(l) != len(alias.args):
1574 if len(l) != len(alias.args):
1565 raise error.ParseError(
1575 raise error.ParseError(
1566 _('invalid number of arguments: %s') % len(l))
1576 _('invalid number of arguments: %s') % len(l))
1567 l = [_expandaliases(aliases, a, [], cache) for a in l]
1577 l = [_expandaliases(aliases, a, [], cache) for a in l]
1568 result = _expandargs(result, dict(zip(alias.args, l)))
1578 result = _expandargs(result, dict(zip(alias.args, l)))
1569 else:
1579 else:
1570 result = tuple(_expandaliases(aliases, t, expanding, cache)
1580 result = tuple(_expandaliases(aliases, t, expanding, cache)
1571 for t in tree)
1581 for t in tree)
1572 return result
1582 return result
1573
1583
1574 def findaliases(ui, tree):
1584 def findaliases(ui, tree):
1575 _checkaliasarg(tree)
1585 _checkaliasarg(tree)
1576 aliases = {}
1586 aliases = {}
1577 for k, v in ui.configitems('revsetalias'):
1587 for k, v in ui.configitems('revsetalias'):
1578 alias = revsetalias(k, v)
1588 alias = revsetalias(k, v)
1579 aliases[alias.name] = alias
1589 aliases[alias.name] = alias
1580 return _expandaliases(aliases, tree, [], {})
1590 return _expandaliases(aliases, tree, [], {})
1581
1591
1582 parse = parser.parser(tokenize, elements).parse
1592 parse = parser.parser(tokenize, elements).parse
1583
1593
1584 def match(ui, spec):
1594 def match(ui, spec):
1585 if not spec:
1595 if not spec:
1586 raise error.ParseError(_("empty query"))
1596 raise error.ParseError(_("empty query"))
1587 tree, pos = parse(spec)
1597 tree, pos = parse(spec)
1588 if (pos != len(spec)):
1598 if (pos != len(spec)):
1589 raise error.ParseError(_("invalid token"), pos)
1599 raise error.ParseError(_("invalid token"), pos)
1590 if ui:
1600 if ui:
1591 tree = findaliases(ui, tree)
1601 tree = findaliases(ui, tree)
1592 weight, tree = optimize(tree, True)
1602 weight, tree = optimize(tree, True)
1593 def mfunc(repo, subset):
1603 def mfunc(repo, subset):
1594 return getset(repo, subset, tree)
1604 return getset(repo, subset, tree)
1595 return mfunc
1605 return mfunc
1596
1606
1597 def formatspec(expr, *args):
1607 def formatspec(expr, *args):
1598 '''
1608 '''
1599 This is a convenience function for using revsets internally, and
1609 This is a convenience function for using revsets internally, and
1600 escapes arguments appropriately. Aliases are intentionally ignored
1610 escapes arguments appropriately. Aliases are intentionally ignored
1601 so that intended expression behavior isn't accidentally subverted.
1611 so that intended expression behavior isn't accidentally subverted.
1602
1612
1603 Supported arguments:
1613 Supported arguments:
1604
1614
1605 %r = revset expression, parenthesized
1615 %r = revset expression, parenthesized
1606 %d = int(arg), no quoting
1616 %d = int(arg), no quoting
1607 %s = string(arg), escaped and single-quoted
1617 %s = string(arg), escaped and single-quoted
1608 %b = arg.branch(), escaped and single-quoted
1618 %b = arg.branch(), escaped and single-quoted
1609 %n = hex(arg), single-quoted
1619 %n = hex(arg), single-quoted
1610 %% = a literal '%'
1620 %% = a literal '%'
1611
1621
1612 Prefixing the type with 'l' specifies a parenthesized list of that type.
1622 Prefixing the type with 'l' specifies a parenthesized list of that type.
1613
1623
1614 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1624 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1615 '(10 or 11):: and ((this()) or (that()))'
1625 '(10 or 11):: and ((this()) or (that()))'
1616 >>> formatspec('%d:: and not %d::', 10, 20)
1626 >>> formatspec('%d:: and not %d::', 10, 20)
1617 '10:: and not 20::'
1627 '10:: and not 20::'
1618 >>> formatspec('%ld or %ld', [], [1])
1628 >>> formatspec('%ld or %ld', [], [1])
1619 "_list('') or 1"
1629 "_list('') or 1"
1620 >>> formatspec('keyword(%s)', 'foo\\xe9')
1630 >>> formatspec('keyword(%s)', 'foo\\xe9')
1621 "keyword('foo\\\\xe9')"
1631 "keyword('foo\\\\xe9')"
1622 >>> b = lambda: 'default'
1632 >>> b = lambda: 'default'
1623 >>> b.branch = b
1633 >>> b.branch = b
1624 >>> formatspec('branch(%b)', b)
1634 >>> formatspec('branch(%b)', b)
1625 "branch('default')"
1635 "branch('default')"
1626 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1636 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1627 "root(_list('a\\x00b\\x00c\\x00d'))"
1637 "root(_list('a\\x00b\\x00c\\x00d'))"
1628 '''
1638 '''
1629
1639
1630 def quote(s):
1640 def quote(s):
1631 return repr(str(s))
1641 return repr(str(s))
1632
1642
1633 def argtype(c, arg):
1643 def argtype(c, arg):
1634 if c == 'd':
1644 if c == 'd':
1635 return str(int(arg))
1645 return str(int(arg))
1636 elif c == 's':
1646 elif c == 's':
1637 return quote(arg)
1647 return quote(arg)
1638 elif c == 'r':
1648 elif c == 'r':
1639 parse(arg) # make sure syntax errors are confined
1649 parse(arg) # make sure syntax errors are confined
1640 return '(%s)' % arg
1650 return '(%s)' % arg
1641 elif c == 'n':
1651 elif c == 'n':
1642 return quote(node.hex(arg))
1652 return quote(node.hex(arg))
1643 elif c == 'b':
1653 elif c == 'b':
1644 return quote(arg.branch())
1654 return quote(arg.branch())
1645
1655
1646 def listexp(s, t):
1656 def listexp(s, t):
1647 l = len(s)
1657 l = len(s)
1648 if l == 0:
1658 if l == 0:
1649 return "_list('')"
1659 return "_list('')"
1650 elif l == 1:
1660 elif l == 1:
1651 return argtype(t, s[0])
1661 return argtype(t, s[0])
1652 elif t == 'd':
1662 elif t == 'd':
1653 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1663 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1654 elif t == 's':
1664 elif t == 's':
1655 return "_list('%s')" % "\0".join(s)
1665 return "_list('%s')" % "\0".join(s)
1656 elif t == 'n':
1666 elif t == 'n':
1657 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1667 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1658 elif t == 'b':
1668 elif t == 'b':
1659 return "_list('%s')" % "\0".join(a.branch() for a in s)
1669 return "_list('%s')" % "\0".join(a.branch() for a in s)
1660
1670
1661 m = l // 2
1671 m = l // 2
1662 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1672 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1663
1673
1664 ret = ''
1674 ret = ''
1665 pos = 0
1675 pos = 0
1666 arg = 0
1676 arg = 0
1667 while pos < len(expr):
1677 while pos < len(expr):
1668 c = expr[pos]
1678 c = expr[pos]
1669 if c == '%':
1679 if c == '%':
1670 pos += 1
1680 pos += 1
1671 d = expr[pos]
1681 d = expr[pos]
1672 if d == '%':
1682 if d == '%':
1673 ret += d
1683 ret += d
1674 elif d in 'dsnbr':
1684 elif d in 'dsnbr':
1675 ret += argtype(d, args[arg])
1685 ret += argtype(d, args[arg])
1676 arg += 1
1686 arg += 1
1677 elif d == 'l':
1687 elif d == 'l':
1678 # a list of some type
1688 # a list of some type
1679 pos += 1
1689 pos += 1
1680 d = expr[pos]
1690 d = expr[pos]
1681 ret += listexp(list(args[arg]), d)
1691 ret += listexp(list(args[arg]), d)
1682 arg += 1
1692 arg += 1
1683 else:
1693 else:
1684 raise util.Abort('unexpected revspec format character %s' % d)
1694 raise util.Abort('unexpected revspec format character %s' % d)
1685 else:
1695 else:
1686 ret += c
1696 ret += c
1687 pos += 1
1697 pos += 1
1688
1698
1689 return ret
1699 return ret
1690
1700
1691 def prettyformat(tree):
1701 def prettyformat(tree):
1692 def _prettyformat(tree, level, lines):
1702 def _prettyformat(tree, level, lines):
1693 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1703 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1694 lines.append((level, str(tree)))
1704 lines.append((level, str(tree)))
1695 else:
1705 else:
1696 lines.append((level, '(%s' % tree[0]))
1706 lines.append((level, '(%s' % tree[0]))
1697 for s in tree[1:]:
1707 for s in tree[1:]:
1698 _prettyformat(s, level + 1, lines)
1708 _prettyformat(s, level + 1, lines)
1699 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1709 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1700
1710
1701 lines = []
1711 lines = []
1702 _prettyformat(tree, 0, lines)
1712 _prettyformat(tree, 0, lines)
1703 output = '\n'.join((' '*l + s) for l, s in lines)
1713 output = '\n'.join((' '*l + s) for l, s in lines)
1704 return output
1714 return output
1705
1715
1706 # tell hggettext to extract docstrings from these functions:
1716 # tell hggettext to extract docstrings from these functions:
1707 i18nfunctions = symbols.values()
1717 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now