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