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