##// END OF EJS Templates
revset: add 'l' flag to formatspec for args...
Matt Mackall -
r15140:353a1ba9 default
parent child Browse files
Show More
@@ -1,1106 +1,1118 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, node
9 import parser, util, error, discovery, hbisect, node
10 import bookmarks as bookmarksmod
10 import bookmarks as bookmarksmod
11 import match as matchmod
11 import match as matchmod
12 from i18n import _
12 from i18n import _
13
13
14 elements = {
14 elements = {
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 "~": (18, None, ("ancestor", 18)),
16 "~": (18, None, ("ancestor", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
18 "-": (5, ("negate", 19), ("minus", 5)),
18 "-": (5, ("negate", 19), ("minus", 5)),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
20 ("dagrangepost", 17)),
20 ("dagrangepost", 17)),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
22 ("dagrangepost", 17)),
22 ("dagrangepost", 17)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
24 "not": (10, ("not", 10)),
24 "not": (10, ("not", 10)),
25 "!": (10, ("not", 10)),
25 "!": (10, ("not", 10)),
26 "and": (5, None, ("and", 5)),
26 "and": (5, None, ("and", 5)),
27 "&": (5, None, ("and", 5)),
27 "&": (5, None, ("and", 5)),
28 "or": (4, None, ("or", 4)),
28 "or": (4, None, ("or", 4)),
29 "|": (4, None, ("or", 4)),
29 "|": (4, None, ("or", 4)),
30 "+": (4, None, ("or", 4)),
30 "+": (4, None, ("or", 4)),
31 ",": (2, None, ("list", 2)),
31 ",": (2, None, ("list", 2)),
32 ")": (0, None, None),
32 ")": (0, None, None),
33 "symbol": (0, ("symbol",), None),
33 "symbol": (0, ("symbol",), None),
34 "string": (0, ("string",), None),
34 "string": (0, ("string",), None),
35 "end": (0, None, None),
35 "end": (0, None, None),
36 }
36 }
37
37
38 keywords = set(['and', 'or', 'not'])
38 keywords = set(['and', 'or', 'not'])
39
39
40 def tokenize(program):
40 def tokenize(program):
41 pos, l = 0, len(program)
41 pos, l = 0, len(program)
42 while pos < l:
42 while pos < l:
43 c = program[pos]
43 c = program[pos]
44 if c.isspace(): # skip inter-token whitespace
44 if c.isspace(): # skip inter-token whitespace
45 pass
45 pass
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
47 yield ('::', None, pos)
47 yield ('::', None, pos)
48 pos += 1 # skip ahead
48 pos += 1 # skip ahead
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
50 yield ('..', None, pos)
50 yield ('..', None, pos)
51 pos += 1 # skip ahead
51 pos += 1 # skip ahead
52 elif c in "():,-|&+!~^": # handle simple operators
52 elif c in "():,-|&+!~^": # handle simple operators
53 yield (c, None, pos)
53 yield (c, None, pos)
54 elif (c in '"\'' or c == 'r' and
54 elif (c in '"\'' or c == 'r' and
55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
56 if c == 'r':
56 if c == 'r':
57 pos += 1
57 pos += 1
58 c = program[pos]
58 c = program[pos]
59 decode = lambda x: x
59 decode = lambda x: x
60 else:
60 else:
61 decode = lambda x: x.decode('string-escape')
61 decode = lambda x: x.decode('string-escape')
62 pos += 1
62 pos += 1
63 s = pos
63 s = pos
64 while pos < l: # find closing quote
64 while pos < l: # find closing quote
65 d = program[pos]
65 d = program[pos]
66 if d == '\\': # skip over escaped characters
66 if d == '\\': # skip over escaped characters
67 pos += 2
67 pos += 2
68 continue
68 continue
69 if d == c:
69 if d == c:
70 yield ('string', decode(program[s:pos]), s)
70 yield ('string', decode(program[s:pos]), s)
71 break
71 break
72 pos += 1
72 pos += 1
73 else:
73 else:
74 raise error.ParseError(_("unterminated string"), s)
74 raise error.ParseError(_("unterminated string"), s)
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
76 s = pos
76 s = pos
77 pos += 1
77 pos += 1
78 while pos < l: # find end of symbol
78 while pos < l: # find end of symbol
79 d = program[pos]
79 d = program[pos]
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
81 break
81 break
82 if d == '.' and program[pos - 1] == '.': # special case for ..
82 if d == '.' and program[pos - 1] == '.': # special case for ..
83 pos -= 1
83 pos -= 1
84 break
84 break
85 pos += 1
85 pos += 1
86 sym = program[s:pos]
86 sym = program[s:pos]
87 if sym in keywords: # operator keywords
87 if sym in keywords: # operator keywords
88 yield (sym, None, s)
88 yield (sym, None, s)
89 else:
89 else:
90 yield ('symbol', sym, s)
90 yield ('symbol', sym, s)
91 pos -= 1
91 pos -= 1
92 else:
92 else:
93 raise error.ParseError(_("syntax error"), pos)
93 raise error.ParseError(_("syntax error"), pos)
94 pos += 1
94 pos += 1
95 yield ('end', None, pos)
95 yield ('end', None, pos)
96
96
97 # helpers
97 # helpers
98
98
99 def getstring(x, err):
99 def getstring(x, err):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
101 return x[1]
101 return x[1]
102 raise error.ParseError(err)
102 raise error.ParseError(err)
103
103
104 def getlist(x):
104 def getlist(x):
105 if not x:
105 if not x:
106 return []
106 return []
107 if x[0] == 'list':
107 if x[0] == 'list':
108 return getlist(x[1]) + [x[2]]
108 return getlist(x[1]) + [x[2]]
109 return [x]
109 return [x]
110
110
111 def getargs(x, min, max, err):
111 def getargs(x, min, max, err):
112 l = getlist(x)
112 l = getlist(x)
113 if len(l) < min or len(l) > max:
113 if len(l) < min or len(l) > max:
114 raise error.ParseError(err)
114 raise error.ParseError(err)
115 return l
115 return l
116
116
117 def getset(repo, subset, x):
117 def getset(repo, subset, x):
118 if not x:
118 if not x:
119 raise error.ParseError(_("missing argument"))
119 raise error.ParseError(_("missing argument"))
120 return methods[x[0]](repo, subset, *x[1:])
120 return methods[x[0]](repo, subset, *x[1:])
121
121
122 # operator methods
122 # operator methods
123
123
124 def stringset(repo, subset, x):
124 def stringset(repo, subset, x):
125 x = repo[x].rev()
125 x = repo[x].rev()
126 if x == -1 and len(subset) == len(repo):
126 if x == -1 and len(subset) == len(repo):
127 return [-1]
127 return [-1]
128 if len(subset) == len(repo) or x in subset:
128 if len(subset) == len(repo) or x in subset:
129 return [x]
129 return [x]
130 return []
130 return []
131
131
132 def symbolset(repo, subset, x):
132 def symbolset(repo, subset, x):
133 if x in symbols:
133 if x in symbols:
134 raise error.ParseError(_("can't use %s here") % x)
134 raise error.ParseError(_("can't use %s here") % x)
135 return stringset(repo, subset, x)
135 return stringset(repo, subset, x)
136
136
137 def rangeset(repo, subset, x, y):
137 def rangeset(repo, subset, x, y):
138 m = getset(repo, subset, x)
138 m = getset(repo, subset, x)
139 if not m:
139 if not m:
140 m = getset(repo, range(len(repo)), x)
140 m = getset(repo, range(len(repo)), x)
141
141
142 n = getset(repo, subset, y)
142 n = getset(repo, subset, y)
143 if not n:
143 if not n:
144 n = getset(repo, range(len(repo)), y)
144 n = getset(repo, range(len(repo)), y)
145
145
146 if not m or not n:
146 if not m or not n:
147 return []
147 return []
148 m, n = m[0], n[-1]
148 m, n = m[0], n[-1]
149
149
150 if m < n:
150 if m < n:
151 r = range(m, n + 1)
151 r = range(m, n + 1)
152 else:
152 else:
153 r = range(m, n - 1, -1)
153 r = range(m, n - 1, -1)
154 s = set(subset)
154 s = set(subset)
155 return [x for x in r if x in s]
155 return [x for x in r if x in s]
156
156
157 def andset(repo, subset, x, y):
157 def andset(repo, subset, x, y):
158 return getset(repo, getset(repo, subset, x), y)
158 return getset(repo, getset(repo, subset, x), y)
159
159
160 def orset(repo, subset, x, y):
160 def orset(repo, subset, x, y):
161 xl = getset(repo, subset, x)
161 xl = getset(repo, subset, x)
162 s = set(xl)
162 s = set(xl)
163 yl = getset(repo, [r for r in subset if r not in s], y)
163 yl = getset(repo, [r for r in subset if r not in s], y)
164 return xl + yl
164 return xl + yl
165
165
166 def notset(repo, subset, x):
166 def notset(repo, subset, x):
167 s = set(getset(repo, subset, x))
167 s = set(getset(repo, subset, x))
168 return [r for r in subset if r not in s]
168 return [r for r in subset if r not in s]
169
169
170 def listset(repo, subset, a, b):
170 def listset(repo, subset, a, b):
171 raise error.ParseError(_("can't use a list in this context"))
171 raise error.ParseError(_("can't use a list in this context"))
172
172
173 def func(repo, subset, a, b):
173 def func(repo, subset, a, b):
174 if a[0] == 'symbol' and a[1] in symbols:
174 if a[0] == 'symbol' and a[1] in symbols:
175 return symbols[a[1]](repo, subset, b)
175 return symbols[a[1]](repo, subset, b)
176 raise error.ParseError(_("not a function: %s") % a[1])
176 raise error.ParseError(_("not a function: %s") % a[1])
177
177
178 # functions
178 # functions
179
179
180 def adds(repo, subset, x):
180 def adds(repo, subset, x):
181 """``adds(pattern)``
181 """``adds(pattern)``
182 Changesets that add a file matching pattern.
182 Changesets that add a file matching pattern.
183 """
183 """
184 # i18n: "adds" is a keyword
184 # i18n: "adds" is a keyword
185 pat = getstring(x, _("adds requires a pattern"))
185 pat = getstring(x, _("adds requires a pattern"))
186 return checkstatus(repo, subset, pat, 1)
186 return checkstatus(repo, subset, pat, 1)
187
187
188 def ancestor(repo, subset, x):
188 def ancestor(repo, subset, x):
189 """``ancestor(single, single)``
189 """``ancestor(single, single)``
190 Greatest common ancestor of the two changesets.
190 Greatest common ancestor of the two changesets.
191 """
191 """
192 # i18n: "ancestor" is a keyword
192 # i18n: "ancestor" is a keyword
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
194 r = range(len(repo))
194 r = range(len(repo))
195 a = getset(repo, r, l[0])
195 a = getset(repo, r, l[0])
196 b = getset(repo, r, l[1])
196 b = getset(repo, r, l[1])
197 if len(a) != 1 or len(b) != 1:
197 if len(a) != 1 or len(b) != 1:
198 # i18n: "ancestor" is a keyword
198 # i18n: "ancestor" is a keyword
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
201
201
202 return [r for r in an if r in subset]
202 return [r for r in an if r in subset]
203
203
204 def ancestors(repo, subset, x):
204 def ancestors(repo, subset, x):
205 """``ancestors(set)``
205 """``ancestors(set)``
206 Changesets that are ancestors of a changeset in set.
206 Changesets that are ancestors of a changeset in set.
207 """
207 """
208 args = getset(repo, range(len(repo)), x)
208 args = getset(repo, range(len(repo)), x)
209 if not args:
209 if not args:
210 return []
210 return []
211 s = set(repo.changelog.ancestors(*args)) | set(args)
211 s = set(repo.changelog.ancestors(*args)) | set(args)
212 return [r for r in subset if r in s]
212 return [r for r in subset if r in s]
213
213
214 def ancestorspec(repo, subset, x, n):
214 def ancestorspec(repo, subset, x, n):
215 """``set~n``
215 """``set~n``
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
217 """
217 """
218 try:
218 try:
219 n = int(n[1])
219 n = int(n[1])
220 except (TypeError, ValueError):
220 except (TypeError, ValueError):
221 raise error.ParseError(_("~ expects a number"))
221 raise error.ParseError(_("~ expects a number"))
222 ps = set()
222 ps = set()
223 cl = repo.changelog
223 cl = repo.changelog
224 for r in getset(repo, subset, x):
224 for r in getset(repo, subset, x):
225 for i in range(n):
225 for i in range(n):
226 r = cl.parentrevs(r)[0]
226 r = cl.parentrevs(r)[0]
227 ps.add(r)
227 ps.add(r)
228 return [r for r in subset if r in ps]
228 return [r for r in subset if r in ps]
229
229
230 def author(repo, subset, x):
230 def author(repo, subset, x):
231 """``author(string)``
231 """``author(string)``
232 Alias for ``user(string)``.
232 Alias for ``user(string)``.
233 """
233 """
234 # i18n: "author" is a keyword
234 # i18n: "author" is a keyword
235 n = getstring(x, _("author requires a string")).lower()
235 n = getstring(x, _("author requires a string")).lower()
236 return [r for r in subset if n in repo[r].user().lower()]
236 return [r for r in subset if n in repo[r].user().lower()]
237
237
238 def bisect(repo, subset, x):
238 def bisect(repo, subset, x):
239 """``bisect(string)``
239 """``bisect(string)``
240 Changesets marked in the specified bisect status (``good``, ``bad``,
240 Changesets marked in the specified bisect status (``good``, ``bad``,
241 ``skip``), or any of the meta-status:
241 ``skip``), or any of the meta-status:
242
242
243 - ``range`` : all csets taking part in the bisection
243 - ``range`` : all csets taking part in the bisection
244 - ``pruned`` : good|bad|skip, excluding out-of-range csets
244 - ``pruned`` : good|bad|skip, excluding out-of-range csets
245 - ``untested`` : csets whose fate is yet unknown
245 - ``untested`` : csets whose fate is yet unknown
246 """
246 """
247 status = getstring(x, _("bisect requires a string")).lower()
247 status = getstring(x, _("bisect requires a string")).lower()
248 return [r for r in subset if r in hbisect.get(repo, status)]
248 return [r for r in subset if r in hbisect.get(repo, status)]
249
249
250 # Backward-compatibility
250 # Backward-compatibility
251 # - no help entry so that we do not advertise it any more
251 # - no help entry so that we do not advertise it any more
252 def bisected(repo, subset, x):
252 def bisected(repo, subset, x):
253 return bisect(repo, subset, x)
253 return bisect(repo, subset, x)
254
254
255 def bookmark(repo, subset, x):
255 def bookmark(repo, subset, x):
256 """``bookmark([name])``
256 """``bookmark([name])``
257 The named bookmark or all bookmarks.
257 The named bookmark or all bookmarks.
258 """
258 """
259 # i18n: "bookmark" is a keyword
259 # i18n: "bookmark" is a keyword
260 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
260 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
261 if args:
261 if args:
262 bm = getstring(args[0],
262 bm = getstring(args[0],
263 # i18n: "bookmark" is a keyword
263 # i18n: "bookmark" is a keyword
264 _('the argument to bookmark must be a string'))
264 _('the argument to bookmark must be a string'))
265 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
265 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
266 if not bmrev:
266 if not bmrev:
267 raise util.Abort(_("bookmark '%s' does not exist") % bm)
267 raise util.Abort(_("bookmark '%s' does not exist") % bm)
268 bmrev = repo[bmrev].rev()
268 bmrev = repo[bmrev].rev()
269 return [r for r in subset if r == bmrev]
269 return [r for r in subset if r == bmrev]
270 bms = set([repo[r].rev()
270 bms = set([repo[r].rev()
271 for r in bookmarksmod.listbookmarks(repo).values()])
271 for r in bookmarksmod.listbookmarks(repo).values()])
272 return [r for r in subset if r in bms]
272 return [r for r in subset if r in bms]
273
273
274 def branch(repo, subset, x):
274 def branch(repo, subset, x):
275 """``branch(string or set)``
275 """``branch(string or set)``
276 All changesets belonging to the given branch or the branches of the given
276 All changesets belonging to the given branch or the branches of the given
277 changesets.
277 changesets.
278 """
278 """
279 try:
279 try:
280 b = getstring(x, '')
280 b = getstring(x, '')
281 if b in repo.branchmap():
281 if b in repo.branchmap():
282 return [r for r in subset if repo[r].branch() == b]
282 return [r for r in subset if repo[r].branch() == b]
283 except error.ParseError:
283 except error.ParseError:
284 # not a string, but another revspec, e.g. tip()
284 # not a string, but another revspec, e.g. tip()
285 pass
285 pass
286
286
287 s = getset(repo, range(len(repo)), x)
287 s = getset(repo, range(len(repo)), x)
288 b = set()
288 b = set()
289 for r in s:
289 for r in s:
290 b.add(repo[r].branch())
290 b.add(repo[r].branch())
291 s = set(s)
291 s = set(s)
292 return [r for r in subset if r in s or repo[r].branch() in b]
292 return [r for r in subset if r in s or repo[r].branch() in b]
293
293
294 def checkstatus(repo, subset, pat, field):
294 def checkstatus(repo, subset, pat, field):
295 m = matchmod.match(repo.root, repo.getcwd(), [pat])
295 m = matchmod.match(repo.root, repo.getcwd(), [pat])
296 s = []
296 s = []
297 fast = (m.files() == [pat])
297 fast = (m.files() == [pat])
298 for r in subset:
298 for r in subset:
299 c = repo[r]
299 c = repo[r]
300 if fast:
300 if fast:
301 if pat not in c.files():
301 if pat not in c.files():
302 continue
302 continue
303 else:
303 else:
304 for f in c.files():
304 for f in c.files():
305 if m(f):
305 if m(f):
306 break
306 break
307 else:
307 else:
308 continue
308 continue
309 files = repo.status(c.p1().node(), c.node())[field]
309 files = repo.status(c.p1().node(), c.node())[field]
310 if fast:
310 if fast:
311 if pat in files:
311 if pat in files:
312 s.append(r)
312 s.append(r)
313 else:
313 else:
314 for f in files:
314 for f in files:
315 if m(f):
315 if m(f):
316 s.append(r)
316 s.append(r)
317 break
317 break
318 return s
318 return s
319
319
320 def children(repo, subset, x):
320 def children(repo, subset, x):
321 """``children(set)``
321 """``children(set)``
322 Child changesets of changesets in set.
322 Child changesets of changesets in set.
323 """
323 """
324 cs = set()
324 cs = set()
325 cl = repo.changelog
325 cl = repo.changelog
326 s = set(getset(repo, range(len(repo)), x))
326 s = set(getset(repo, range(len(repo)), x))
327 for r in xrange(0, len(repo)):
327 for r in xrange(0, len(repo)):
328 for p in cl.parentrevs(r):
328 for p in cl.parentrevs(r):
329 if p in s:
329 if p in s:
330 cs.add(r)
330 cs.add(r)
331 return [r for r in subset if r in cs]
331 return [r for r in subset if r in cs]
332
332
333 def closed(repo, subset, x):
333 def closed(repo, subset, x):
334 """``closed()``
334 """``closed()``
335 Changeset is closed.
335 Changeset is closed.
336 """
336 """
337 # i18n: "closed" is a keyword
337 # i18n: "closed" is a keyword
338 getargs(x, 0, 0, _("closed takes no arguments"))
338 getargs(x, 0, 0, _("closed takes no arguments"))
339 return [r for r in subset if repo[r].extra().get('close')]
339 return [r for r in subset if repo[r].extra().get('close')]
340
340
341 def contains(repo, subset, x):
341 def contains(repo, subset, x):
342 """``contains(pattern)``
342 """``contains(pattern)``
343 Revision contains a file matching pattern. See :hg:`help patterns`
343 Revision contains a file matching pattern. See :hg:`help patterns`
344 for information about file patterns.
344 for information about file patterns.
345 """
345 """
346 # i18n: "contains" is a keyword
346 # i18n: "contains" is a keyword
347 pat = getstring(x, _("contains requires a pattern"))
347 pat = getstring(x, _("contains requires a pattern"))
348 m = matchmod.match(repo.root, repo.getcwd(), [pat])
348 m = matchmod.match(repo.root, repo.getcwd(), [pat])
349 s = []
349 s = []
350 if m.files() == [pat]:
350 if m.files() == [pat]:
351 for r in subset:
351 for r in subset:
352 if pat in repo[r]:
352 if pat in repo[r]:
353 s.append(r)
353 s.append(r)
354 else:
354 else:
355 for r in subset:
355 for r in subset:
356 for f in repo[r].manifest():
356 for f in repo[r].manifest():
357 if m(f):
357 if m(f):
358 s.append(r)
358 s.append(r)
359 break
359 break
360 return s
360 return s
361
361
362 def date(repo, subset, x):
362 def date(repo, subset, x):
363 """``date(interval)``
363 """``date(interval)``
364 Changesets within the interval, see :hg:`help dates`.
364 Changesets within the interval, see :hg:`help dates`.
365 """
365 """
366 # i18n: "date" is a keyword
366 # i18n: "date" is a keyword
367 ds = getstring(x, _("date requires a string"))
367 ds = getstring(x, _("date requires a string"))
368 dm = util.matchdate(ds)
368 dm = util.matchdate(ds)
369 return [r for r in subset if dm(repo[r].date()[0])]
369 return [r for r in subset if dm(repo[r].date()[0])]
370
370
371 def desc(repo, subset, x):
371 def desc(repo, subset, x):
372 """``desc(string)``
372 """``desc(string)``
373 Search commit message for string. The match is case-insensitive.
373 Search commit message for string. The match is case-insensitive.
374 """
374 """
375 # i18n: "desc" is a keyword
375 # i18n: "desc" is a keyword
376 ds = getstring(x, _("desc requires a string")).lower()
376 ds = getstring(x, _("desc requires a string")).lower()
377 l = []
377 l = []
378 for r in subset:
378 for r in subset:
379 c = repo[r]
379 c = repo[r]
380 if ds in c.description().lower():
380 if ds in c.description().lower():
381 l.append(r)
381 l.append(r)
382 return l
382 return l
383
383
384 def descendants(repo, subset, x):
384 def descendants(repo, subset, x):
385 """``descendants(set)``
385 """``descendants(set)``
386 Changesets which are descendants of changesets in set.
386 Changesets which are descendants of changesets in set.
387 """
387 """
388 args = getset(repo, range(len(repo)), x)
388 args = getset(repo, range(len(repo)), x)
389 if not args:
389 if not args:
390 return []
390 return []
391 s = set(repo.changelog.descendants(*args)) | set(args)
391 s = set(repo.changelog.descendants(*args)) | set(args)
392 return [r for r in subset if r in s]
392 return [r for r in subset if r in s]
393
393
394 def filelog(repo, subset, x):
394 def filelog(repo, subset, x):
395 """``filelog(pattern)``
395 """``filelog(pattern)``
396 Changesets connected to the specified filelog.
396 Changesets connected to the specified filelog.
397 """
397 """
398
398
399 pat = getstring(x, _("filelog requires a pattern"))
399 pat = getstring(x, _("filelog requires a pattern"))
400 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
400 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
401 s = set()
401 s = set()
402
402
403 if not m.anypats():
403 if not m.anypats():
404 for f in m.files():
404 for f in m.files():
405 fl = repo.file(f)
405 fl = repo.file(f)
406 for fr in fl:
406 for fr in fl:
407 s.add(fl.linkrev(fr))
407 s.add(fl.linkrev(fr))
408 else:
408 else:
409 for f in repo[None]:
409 for f in repo[None]:
410 if m(f):
410 if m(f):
411 fl = repo.file(f)
411 fl = repo.file(f)
412 for fr in fl:
412 for fr in fl:
413 s.add(fl.linkrev(fr))
413 s.add(fl.linkrev(fr))
414
414
415 return [r for r in subset if r in s]
415 return [r for r in subset if r in s]
416
416
417 def first(repo, subset, x):
417 def first(repo, subset, x):
418 """``first(set, [n])``
418 """``first(set, [n])``
419 An alias for limit().
419 An alias for limit().
420 """
420 """
421 return limit(repo, subset, x)
421 return limit(repo, subset, x)
422
422
423 def follow(repo, subset, x):
423 def follow(repo, subset, x):
424 """``follow([file])``
424 """``follow([file])``
425 An alias for ``::.`` (ancestors of the working copy's first parent).
425 An alias for ``::.`` (ancestors of the working copy's first parent).
426 If a filename is specified, the history of the given file is followed,
426 If a filename is specified, the history of the given file is followed,
427 including copies.
427 including copies.
428 """
428 """
429 # i18n: "follow" is a keyword
429 # i18n: "follow" is a keyword
430 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
430 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
431 p = repo['.'].rev()
431 p = repo['.'].rev()
432 if l:
432 if l:
433 x = getstring(l[0], _("follow expected a filename"))
433 x = getstring(l[0], _("follow expected a filename"))
434 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
434 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
435 else:
435 else:
436 s = set(repo.changelog.ancestors(p))
436 s = set(repo.changelog.ancestors(p))
437
437
438 s |= set([p])
438 s |= set([p])
439 return [r for r in subset if r in s]
439 return [r for r in subset if r in s]
440
440
441 def followfile(repo, subset, x):
441 def followfile(repo, subset, x):
442 """``follow()``
442 """``follow()``
443 An alias for ``::.`` (ancestors of the working copy's first parent).
443 An alias for ``::.`` (ancestors of the working copy's first parent).
444 """
444 """
445 # i18n: "follow" is a keyword
445 # i18n: "follow" is a keyword
446 getargs(x, 0, 0, _("follow takes no arguments"))
446 getargs(x, 0, 0, _("follow takes no arguments"))
447 p = repo['.'].rev()
447 p = repo['.'].rev()
448 s = set(repo.changelog.ancestors(p)) | set([p])
448 s = set(repo.changelog.ancestors(p)) | set([p])
449 return [r for r in subset if r in s]
449 return [r for r in subset if r in s]
450
450
451 def getall(repo, subset, x):
451 def getall(repo, subset, x):
452 """``all()``
452 """``all()``
453 All changesets, the same as ``0:tip``.
453 All changesets, the same as ``0:tip``.
454 """
454 """
455 # i18n: "all" is a keyword
455 # i18n: "all" is a keyword
456 getargs(x, 0, 0, _("all takes no arguments"))
456 getargs(x, 0, 0, _("all takes no arguments"))
457 return subset
457 return subset
458
458
459 def grep(repo, subset, x):
459 def grep(repo, subset, x):
460 """``grep(regex)``
460 """``grep(regex)``
461 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
461 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
462 to ensure special escape characters are handled correctly. Unlike
462 to ensure special escape characters are handled correctly. Unlike
463 ``keyword(string)``, the match is case-sensitive.
463 ``keyword(string)``, the match is case-sensitive.
464 """
464 """
465 try:
465 try:
466 # i18n: "grep" is a keyword
466 # i18n: "grep" is a keyword
467 gr = re.compile(getstring(x, _("grep requires a string")))
467 gr = re.compile(getstring(x, _("grep requires a string")))
468 except re.error, e:
468 except re.error, e:
469 raise error.ParseError(_('invalid match pattern: %s') % e)
469 raise error.ParseError(_('invalid match pattern: %s') % e)
470 l = []
470 l = []
471 for r in subset:
471 for r in subset:
472 c = repo[r]
472 c = repo[r]
473 for e in c.files() + [c.user(), c.description()]:
473 for e in c.files() + [c.user(), c.description()]:
474 if gr.search(e):
474 if gr.search(e):
475 l.append(r)
475 l.append(r)
476 break
476 break
477 return l
477 return l
478
478
479 def hasfile(repo, subset, x):
479 def hasfile(repo, subset, x):
480 """``file(pattern)``
480 """``file(pattern)``
481 Changesets affecting files matched by pattern.
481 Changesets affecting files matched by pattern.
482 """
482 """
483 # i18n: "file" is a keyword
483 # i18n: "file" is a keyword
484 pat = getstring(x, _("file requires a pattern"))
484 pat = getstring(x, _("file requires a pattern"))
485 m = matchmod.match(repo.root, repo.getcwd(), [pat])
485 m = matchmod.match(repo.root, repo.getcwd(), [pat])
486 s = []
486 s = []
487 for r in subset:
487 for r in subset:
488 for f in repo[r].files():
488 for f in repo[r].files():
489 if m(f):
489 if m(f):
490 s.append(r)
490 s.append(r)
491 break
491 break
492 return s
492 return s
493
493
494 def head(repo, subset, x):
494 def head(repo, subset, x):
495 """``head()``
495 """``head()``
496 Changeset is a named branch head.
496 Changeset is a named branch head.
497 """
497 """
498 # i18n: "head" is a keyword
498 # i18n: "head" is a keyword
499 getargs(x, 0, 0, _("head takes no arguments"))
499 getargs(x, 0, 0, _("head takes no arguments"))
500 hs = set()
500 hs = set()
501 for b, ls in repo.branchmap().iteritems():
501 for b, ls in repo.branchmap().iteritems():
502 hs.update(repo[h].rev() for h in ls)
502 hs.update(repo[h].rev() for h in ls)
503 return [r for r in subset if r in hs]
503 return [r for r in subset if r in hs]
504
504
505 def heads(repo, subset, x):
505 def heads(repo, subset, x):
506 """``heads(set)``
506 """``heads(set)``
507 Members of set with no children in set.
507 Members of set with no children in set.
508 """
508 """
509 s = getset(repo, subset, x)
509 s = getset(repo, subset, x)
510 ps = set(parents(repo, subset, x))
510 ps = set(parents(repo, subset, x))
511 return [r for r in s if r not in ps]
511 return [r for r in s if r not in ps]
512
512
513 def keyword(repo, subset, x):
513 def keyword(repo, subset, x):
514 """``keyword(string)``
514 """``keyword(string)``
515 Search commit message, user name, and names of changed files for
515 Search commit message, user name, and names of changed files for
516 string. The match is case-insensitive.
516 string. The match is case-insensitive.
517 """
517 """
518 # i18n: "keyword" is a keyword
518 # i18n: "keyword" is a keyword
519 kw = getstring(x, _("keyword requires a string")).lower()
519 kw = getstring(x, _("keyword requires a string")).lower()
520 l = []
520 l = []
521 for r in subset:
521 for r in subset:
522 c = repo[r]
522 c = repo[r]
523 t = " ".join(c.files() + [c.user(), c.description()])
523 t = " ".join(c.files() + [c.user(), c.description()])
524 if kw in t.lower():
524 if kw in t.lower():
525 l.append(r)
525 l.append(r)
526 return l
526 return l
527
527
528 def limit(repo, subset, x):
528 def limit(repo, subset, x):
529 """``limit(set, [n])``
529 """``limit(set, [n])``
530 First n members of set, defaulting to 1.
530 First n members of set, defaulting to 1.
531 """
531 """
532 # i18n: "limit" is a keyword
532 # i18n: "limit" is a keyword
533 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
533 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
534 try:
534 try:
535 lim = 1
535 lim = 1
536 if len(l) == 2:
536 if len(l) == 2:
537 # i18n: "limit" is a keyword
537 # i18n: "limit" is a keyword
538 lim = int(getstring(l[1], _("limit requires a number")))
538 lim = int(getstring(l[1], _("limit requires a number")))
539 except (TypeError, ValueError):
539 except (TypeError, ValueError):
540 # i18n: "limit" is a keyword
540 # i18n: "limit" is a keyword
541 raise error.ParseError(_("limit expects a number"))
541 raise error.ParseError(_("limit expects a number"))
542 ss = set(subset)
542 ss = set(subset)
543 os = getset(repo, range(len(repo)), l[0])[:lim]
543 os = getset(repo, range(len(repo)), l[0])[:lim]
544 return [r for r in os if r in ss]
544 return [r for r in os if r in ss]
545
545
546 def last(repo, subset, x):
546 def last(repo, subset, x):
547 """``last(set, [n])``
547 """``last(set, [n])``
548 Last n members of set, defaulting to 1.
548 Last n members of set, defaulting to 1.
549 """
549 """
550 # i18n: "last" is a keyword
550 # i18n: "last" is a keyword
551 l = getargs(x, 1, 2, _("last requires one or two arguments"))
551 l = getargs(x, 1, 2, _("last requires one or two arguments"))
552 try:
552 try:
553 lim = 1
553 lim = 1
554 if len(l) == 2:
554 if len(l) == 2:
555 # i18n: "last" is a keyword
555 # i18n: "last" is a keyword
556 lim = int(getstring(l[1], _("last requires a number")))
556 lim = int(getstring(l[1], _("last requires a number")))
557 except (TypeError, ValueError):
557 except (TypeError, ValueError):
558 # i18n: "last" is a keyword
558 # i18n: "last" is a keyword
559 raise error.ParseError(_("last expects a number"))
559 raise error.ParseError(_("last expects a number"))
560 ss = set(subset)
560 ss = set(subset)
561 os = getset(repo, range(len(repo)), l[0])[-lim:]
561 os = getset(repo, range(len(repo)), l[0])[-lim:]
562 return [r for r in os if r in ss]
562 return [r for r in os if r in ss]
563
563
564 def maxrev(repo, subset, x):
564 def maxrev(repo, subset, x):
565 """``max(set)``
565 """``max(set)``
566 Changeset with highest revision number in set.
566 Changeset with highest revision number in set.
567 """
567 """
568 os = getset(repo, range(len(repo)), x)
568 os = getset(repo, range(len(repo)), x)
569 if os:
569 if os:
570 m = max(os)
570 m = max(os)
571 if m in subset:
571 if m in subset:
572 return [m]
572 return [m]
573 return []
573 return []
574
574
575 def merge(repo, subset, x):
575 def merge(repo, subset, x):
576 """``merge()``
576 """``merge()``
577 Changeset is a merge changeset.
577 Changeset is a merge changeset.
578 """
578 """
579 # i18n: "merge" is a keyword
579 # i18n: "merge" is a keyword
580 getargs(x, 0, 0, _("merge takes no arguments"))
580 getargs(x, 0, 0, _("merge takes no arguments"))
581 cl = repo.changelog
581 cl = repo.changelog
582 return [r for r in subset if cl.parentrevs(r)[1] != -1]
582 return [r for r in subset if cl.parentrevs(r)[1] != -1]
583
583
584 def minrev(repo, subset, x):
584 def minrev(repo, subset, x):
585 """``min(set)``
585 """``min(set)``
586 Changeset with lowest revision number in set.
586 Changeset with lowest revision number in set.
587 """
587 """
588 os = getset(repo, range(len(repo)), x)
588 os = getset(repo, range(len(repo)), x)
589 if os:
589 if os:
590 m = min(os)
590 m = min(os)
591 if m in subset:
591 if m in subset:
592 return [m]
592 return [m]
593 return []
593 return []
594
594
595 def modifies(repo, subset, x):
595 def modifies(repo, subset, x):
596 """``modifies(pattern)``
596 """``modifies(pattern)``
597 Changesets modifying files matched by pattern.
597 Changesets modifying files matched by pattern.
598 """
598 """
599 # i18n: "modifies" is a keyword
599 # i18n: "modifies" is a keyword
600 pat = getstring(x, _("modifies requires a pattern"))
600 pat = getstring(x, _("modifies requires a pattern"))
601 return checkstatus(repo, subset, pat, 0)
601 return checkstatus(repo, subset, pat, 0)
602
602
603 def node(repo, subset, x):
603 def node(repo, subset, x):
604 """``id(string)``
604 """``id(string)``
605 Revision non-ambiguously specified by the given hex string prefix.
605 Revision non-ambiguously specified by the given hex string prefix.
606 """
606 """
607 # i18n: "id" is a keyword
607 # i18n: "id" is a keyword
608 l = getargs(x, 1, 1, _("id requires one argument"))
608 l = getargs(x, 1, 1, _("id requires one argument"))
609 # i18n: "id" is a keyword
609 # i18n: "id" is a keyword
610 n = getstring(l[0], _("id requires a string"))
610 n = getstring(l[0], _("id requires a string"))
611 if len(n) == 40:
611 if len(n) == 40:
612 rn = repo[n].rev()
612 rn = repo[n].rev()
613 else:
613 else:
614 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
614 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
615 return [r for r in subset if r == rn]
615 return [r for r in subset if r == rn]
616
616
617 def outgoing(repo, subset, x):
617 def outgoing(repo, subset, x):
618 """``outgoing([path])``
618 """``outgoing([path])``
619 Changesets not found in the specified destination repository, or the
619 Changesets not found in the specified destination repository, or the
620 default push location.
620 default push location.
621 """
621 """
622 import hg # avoid start-up nasties
622 import hg # avoid start-up nasties
623 # i18n: "outgoing" is a keyword
623 # i18n: "outgoing" is a keyword
624 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
624 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
625 # i18n: "outgoing" is a keyword
625 # i18n: "outgoing" is a keyword
626 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
626 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
627 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
627 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
628 dest, branches = hg.parseurl(dest)
628 dest, branches = hg.parseurl(dest)
629 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
629 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
630 if revs:
630 if revs:
631 revs = [repo.lookup(rev) for rev in revs]
631 revs = [repo.lookup(rev) for rev in revs]
632 other = hg.peer(repo, {}, dest)
632 other = hg.peer(repo, {}, dest)
633 repo.ui.pushbuffer()
633 repo.ui.pushbuffer()
634 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
634 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
635 repo.ui.popbuffer()
635 repo.ui.popbuffer()
636 cl = repo.changelog
636 cl = repo.changelog
637 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
637 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
638 return [r for r in subset if r in o]
638 return [r for r in subset if r in o]
639
639
640 def p1(repo, subset, x):
640 def p1(repo, subset, x):
641 """``p1([set])``
641 """``p1([set])``
642 First parent of changesets in set, or the working directory.
642 First parent of changesets in set, or the working directory.
643 """
643 """
644 if x is None:
644 if x is None:
645 p = repo[x].p1().rev()
645 p = repo[x].p1().rev()
646 return [r for r in subset if r == p]
646 return [r for r in subset if r == p]
647
647
648 ps = set()
648 ps = set()
649 cl = repo.changelog
649 cl = repo.changelog
650 for r in getset(repo, range(len(repo)), x):
650 for r in getset(repo, range(len(repo)), x):
651 ps.add(cl.parentrevs(r)[0])
651 ps.add(cl.parentrevs(r)[0])
652 return [r for r in subset if r in ps]
652 return [r for r in subset if r in ps]
653
653
654 def p2(repo, subset, x):
654 def p2(repo, subset, x):
655 """``p2([set])``
655 """``p2([set])``
656 Second parent of changesets in set, or the working directory.
656 Second parent of changesets in set, or the working directory.
657 """
657 """
658 if x is None:
658 if x is None:
659 ps = repo[x].parents()
659 ps = repo[x].parents()
660 try:
660 try:
661 p = ps[1].rev()
661 p = ps[1].rev()
662 return [r for r in subset if r == p]
662 return [r for r in subset if r == p]
663 except IndexError:
663 except IndexError:
664 return []
664 return []
665
665
666 ps = set()
666 ps = set()
667 cl = repo.changelog
667 cl = repo.changelog
668 for r in getset(repo, range(len(repo)), x):
668 for r in getset(repo, range(len(repo)), x):
669 ps.add(cl.parentrevs(r)[1])
669 ps.add(cl.parentrevs(r)[1])
670 return [r for r in subset if r in ps]
670 return [r for r in subset if r in ps]
671
671
672 def parents(repo, subset, x):
672 def parents(repo, subset, x):
673 """``parents([set])``
673 """``parents([set])``
674 The set of all parents for all changesets in set, or the working directory.
674 The set of all parents for all changesets in set, or the working directory.
675 """
675 """
676 if x is None:
676 if x is None:
677 ps = tuple(p.rev() for p in repo[x].parents())
677 ps = tuple(p.rev() for p in repo[x].parents())
678 return [r for r in subset if r in ps]
678 return [r for r in subset if r in ps]
679
679
680 ps = set()
680 ps = set()
681 cl = repo.changelog
681 cl = repo.changelog
682 for r in getset(repo, range(len(repo)), x):
682 for r in getset(repo, range(len(repo)), x):
683 ps.update(cl.parentrevs(r))
683 ps.update(cl.parentrevs(r))
684 return [r for r in subset if r in ps]
684 return [r for r in subset if r in ps]
685
685
686 def parentspec(repo, subset, x, n):
686 def parentspec(repo, subset, x, n):
687 """``set^0``
687 """``set^0``
688 The set.
688 The set.
689 ``set^1`` (or ``set^``), ``set^2``
689 ``set^1`` (or ``set^``), ``set^2``
690 First or second parent, respectively, of all changesets in set.
690 First or second parent, respectively, of all changesets in set.
691 """
691 """
692 try:
692 try:
693 n = int(n[1])
693 n = int(n[1])
694 if n not in (0, 1, 2):
694 if n not in (0, 1, 2):
695 raise ValueError
695 raise ValueError
696 except (TypeError, ValueError):
696 except (TypeError, ValueError):
697 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
697 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
698 ps = set()
698 ps = set()
699 cl = repo.changelog
699 cl = repo.changelog
700 for r in getset(repo, subset, x):
700 for r in getset(repo, subset, x):
701 if n == 0:
701 if n == 0:
702 ps.add(r)
702 ps.add(r)
703 elif n == 1:
703 elif n == 1:
704 ps.add(cl.parentrevs(r)[0])
704 ps.add(cl.parentrevs(r)[0])
705 elif n == 2:
705 elif n == 2:
706 parents = cl.parentrevs(r)
706 parents = cl.parentrevs(r)
707 if len(parents) > 1:
707 if len(parents) > 1:
708 ps.add(parents[1])
708 ps.add(parents[1])
709 return [r for r in subset if r in ps]
709 return [r for r in subset if r in ps]
710
710
711 def present(repo, subset, x):
711 def present(repo, subset, x):
712 """``present(set)``
712 """``present(set)``
713 An empty set, if any revision in set isn't found; otherwise,
713 An empty set, if any revision in set isn't found; otherwise,
714 all revisions in set.
714 all revisions in set.
715 """
715 """
716 try:
716 try:
717 return getset(repo, subset, x)
717 return getset(repo, subset, x)
718 except error.RepoLookupError:
718 except error.RepoLookupError:
719 return []
719 return []
720
720
721 def removes(repo, subset, x):
721 def removes(repo, subset, x):
722 """``removes(pattern)``
722 """``removes(pattern)``
723 Changesets which remove files matching pattern.
723 Changesets which remove files matching pattern.
724 """
724 """
725 # i18n: "removes" is a keyword
725 # i18n: "removes" is a keyword
726 pat = getstring(x, _("removes requires a pattern"))
726 pat = getstring(x, _("removes requires a pattern"))
727 return checkstatus(repo, subset, pat, 2)
727 return checkstatus(repo, subset, pat, 2)
728
728
729 def rev(repo, subset, x):
729 def rev(repo, subset, x):
730 """``rev(number)``
730 """``rev(number)``
731 Revision with the given numeric identifier.
731 Revision with the given numeric identifier.
732 """
732 """
733 # i18n: "rev" is a keyword
733 # i18n: "rev" is a keyword
734 l = getargs(x, 1, 1, _("rev requires one argument"))
734 l = getargs(x, 1, 1, _("rev requires one argument"))
735 try:
735 try:
736 # i18n: "rev" is a keyword
736 # i18n: "rev" is a keyword
737 l = int(getstring(l[0], _("rev requires a number")))
737 l = int(getstring(l[0], _("rev requires a number")))
738 except (TypeError, ValueError):
738 except (TypeError, ValueError):
739 # i18n: "rev" is a keyword
739 # i18n: "rev" is a keyword
740 raise error.ParseError(_("rev expects a number"))
740 raise error.ParseError(_("rev expects a number"))
741 return [r for r in subset if r == l]
741 return [r for r in subset if r == l]
742
742
743 def reverse(repo, subset, x):
743 def reverse(repo, subset, x):
744 """``reverse(set)``
744 """``reverse(set)``
745 Reverse order of set.
745 Reverse order of set.
746 """
746 """
747 l = getset(repo, subset, x)
747 l = getset(repo, subset, x)
748 l.reverse()
748 l.reverse()
749 return l
749 return l
750
750
751 def roots(repo, subset, x):
751 def roots(repo, subset, x):
752 """``roots(set)``
752 """``roots(set)``
753 Changesets with no parent changeset in set.
753 Changesets with no parent changeset in set.
754 """
754 """
755 s = getset(repo, subset, x)
755 s = getset(repo, subset, x)
756 cs = set(children(repo, subset, x))
756 cs = set(children(repo, subset, x))
757 return [r for r in s if r not in cs]
757 return [r for r in s if r not in cs]
758
758
759 def sort(repo, subset, x):
759 def sort(repo, subset, x):
760 """``sort(set[, [-]key...])``
760 """``sort(set[, [-]key...])``
761 Sort set by keys. The default sort order is ascending, specify a key
761 Sort set by keys. The default sort order is ascending, specify a key
762 as ``-key`` to sort in descending order.
762 as ``-key`` to sort in descending order.
763
763
764 The keys can be:
764 The keys can be:
765
765
766 - ``rev`` for the revision number,
766 - ``rev`` for the revision number,
767 - ``branch`` for the branch name,
767 - ``branch`` for the branch name,
768 - ``desc`` for the commit message (description),
768 - ``desc`` for the commit message (description),
769 - ``user`` for user name (``author`` can be used as an alias),
769 - ``user`` for user name (``author`` can be used as an alias),
770 - ``date`` for the commit date
770 - ``date`` for the commit date
771 """
771 """
772 # i18n: "sort" is a keyword
772 # i18n: "sort" is a keyword
773 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
773 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
774 keys = "rev"
774 keys = "rev"
775 if len(l) == 2:
775 if len(l) == 2:
776 keys = getstring(l[1], _("sort spec must be a string"))
776 keys = getstring(l[1], _("sort spec must be a string"))
777
777
778 s = l[0]
778 s = l[0]
779 keys = keys.split()
779 keys = keys.split()
780 l = []
780 l = []
781 def invert(s):
781 def invert(s):
782 return "".join(chr(255 - ord(c)) for c in s)
782 return "".join(chr(255 - ord(c)) for c in s)
783 for r in getset(repo, subset, s):
783 for r in getset(repo, subset, s):
784 c = repo[r]
784 c = repo[r]
785 e = []
785 e = []
786 for k in keys:
786 for k in keys:
787 if k == 'rev':
787 if k == 'rev':
788 e.append(r)
788 e.append(r)
789 elif k == '-rev':
789 elif k == '-rev':
790 e.append(-r)
790 e.append(-r)
791 elif k == 'branch':
791 elif k == 'branch':
792 e.append(c.branch())
792 e.append(c.branch())
793 elif k == '-branch':
793 elif k == '-branch':
794 e.append(invert(c.branch()))
794 e.append(invert(c.branch()))
795 elif k == 'desc':
795 elif k == 'desc':
796 e.append(c.description())
796 e.append(c.description())
797 elif k == '-desc':
797 elif k == '-desc':
798 e.append(invert(c.description()))
798 e.append(invert(c.description()))
799 elif k in 'user author':
799 elif k in 'user author':
800 e.append(c.user())
800 e.append(c.user())
801 elif k in '-user -author':
801 elif k in '-user -author':
802 e.append(invert(c.user()))
802 e.append(invert(c.user()))
803 elif k == 'date':
803 elif k == 'date':
804 e.append(c.date()[0])
804 e.append(c.date()[0])
805 elif k == '-date':
805 elif k == '-date':
806 e.append(-c.date()[0])
806 e.append(-c.date()[0])
807 else:
807 else:
808 raise error.ParseError(_("unknown sort key %r") % k)
808 raise error.ParseError(_("unknown sort key %r") % k)
809 e.append(r)
809 e.append(r)
810 l.append(e)
810 l.append(e)
811 l.sort()
811 l.sort()
812 return [e[-1] for e in l]
812 return [e[-1] for e in l]
813
813
814 def tag(repo, subset, x):
814 def tag(repo, subset, x):
815 """``tag([name])``
815 """``tag([name])``
816 The specified tag by name, or all tagged revisions if no name is given.
816 The specified tag by name, or all tagged revisions if no name is given.
817 """
817 """
818 # i18n: "tag" is a keyword
818 # i18n: "tag" is a keyword
819 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
819 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
820 cl = repo.changelog
820 cl = repo.changelog
821 if args:
821 if args:
822 tn = getstring(args[0],
822 tn = getstring(args[0],
823 # i18n: "tag" is a keyword
823 # i18n: "tag" is a keyword
824 _('the argument to tag must be a string'))
824 _('the argument to tag must be a string'))
825 if not repo.tags().get(tn, None):
825 if not repo.tags().get(tn, None):
826 raise util.Abort(_("tag '%s' does not exist") % tn)
826 raise util.Abort(_("tag '%s' does not exist") % tn)
827 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
827 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
828 else:
828 else:
829 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
829 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
830 return [r for r in subset if r in s]
830 return [r for r in subset if r in s]
831
831
832 def tagged(repo, subset, x):
832 def tagged(repo, subset, x):
833 return tag(repo, subset, x)
833 return tag(repo, subset, x)
834
834
835 def user(repo, subset, x):
835 def user(repo, subset, x):
836 """``user(string)``
836 """``user(string)``
837 User name contains string. The match is case-insensitive.
837 User name contains string. The match is case-insensitive.
838 """
838 """
839 return author(repo, subset, x)
839 return author(repo, subset, x)
840
840
841 symbols = {
841 symbols = {
842 "adds": adds,
842 "adds": adds,
843 "all": getall,
843 "all": getall,
844 "ancestor": ancestor,
844 "ancestor": ancestor,
845 "ancestors": ancestors,
845 "ancestors": ancestors,
846 "author": author,
846 "author": author,
847 "bisect": bisect,
847 "bisect": bisect,
848 "bisected": bisected,
848 "bisected": bisected,
849 "bookmark": bookmark,
849 "bookmark": bookmark,
850 "branch": branch,
850 "branch": branch,
851 "children": children,
851 "children": children,
852 "closed": closed,
852 "closed": closed,
853 "contains": contains,
853 "contains": contains,
854 "date": date,
854 "date": date,
855 "desc": desc,
855 "desc": desc,
856 "descendants": descendants,
856 "descendants": descendants,
857 "file": hasfile,
857 "file": hasfile,
858 "filelog": filelog,
858 "filelog": filelog,
859 "first": first,
859 "first": first,
860 "follow": follow,
860 "follow": follow,
861 "grep": grep,
861 "grep": grep,
862 "head": head,
862 "head": head,
863 "heads": heads,
863 "heads": heads,
864 "id": node,
864 "id": node,
865 "keyword": keyword,
865 "keyword": keyword,
866 "last": last,
866 "last": last,
867 "limit": limit,
867 "limit": limit,
868 "max": maxrev,
868 "max": maxrev,
869 "merge": merge,
869 "merge": merge,
870 "min": minrev,
870 "min": minrev,
871 "modifies": modifies,
871 "modifies": modifies,
872 "outgoing": outgoing,
872 "outgoing": outgoing,
873 "p1": p1,
873 "p1": p1,
874 "p2": p2,
874 "p2": p2,
875 "parents": parents,
875 "parents": parents,
876 "present": present,
876 "present": present,
877 "removes": removes,
877 "removes": removes,
878 "rev": rev,
878 "rev": rev,
879 "reverse": reverse,
879 "reverse": reverse,
880 "roots": roots,
880 "roots": roots,
881 "sort": sort,
881 "sort": sort,
882 "tag": tag,
882 "tag": tag,
883 "tagged": tagged,
883 "tagged": tagged,
884 "user": user,
884 "user": user,
885 }
885 }
886
886
887 methods = {
887 methods = {
888 "range": rangeset,
888 "range": rangeset,
889 "string": stringset,
889 "string": stringset,
890 "symbol": symbolset,
890 "symbol": symbolset,
891 "and": andset,
891 "and": andset,
892 "or": orset,
892 "or": orset,
893 "not": notset,
893 "not": notset,
894 "list": listset,
894 "list": listset,
895 "func": func,
895 "func": func,
896 "ancestor": ancestorspec,
896 "ancestor": ancestorspec,
897 "parent": parentspec,
897 "parent": parentspec,
898 "parentpost": p1,
898 "parentpost": p1,
899 }
899 }
900
900
901 def optimize(x, small):
901 def optimize(x, small):
902 if x is None:
902 if x is None:
903 return 0, x
903 return 0, x
904
904
905 smallbonus = 1
905 smallbonus = 1
906 if small:
906 if small:
907 smallbonus = .5
907 smallbonus = .5
908
908
909 op = x[0]
909 op = x[0]
910 if op == 'minus':
910 if op == 'minus':
911 return optimize(('and', x[1], ('not', x[2])), small)
911 return optimize(('and', x[1], ('not', x[2])), small)
912 elif op == 'dagrange':
912 elif op == 'dagrange':
913 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
913 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
914 ('func', ('symbol', 'ancestors'), x[2])), small)
914 ('func', ('symbol', 'ancestors'), x[2])), small)
915 elif op == 'dagrangepre':
915 elif op == 'dagrangepre':
916 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
916 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
917 elif op == 'dagrangepost':
917 elif op == 'dagrangepost':
918 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
918 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
919 elif op == 'rangepre':
919 elif op == 'rangepre':
920 return optimize(('range', ('string', '0'), x[1]), small)
920 return optimize(('range', ('string', '0'), x[1]), small)
921 elif op == 'rangepost':
921 elif op == 'rangepost':
922 return optimize(('range', x[1], ('string', 'tip')), small)
922 return optimize(('range', x[1], ('string', 'tip')), small)
923 elif op == 'negate':
923 elif op == 'negate':
924 return optimize(('string',
924 return optimize(('string',
925 '-' + getstring(x[1], _("can't negate that"))), small)
925 '-' + getstring(x[1], _("can't negate that"))), small)
926 elif op in 'string symbol negate':
926 elif op in 'string symbol negate':
927 return smallbonus, x # single revisions are small
927 return smallbonus, x # single revisions are small
928 elif op == 'and' or op == 'dagrange':
928 elif op == 'and' or op == 'dagrange':
929 wa, ta = optimize(x[1], True)
929 wa, ta = optimize(x[1], True)
930 wb, tb = optimize(x[2], True)
930 wb, tb = optimize(x[2], True)
931 w = min(wa, wb)
931 w = min(wa, wb)
932 if wa > wb:
932 if wa > wb:
933 return w, (op, tb, ta)
933 return w, (op, tb, ta)
934 return w, (op, ta, tb)
934 return w, (op, ta, tb)
935 elif op == 'or':
935 elif op == 'or':
936 wa, ta = optimize(x[1], False)
936 wa, ta = optimize(x[1], False)
937 wb, tb = optimize(x[2], False)
937 wb, tb = optimize(x[2], False)
938 if wb < wa:
938 if wb < wa:
939 wb, wa = wa, wb
939 wb, wa = wa, wb
940 return max(wa, wb), (op, ta, tb)
940 return max(wa, wb), (op, ta, tb)
941 elif op == 'not':
941 elif op == 'not':
942 o = optimize(x[1], not small)
942 o = optimize(x[1], not small)
943 return o[0], (op, o[1])
943 return o[0], (op, o[1])
944 elif op == 'parentpost':
944 elif op == 'parentpost':
945 o = optimize(x[1], small)
945 o = optimize(x[1], small)
946 return o[0], (op, o[1])
946 return o[0], (op, o[1])
947 elif op == 'group':
947 elif op == 'group':
948 return optimize(x[1], small)
948 return optimize(x[1], small)
949 elif op in 'range list parent ancestorspec':
949 elif op in 'range list parent ancestorspec':
950 if op == 'parent':
950 if op == 'parent':
951 # x^:y means (x^) : y, not x ^ (:y)
951 # x^:y means (x^) : y, not x ^ (:y)
952 post = ('parentpost', x[1])
952 post = ('parentpost', x[1])
953 if x[2][0] == 'dagrangepre':
953 if x[2][0] == 'dagrangepre':
954 return optimize(('dagrange', post, x[2][1]), small)
954 return optimize(('dagrange', post, x[2][1]), small)
955 elif x[2][0] == 'rangepre':
955 elif x[2][0] == 'rangepre':
956 return optimize(('range', post, x[2][1]), small)
956 return optimize(('range', post, x[2][1]), small)
957
957
958 wa, ta = optimize(x[1], small)
958 wa, ta = optimize(x[1], small)
959 wb, tb = optimize(x[2], small)
959 wb, tb = optimize(x[2], small)
960 return wa + wb, (op, ta, tb)
960 return wa + wb, (op, ta, tb)
961 elif op == 'func':
961 elif op == 'func':
962 f = getstring(x[1], _("not a symbol"))
962 f = getstring(x[1], _("not a symbol"))
963 wa, ta = optimize(x[2], small)
963 wa, ta = optimize(x[2], small)
964 if f in ("author branch closed date desc file grep keyword "
964 if f in ("author branch closed date desc file grep keyword "
965 "outgoing user"):
965 "outgoing user"):
966 w = 10 # slow
966 w = 10 # slow
967 elif f in "modifies adds removes":
967 elif f in "modifies adds removes":
968 w = 30 # slower
968 w = 30 # slower
969 elif f == "contains":
969 elif f == "contains":
970 w = 100 # very slow
970 w = 100 # very slow
971 elif f == "ancestor":
971 elif f == "ancestor":
972 w = 1 * smallbonus
972 w = 1 * smallbonus
973 elif f in "reverse limit first":
973 elif f in "reverse limit first":
974 w = 0
974 w = 0
975 elif f in "sort":
975 elif f in "sort":
976 w = 10 # assume most sorts look at changelog
976 w = 10 # assume most sorts look at changelog
977 else:
977 else:
978 w = 1
978 w = 1
979 return w + wa, (op, x[1], ta)
979 return w + wa, (op, x[1], ta)
980 return 1, x
980 return 1, x
981
981
982 class revsetalias(object):
982 class revsetalias(object):
983 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
983 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
984 args = None
984 args = None
985
985
986 def __init__(self, name, value):
986 def __init__(self, name, value):
987 '''Aliases like:
987 '''Aliases like:
988
988
989 h = heads(default)
989 h = heads(default)
990 b($1) = ancestors($1) - ancestors(default)
990 b($1) = ancestors($1) - ancestors(default)
991 '''
991 '''
992 if isinstance(name, tuple): # parameter substitution
992 if isinstance(name, tuple): # parameter substitution
993 self.tree = name
993 self.tree = name
994 self.replacement = value
994 self.replacement = value
995 else: # alias definition
995 else: # alias definition
996 m = self.funcre.search(name)
996 m = self.funcre.search(name)
997 if m:
997 if m:
998 self.tree = ('func', ('symbol', m.group(1)))
998 self.tree = ('func', ('symbol', m.group(1)))
999 self.args = [x.strip() for x in m.group(2).split(',')]
999 self.args = [x.strip() for x in m.group(2).split(',')]
1000 for arg in self.args:
1000 for arg in self.args:
1001 value = value.replace(arg, repr(arg))
1001 value = value.replace(arg, repr(arg))
1002 else:
1002 else:
1003 self.tree = ('symbol', name)
1003 self.tree = ('symbol', name)
1004
1004
1005 self.replacement, pos = parse(value)
1005 self.replacement, pos = parse(value)
1006 if pos != len(value):
1006 if pos != len(value):
1007 raise error.ParseError(_('invalid token'), pos)
1007 raise error.ParseError(_('invalid token'), pos)
1008
1008
1009 def process(self, tree):
1009 def process(self, tree):
1010 if isinstance(tree, tuple):
1010 if isinstance(tree, tuple):
1011 if self.args is None:
1011 if self.args is None:
1012 if tree == self.tree:
1012 if tree == self.tree:
1013 return self.replacement
1013 return self.replacement
1014 elif tree[:2] == self.tree:
1014 elif tree[:2] == self.tree:
1015 l = getlist(tree[2])
1015 l = getlist(tree[2])
1016 if len(l) != len(self.args):
1016 if len(l) != len(self.args):
1017 raise error.ParseError(
1017 raise error.ParseError(
1018 _('invalid number of arguments: %s') % len(l))
1018 _('invalid number of arguments: %s') % len(l))
1019 result = self.replacement
1019 result = self.replacement
1020 for a, v in zip(self.args, l):
1020 for a, v in zip(self.args, l):
1021 valalias = revsetalias(('string', a), v)
1021 valalias = revsetalias(('string', a), v)
1022 result = valalias.process(result)
1022 result = valalias.process(result)
1023 return result
1023 return result
1024 return tuple(map(self.process, tree))
1024 return tuple(map(self.process, tree))
1025 return tree
1025 return tree
1026
1026
1027 def findaliases(ui, tree):
1027 def findaliases(ui, tree):
1028 for k, v in ui.configitems('revsetalias'):
1028 for k, v in ui.configitems('revsetalias'):
1029 alias = revsetalias(k, v)
1029 alias = revsetalias(k, v)
1030 tree = alias.process(tree)
1030 tree = alias.process(tree)
1031 return tree
1031 return tree
1032
1032
1033 parse = parser.parser(tokenize, elements).parse
1033 parse = parser.parser(tokenize, elements).parse
1034
1034
1035 def match(ui, spec):
1035 def match(ui, spec):
1036 if not spec:
1036 if not spec:
1037 raise error.ParseError(_("empty query"))
1037 raise error.ParseError(_("empty query"))
1038 tree, pos = parse(spec)
1038 tree, pos = parse(spec)
1039 if (pos != len(spec)):
1039 if (pos != len(spec)):
1040 raise error.ParseError(_("invalid token"), pos)
1040 raise error.ParseError(_("invalid token"), pos)
1041 if ui:
1041 if ui:
1042 tree = findaliases(ui, tree)
1042 tree = findaliases(ui, tree)
1043 weight, tree = optimize(tree, True)
1043 weight, tree = optimize(tree, True)
1044 def mfunc(repo, subset):
1044 def mfunc(repo, subset):
1045 return getset(repo, subset, tree)
1045 return getset(repo, subset, tree)
1046 return mfunc
1046 return mfunc
1047
1047
1048 def formatspec(expr, *args):
1048 def formatspec(expr, *args):
1049 '''
1049 '''
1050 This is a convenience function for using revsets internally, and
1050 This is a convenience function for using revsets internally, and
1051 escapes arguments appropriately. Aliases are intentionally ignored
1051 escapes arguments appropriately. Aliases are intentionally ignored
1052 so that intended expression behavior isn't accidentally subverted.
1052 so that intended expression behavior isn't accidentally subverted.
1053
1053
1054 Supported arguments:
1054 Supported arguments:
1055
1055
1056 %d = int(arg), no quoting
1056 %d = int(arg), no quoting
1057 %s = string(arg), escaped and single-quoted
1057 %s = string(arg), escaped and single-quoted
1058 %b = arg.branch(), escaped and single-quoted
1058 %b = arg.branch(), escaped and single-quoted
1059 %n = hex(arg), single-quoted
1059 %n = hex(arg), single-quoted
1060 %% = a literal '%'
1060 %% = a literal '%'
1061
1061
1062 Prefixing the type with 'l' specifies a list of that type.
1063
1062 >>> formatspec('%d:: and not %d::', 10, 20)
1064 >>> formatspec('%d:: and not %d::', 10, 20)
1063 '10:: and not 20::'
1065 '10:: and not 20::'
1064 >>> formatspec('keyword(%s)', 'foo\\xe9')
1066 >>> formatspec('keyword(%s)', 'foo\\xe9')
1065 "keyword('foo\\\\xe9')"
1067 "keyword('foo\\\\xe9')"
1066 >>> b = lambda: 'default'
1068 >>> b = lambda: 'default'
1067 >>> b.branch = b
1069 >>> b.branch = b
1068 >>> formatspec('branch(%b)', b)
1070 >>> formatspec('branch(%b)', b)
1069 "branch('default')"
1071 "branch('default')"
1072 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1073 "root(('a' or 'b' or 'c' or 'd'))"
1070 '''
1074 '''
1071
1075
1072 def quote(s):
1076 def quote(s):
1073 return repr(str(s))
1077 return repr(str(s))
1074
1078
1079 def argtype(c, arg):
1080 if c == 'd':
1081 return str(int(arg))
1082 elif c == 's':
1083 return quote(arg)
1084 elif c == 'n':
1085 return quote(node.hex(arg))
1086 elif c == 'b':
1087 return quote(arg.branch())
1088
1075 ret = ''
1089 ret = ''
1076 pos = 0
1090 pos = 0
1077 arg = 0
1091 arg = 0
1078 while pos < len(expr):
1092 while pos < len(expr):
1079 c = expr[pos]
1093 c = expr[pos]
1080 if c == '%':
1094 if c == '%':
1081 pos += 1
1095 pos += 1
1082 d = expr[pos]
1096 d = expr[pos]
1083 if d == '%':
1097 if d == '%':
1084 ret += d
1098 ret += d
1085 elif d == 'd':
1099 elif d in 'dsnb':
1086 ret += str(int(args[arg]))
1100 ret += argtype(d, args[arg])
1087 arg += 1
1088 elif d == 's':
1089 ret += quote(args[arg])
1090 arg += 1
1101 arg += 1
1091 elif d == 'n':
1102 elif d == 'l':
1092 ret += quote(node.hex(args[arg]))
1103 # a list of some type
1093 arg += 1
1104 pos += 1
1094 elif d == 'b':
1105 d = expr[pos]
1095 ret += quote(args[arg].branch())
1106 lv = ' or '.join(argtype(d, e) for e in args[arg])
1107 ret += '(%s)' % lv
1096 arg += 1
1108 arg += 1
1097 else:
1109 else:
1098 raise util.Abort('unexpected revspec format character %s' % d)
1110 raise util.Abort('unexpected revspec format character %s' % d)
1099 else:
1111 else:
1100 ret += c
1112 ret += c
1101 pos += 1
1113 pos += 1
1102
1114
1103 return ret
1115 return ret
1104
1116
1105 # tell hggettext to extract docstrings from these functions:
1117 # tell hggettext to extract docstrings from these functions:
1106 i18nfunctions = symbols.values()
1118 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now