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