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