##// END OF EJS Templates
revset: fix infinite alias expansion detection...
Patrick Mezard -
r16772:30e46d71 stable
parent child Browse files
Show More
@@ -1,1528 +1,1527 b''
1 # revset.py - revision set queries for mercurial
1 # revset.py - revision set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 import parser, util, error, discovery, hbisect, phases
9 import parser, util, error, discovery, hbisect, phases
10 import node
10 import node
11 import bookmarks as bookmarksmod
11 import bookmarks as bookmarksmod
12 import match as matchmod
12 import match as matchmod
13 from i18n import _
13 from i18n import _
14 import encoding
14 import encoding
15
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 = None
743 rn = None
744 pm = repo.changelog._partialmatch(n)
744 pm = repo.changelog._partialmatch(n)
745 if pm is not None:
745 if pm is not None:
746 rn = repo.changelog.rev(pm)
746 rn = repo.changelog.rev(pm)
747
747
748 return [r for r in subset if r == rn]
748 return [r for r in subset if r == rn]
749
749
750 def outgoing(repo, subset, x):
750 def outgoing(repo, subset, x):
751 """``outgoing([path])``
751 """``outgoing([path])``
752 Changesets not found in the specified destination repository, or the
752 Changesets not found in the specified destination repository, or the
753 default push location.
753 default push location.
754 """
754 """
755 import hg # avoid start-up nasties
755 import hg # avoid start-up nasties
756 # i18n: "outgoing" is a keyword
756 # i18n: "outgoing" is a keyword
757 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
757 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
758 # i18n: "outgoing" is a keyword
758 # i18n: "outgoing" is a keyword
759 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
759 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
760 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
760 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
761 dest, branches = hg.parseurl(dest)
761 dest, branches = hg.parseurl(dest)
762 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
762 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
763 if revs:
763 if revs:
764 revs = [repo.lookup(rev) for rev in revs]
764 revs = [repo.lookup(rev) for rev in revs]
765 other = hg.peer(repo, {}, dest)
765 other = hg.peer(repo, {}, dest)
766 repo.ui.pushbuffer()
766 repo.ui.pushbuffer()
767 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
767 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
768 repo.ui.popbuffer()
768 repo.ui.popbuffer()
769 cl = repo.changelog
769 cl = repo.changelog
770 o = set([cl.rev(r) for r in outgoing.missing])
770 o = set([cl.rev(r) for r in outgoing.missing])
771 return [r for r in subset if r in o]
771 return [r for r in subset if r in o]
772
772
773 def p1(repo, subset, x):
773 def p1(repo, subset, x):
774 """``p1([set])``
774 """``p1([set])``
775 First parent of changesets in set, or the working directory.
775 First parent of changesets in set, or the working directory.
776 """
776 """
777 if x is None:
777 if x is None:
778 p = repo[x].p1().rev()
778 p = repo[x].p1().rev()
779 return [r for r in subset if r == p]
779 return [r for r in subset if r == p]
780
780
781 ps = set()
781 ps = set()
782 cl = repo.changelog
782 cl = repo.changelog
783 for r in getset(repo, range(len(repo)), x):
783 for r in getset(repo, range(len(repo)), x):
784 ps.add(cl.parentrevs(r)[0])
784 ps.add(cl.parentrevs(r)[0])
785 return [r for r in subset if r in ps]
785 return [r for r in subset if r in ps]
786
786
787 def p2(repo, subset, x):
787 def p2(repo, subset, x):
788 """``p2([set])``
788 """``p2([set])``
789 Second parent of changesets in set, or the working directory.
789 Second parent of changesets in set, or the working directory.
790 """
790 """
791 if x is None:
791 if x is None:
792 ps = repo[x].parents()
792 ps = repo[x].parents()
793 try:
793 try:
794 p = ps[1].rev()
794 p = ps[1].rev()
795 return [r for r in subset if r == p]
795 return [r for r in subset if r == p]
796 except IndexError:
796 except IndexError:
797 return []
797 return []
798
798
799 ps = set()
799 ps = set()
800 cl = repo.changelog
800 cl = repo.changelog
801 for r in getset(repo, range(len(repo)), x):
801 for r in getset(repo, range(len(repo)), x):
802 ps.add(cl.parentrevs(r)[1])
802 ps.add(cl.parentrevs(r)[1])
803 return [r for r in subset if r in ps]
803 return [r for r in subset if r in ps]
804
804
805 def parents(repo, subset, x):
805 def parents(repo, subset, x):
806 """``parents([set])``
806 """``parents([set])``
807 The set of all parents for all changesets in set, or the working directory.
807 The set of all parents for all changesets in set, or the working directory.
808 """
808 """
809 if x is None:
809 if x is None:
810 ps = tuple(p.rev() for p in repo[x].parents())
810 ps = tuple(p.rev() for p in repo[x].parents())
811 return [r for r in subset if r in ps]
811 return [r for r in subset if r in ps]
812
812
813 ps = set()
813 ps = set()
814 cl = repo.changelog
814 cl = repo.changelog
815 for r in getset(repo, range(len(repo)), x):
815 for r in getset(repo, range(len(repo)), x):
816 ps.update(cl.parentrevs(r))
816 ps.update(cl.parentrevs(r))
817 return [r for r in subset if r in ps]
817 return [r for r in subset if r in ps]
818
818
819 def parentspec(repo, subset, x, n):
819 def parentspec(repo, subset, x, n):
820 """``set^0``
820 """``set^0``
821 The set.
821 The set.
822 ``set^1`` (or ``set^``), ``set^2``
822 ``set^1`` (or ``set^``), ``set^2``
823 First or second parent, respectively, of all changesets in set.
823 First or second parent, respectively, of all changesets in set.
824 """
824 """
825 try:
825 try:
826 n = int(n[1])
826 n = int(n[1])
827 if n not in (0, 1, 2):
827 if n not in (0, 1, 2):
828 raise ValueError
828 raise ValueError
829 except (TypeError, ValueError):
829 except (TypeError, ValueError):
830 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
830 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
831 ps = set()
831 ps = set()
832 cl = repo.changelog
832 cl = repo.changelog
833 for r in getset(repo, subset, x):
833 for r in getset(repo, subset, x):
834 if n == 0:
834 if n == 0:
835 ps.add(r)
835 ps.add(r)
836 elif n == 1:
836 elif n == 1:
837 ps.add(cl.parentrevs(r)[0])
837 ps.add(cl.parentrevs(r)[0])
838 elif n == 2:
838 elif n == 2:
839 parents = cl.parentrevs(r)
839 parents = cl.parentrevs(r)
840 if len(parents) > 1:
840 if len(parents) > 1:
841 ps.add(parents[1])
841 ps.add(parents[1])
842 return [r for r in subset if r in ps]
842 return [r for r in subset if r in ps]
843
843
844 def present(repo, subset, x):
844 def present(repo, subset, x):
845 """``present(set)``
845 """``present(set)``
846 An empty set, if any revision in set isn't found; otherwise,
846 An empty set, if any revision in set isn't found; otherwise,
847 all revisions in set.
847 all revisions in set.
848
848
849 If any of specified revisions is not present in the local repository,
849 If any of specified revisions is not present in the local repository,
850 the query is normally aborted. But this predicate allows the query
850 the query is normally aborted. But this predicate allows the query
851 to continue even in such cases.
851 to continue even in such cases.
852 """
852 """
853 try:
853 try:
854 return getset(repo, subset, x)
854 return getset(repo, subset, x)
855 except error.RepoLookupError:
855 except error.RepoLookupError:
856 return []
856 return []
857
857
858 def public(repo, subset, x):
858 def public(repo, subset, x):
859 """``public()``
859 """``public()``
860 Changeset in public phase."""
860 Changeset in public phase."""
861 getargs(x, 0, 0, _("public takes no arguments"))
861 getargs(x, 0, 0, _("public takes no arguments"))
862 return [r for r in subset if repo._phaserev[r] == phases.public]
862 return [r for r in subset if repo._phaserev[r] == phases.public]
863
863
864 def remote(repo, subset, x):
864 def remote(repo, subset, x):
865 """``remote([id [,path]])``
865 """``remote([id [,path]])``
866 Local revision that corresponds to the given identifier in a
866 Local revision that corresponds to the given identifier in a
867 remote repository, if present. Here, the '.' identifier is a
867 remote repository, if present. Here, the '.' identifier is a
868 synonym for the current local branch.
868 synonym for the current local branch.
869 """
869 """
870
870
871 import hg # avoid start-up nasties
871 import hg # avoid start-up nasties
872 # i18n: "remote" is a keyword
872 # i18n: "remote" is a keyword
873 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
873 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
874
874
875 q = '.'
875 q = '.'
876 if len(l) > 0:
876 if len(l) > 0:
877 # i18n: "remote" is a keyword
877 # i18n: "remote" is a keyword
878 q = getstring(l[0], _("remote requires a string id"))
878 q = getstring(l[0], _("remote requires a string id"))
879 if q == '.':
879 if q == '.':
880 q = repo['.'].branch()
880 q = repo['.'].branch()
881
881
882 dest = ''
882 dest = ''
883 if len(l) > 1:
883 if len(l) > 1:
884 # i18n: "remote" is a keyword
884 # i18n: "remote" is a keyword
885 dest = getstring(l[1], _("remote requires a repository path"))
885 dest = getstring(l[1], _("remote requires a repository path"))
886 dest = repo.ui.expandpath(dest or 'default')
886 dest = repo.ui.expandpath(dest or 'default')
887 dest, branches = hg.parseurl(dest)
887 dest, branches = hg.parseurl(dest)
888 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
888 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
889 if revs:
889 if revs:
890 revs = [repo.lookup(rev) for rev in revs]
890 revs = [repo.lookup(rev) for rev in revs]
891 other = hg.peer(repo, {}, dest)
891 other = hg.peer(repo, {}, dest)
892 n = other.lookup(q)
892 n = other.lookup(q)
893 if n in repo:
893 if n in repo:
894 r = repo[n].rev()
894 r = repo[n].rev()
895 if r in subset:
895 if r in subset:
896 return [r]
896 return [r]
897 return []
897 return []
898
898
899 def removes(repo, subset, x):
899 def removes(repo, subset, x):
900 """``removes(pattern)``
900 """``removes(pattern)``
901 Changesets which remove files matching pattern.
901 Changesets which remove files matching pattern.
902 """
902 """
903 # i18n: "removes" is a keyword
903 # i18n: "removes" is a keyword
904 pat = getstring(x, _("removes requires a pattern"))
904 pat = getstring(x, _("removes requires a pattern"))
905 return checkstatus(repo, subset, pat, 2)
905 return checkstatus(repo, subset, pat, 2)
906
906
907 def rev(repo, subset, x):
907 def rev(repo, subset, x):
908 """``rev(number)``
908 """``rev(number)``
909 Revision with the given numeric identifier.
909 Revision with the given numeric identifier.
910 """
910 """
911 # i18n: "rev" is a keyword
911 # i18n: "rev" is a keyword
912 l = getargs(x, 1, 1, _("rev requires one argument"))
912 l = getargs(x, 1, 1, _("rev requires one argument"))
913 try:
913 try:
914 # i18n: "rev" is a keyword
914 # i18n: "rev" is a keyword
915 l = int(getstring(l[0], _("rev requires a number")))
915 l = int(getstring(l[0], _("rev requires a number")))
916 except (TypeError, ValueError):
916 except (TypeError, ValueError):
917 # i18n: "rev" is a keyword
917 # i18n: "rev" is a keyword
918 raise error.ParseError(_("rev expects a number"))
918 raise error.ParseError(_("rev expects a number"))
919 return [r for r in subset if r == l]
919 return [r for r in subset if r == l]
920
920
921 def matching(repo, subset, x):
921 def matching(repo, subset, x):
922 """``matching(revision [, field])``
922 """``matching(revision [, field])``
923 Changesets in which a given set of fields match the set of fields in the
923 Changesets in which a given set of fields match the set of fields in the
924 selected revision or set.
924 selected revision or set.
925
925
926 To match more than one field pass the list of fields to match separated
926 To match more than one field pass the list of fields to match separated
927 by spaces (e.g. ``author description``).
927 by spaces (e.g. ``author description``).
928
928
929 Valid fields are most regular revision fields and some special fields.
929 Valid fields are most regular revision fields and some special fields.
930
930
931 Regular revision fields are ``description``, ``author``, ``branch``,
931 Regular revision fields are ``description``, ``author``, ``branch``,
932 ``date``, ``files``, ``phase``, ``parents``, ``substate`` and ``user``.
932 ``date``, ``files``, ``phase``, ``parents``, ``substate`` and ``user``.
933 Note that ``author`` and ``user`` are synonyms.
933 Note that ``author`` and ``user`` are synonyms.
934
934
935 Special fields are ``summary`` and ``metadata``:
935 Special fields are ``summary`` and ``metadata``:
936 ``summary`` matches the first line of the description.
936 ``summary`` matches the first line of the description.
937 ``metadata`` is equivalent to matching ``description user date``
937 ``metadata`` is equivalent to matching ``description user date``
938 (i.e. it matches the main metadata fields).
938 (i.e. it matches the main metadata fields).
939
939
940 ``metadata`` is the default field which is used when no fields are
940 ``metadata`` is the default field which is used when no fields are
941 specified. You can match more than one field at a time.
941 specified. You can match more than one field at a time.
942 """
942 """
943 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
943 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
944
944
945 revs = getset(repo, xrange(len(repo)), l[0])
945 revs = getset(repo, xrange(len(repo)), l[0])
946
946
947 fieldlist = ['metadata']
947 fieldlist = ['metadata']
948 if len(l) > 1:
948 if len(l) > 1:
949 fieldlist = getstring(l[1],
949 fieldlist = getstring(l[1],
950 _("matching requires a string "
950 _("matching requires a string "
951 "as its second argument")).split()
951 "as its second argument")).split()
952
952
953 # Make sure that there are no repeated fields, and expand the
953 # Make sure that there are no repeated fields, and expand the
954 # 'special' 'metadata' field type
954 # 'special' 'metadata' field type
955 fields = []
955 fields = []
956 for field in fieldlist:
956 for field in fieldlist:
957 if field == 'metadata':
957 if field == 'metadata':
958 fields += ['user', 'description', 'date']
958 fields += ['user', 'description', 'date']
959 else:
959 else:
960 if field == 'author':
960 if field == 'author':
961 field = 'user'
961 field = 'user'
962 fields.append(field)
962 fields.append(field)
963 fields = set(fields)
963 fields = set(fields)
964 if 'summary' in fields and 'description' in fields:
964 if 'summary' in fields and 'description' in fields:
965 # If a revision matches its description it also matches its summary
965 # If a revision matches its description it also matches its summary
966 fields.discard('summary')
966 fields.discard('summary')
967
967
968 # We may want to match more than one field
968 # We may want to match more than one field
969 # Not all fields take the same amount of time to be matched
969 # Not all fields take the same amount of time to be matched
970 # Sort the selected fields in order of increasing matching cost
970 # Sort the selected fields in order of increasing matching cost
971 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
971 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
972 'files', 'description', 'substate']
972 'files', 'description', 'substate']
973 def fieldkeyfunc(f):
973 def fieldkeyfunc(f):
974 try:
974 try:
975 return fieldorder.index(f)
975 return fieldorder.index(f)
976 except ValueError:
976 except ValueError:
977 # assume an unknown field is very costly
977 # assume an unknown field is very costly
978 return len(fieldorder)
978 return len(fieldorder)
979 fields = list(fields)
979 fields = list(fields)
980 fields.sort(key=fieldkeyfunc)
980 fields.sort(key=fieldkeyfunc)
981
981
982 # Each field will be matched with its own "getfield" function
982 # Each field will be matched with its own "getfield" function
983 # which will be added to the getfieldfuncs array of functions
983 # which will be added to the getfieldfuncs array of functions
984 getfieldfuncs = []
984 getfieldfuncs = []
985 _funcs = {
985 _funcs = {
986 'user': lambda r: repo[r].user(),
986 'user': lambda r: repo[r].user(),
987 'branch': lambda r: repo[r].branch(),
987 'branch': lambda r: repo[r].branch(),
988 'date': lambda r: repo[r].date(),
988 'date': lambda r: repo[r].date(),
989 'description': lambda r: repo[r].description(),
989 'description': lambda r: repo[r].description(),
990 'files': lambda r: repo[r].files(),
990 'files': lambda r: repo[r].files(),
991 'parents': lambda r: repo[r].parents(),
991 'parents': lambda r: repo[r].parents(),
992 'phase': lambda r: repo[r].phase(),
992 'phase': lambda r: repo[r].phase(),
993 'substate': lambda r: repo[r].substate,
993 'substate': lambda r: repo[r].substate,
994 'summary': lambda r: repo[r].description().splitlines()[0],
994 'summary': lambda r: repo[r].description().splitlines()[0],
995 }
995 }
996 for info in fields:
996 for info in fields:
997 getfield = _funcs.get(info, None)
997 getfield = _funcs.get(info, None)
998 if getfield is None:
998 if getfield is None:
999 raise error.ParseError(
999 raise error.ParseError(
1000 _("unexpected field name passed to matching: %s") % info)
1000 _("unexpected field name passed to matching: %s") % info)
1001 getfieldfuncs.append(getfield)
1001 getfieldfuncs.append(getfield)
1002 # convert the getfield array of functions into a "getinfo" function
1002 # convert the getfield array of functions into a "getinfo" function
1003 # which returns an array of field values (or a single value if there
1003 # which returns an array of field values (or a single value if there
1004 # is only one field to match)
1004 # is only one field to match)
1005 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1005 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1006
1006
1007 matches = set()
1007 matches = set()
1008 for rev in revs:
1008 for rev in revs:
1009 target = getinfo(rev)
1009 target = getinfo(rev)
1010 for r in subset:
1010 for r in subset:
1011 match = True
1011 match = True
1012 for n, f in enumerate(getfieldfuncs):
1012 for n, f in enumerate(getfieldfuncs):
1013 if target[n] != f(r):
1013 if target[n] != f(r):
1014 match = False
1014 match = False
1015 break
1015 break
1016 if match:
1016 if match:
1017 matches.add(r)
1017 matches.add(r)
1018 return [r for r in subset if r in matches]
1018 return [r for r in subset if r in matches]
1019
1019
1020 def reverse(repo, subset, x):
1020 def reverse(repo, subset, x):
1021 """``reverse(set)``
1021 """``reverse(set)``
1022 Reverse order of set.
1022 Reverse order of set.
1023 """
1023 """
1024 l = getset(repo, subset, x)
1024 l = getset(repo, subset, x)
1025 l.reverse()
1025 l.reverse()
1026 return l
1026 return l
1027
1027
1028 def roots(repo, subset, x):
1028 def roots(repo, subset, x):
1029 """``roots(set)``
1029 """``roots(set)``
1030 Changesets in set with no parent changeset in set.
1030 Changesets in set with no parent changeset in set.
1031 """
1031 """
1032 s = set(getset(repo, xrange(len(repo)), x))
1032 s = set(getset(repo, xrange(len(repo)), x))
1033 subset = [r for r in subset if r in s]
1033 subset = [r for r in subset if r in s]
1034 cs = _children(repo, subset, s)
1034 cs = _children(repo, subset, s)
1035 return [r for r in subset if r not in cs]
1035 return [r for r in subset if r not in cs]
1036
1036
1037 def secret(repo, subset, x):
1037 def secret(repo, subset, x):
1038 """``secret()``
1038 """``secret()``
1039 Changeset in secret phase."""
1039 Changeset in secret phase."""
1040 getargs(x, 0, 0, _("secret takes no arguments"))
1040 getargs(x, 0, 0, _("secret takes no arguments"))
1041 return [r for r in subset if repo._phaserev[r] == phases.secret]
1041 return [r for r in subset if repo._phaserev[r] == phases.secret]
1042
1042
1043 def sort(repo, subset, x):
1043 def sort(repo, subset, x):
1044 """``sort(set[, [-]key...])``
1044 """``sort(set[, [-]key...])``
1045 Sort set by keys. The default sort order is ascending, specify a key
1045 Sort set by keys. The default sort order is ascending, specify a key
1046 as ``-key`` to sort in descending order.
1046 as ``-key`` to sort in descending order.
1047
1047
1048 The keys can be:
1048 The keys can be:
1049
1049
1050 - ``rev`` for the revision number,
1050 - ``rev`` for the revision number,
1051 - ``branch`` for the branch name,
1051 - ``branch`` for the branch name,
1052 - ``desc`` for the commit message (description),
1052 - ``desc`` for the commit message (description),
1053 - ``user`` for user name (``author`` can be used as an alias),
1053 - ``user`` for user name (``author`` can be used as an alias),
1054 - ``date`` for the commit date
1054 - ``date`` for the commit date
1055 """
1055 """
1056 # i18n: "sort" is a keyword
1056 # i18n: "sort" is a keyword
1057 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1057 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1058 keys = "rev"
1058 keys = "rev"
1059 if len(l) == 2:
1059 if len(l) == 2:
1060 keys = getstring(l[1], _("sort spec must be a string"))
1060 keys = getstring(l[1], _("sort spec must be a string"))
1061
1061
1062 s = l[0]
1062 s = l[0]
1063 keys = keys.split()
1063 keys = keys.split()
1064 l = []
1064 l = []
1065 def invert(s):
1065 def invert(s):
1066 return "".join(chr(255 - ord(c)) for c in s)
1066 return "".join(chr(255 - ord(c)) for c in s)
1067 for r in getset(repo, subset, s):
1067 for r in getset(repo, subset, s):
1068 c = repo[r]
1068 c = repo[r]
1069 e = []
1069 e = []
1070 for k in keys:
1070 for k in keys:
1071 if k == 'rev':
1071 if k == 'rev':
1072 e.append(r)
1072 e.append(r)
1073 elif k == '-rev':
1073 elif k == '-rev':
1074 e.append(-r)
1074 e.append(-r)
1075 elif k == 'branch':
1075 elif k == 'branch':
1076 e.append(c.branch())
1076 e.append(c.branch())
1077 elif k == '-branch':
1077 elif k == '-branch':
1078 e.append(invert(c.branch()))
1078 e.append(invert(c.branch()))
1079 elif k == 'desc':
1079 elif k == 'desc':
1080 e.append(c.description())
1080 e.append(c.description())
1081 elif k == '-desc':
1081 elif k == '-desc':
1082 e.append(invert(c.description()))
1082 e.append(invert(c.description()))
1083 elif k in 'user author':
1083 elif k in 'user author':
1084 e.append(c.user())
1084 e.append(c.user())
1085 elif k in '-user -author':
1085 elif k in '-user -author':
1086 e.append(invert(c.user()))
1086 e.append(invert(c.user()))
1087 elif k == 'date':
1087 elif k == 'date':
1088 e.append(c.date()[0])
1088 e.append(c.date()[0])
1089 elif k == '-date':
1089 elif k == '-date':
1090 e.append(-c.date()[0])
1090 e.append(-c.date()[0])
1091 else:
1091 else:
1092 raise error.ParseError(_("unknown sort key %r") % k)
1092 raise error.ParseError(_("unknown sort key %r") % k)
1093 e.append(r)
1093 e.append(r)
1094 l.append(e)
1094 l.append(e)
1095 l.sort()
1095 l.sort()
1096 return [e[-1] for e in l]
1096 return [e[-1] for e in l]
1097
1097
1098 def tag(repo, subset, x):
1098 def tag(repo, subset, x):
1099 """``tag([name])``
1099 """``tag([name])``
1100 The specified tag by name, or all tagged revisions if no name is given.
1100 The specified tag by name, or all tagged revisions if no name is given.
1101 """
1101 """
1102 # i18n: "tag" is a keyword
1102 # i18n: "tag" is a keyword
1103 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1103 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1104 cl = repo.changelog
1104 cl = repo.changelog
1105 if args:
1105 if args:
1106 tn = getstring(args[0],
1106 tn = getstring(args[0],
1107 # i18n: "tag" is a keyword
1107 # i18n: "tag" is a keyword
1108 _('the argument to tag must be a string'))
1108 _('the argument to tag must be a string'))
1109 if not repo.tags().get(tn, None):
1109 if not repo.tags().get(tn, None):
1110 raise util.Abort(_("tag '%s' does not exist") % tn)
1110 raise util.Abort(_("tag '%s' does not exist") % tn)
1111 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
1111 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
1112 else:
1112 else:
1113 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1113 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1114 return [r for r in subset if r in s]
1114 return [r for r in subset if r in s]
1115
1115
1116 def tagged(repo, subset, x):
1116 def tagged(repo, subset, x):
1117 return tag(repo, subset, x)
1117 return tag(repo, subset, x)
1118
1118
1119 def user(repo, subset, x):
1119 def user(repo, subset, x):
1120 """``user(string)``
1120 """``user(string)``
1121 User name contains string. The match is case-insensitive.
1121 User name contains string. The match is case-insensitive.
1122 """
1122 """
1123 return author(repo, subset, x)
1123 return author(repo, subset, x)
1124
1124
1125 # for internal use
1125 # for internal use
1126 def _list(repo, subset, x):
1126 def _list(repo, subset, x):
1127 s = getstring(x, "internal error")
1127 s = getstring(x, "internal error")
1128 if not s:
1128 if not s:
1129 return []
1129 return []
1130 if not isinstance(subset, set):
1130 if not isinstance(subset, set):
1131 subset = set(subset)
1131 subset = set(subset)
1132 ls = [repo[r].rev() for r in s.split('\0')]
1132 ls = [repo[r].rev() for r in s.split('\0')]
1133 return [r for r in ls if r in subset]
1133 return [r for r in ls if r in subset]
1134
1134
1135 symbols = {
1135 symbols = {
1136 "adds": adds,
1136 "adds": adds,
1137 "all": getall,
1137 "all": getall,
1138 "ancestor": ancestor,
1138 "ancestor": ancestor,
1139 "ancestors": ancestors,
1139 "ancestors": ancestors,
1140 "_firstancestors": _firstancestors,
1140 "_firstancestors": _firstancestors,
1141 "author": author,
1141 "author": author,
1142 "bisect": bisect,
1142 "bisect": bisect,
1143 "bisected": bisected,
1143 "bisected": bisected,
1144 "bookmark": bookmark,
1144 "bookmark": bookmark,
1145 "branch": branch,
1145 "branch": branch,
1146 "children": children,
1146 "children": children,
1147 "closed": closed,
1147 "closed": closed,
1148 "contains": contains,
1148 "contains": contains,
1149 "date": date,
1149 "date": date,
1150 "desc": desc,
1150 "desc": desc,
1151 "descendants": descendants,
1151 "descendants": descendants,
1152 "_firstdescendants": _firstdescendants,
1152 "_firstdescendants": _firstdescendants,
1153 "draft": draft,
1153 "draft": draft,
1154 "file": hasfile,
1154 "file": hasfile,
1155 "filelog": filelog,
1155 "filelog": filelog,
1156 "first": first,
1156 "first": first,
1157 "follow": follow,
1157 "follow": follow,
1158 "_followfirst": _followfirst,
1158 "_followfirst": _followfirst,
1159 "grep": grep,
1159 "grep": grep,
1160 "head": head,
1160 "head": head,
1161 "heads": heads,
1161 "heads": heads,
1162 "id": node_,
1162 "id": node_,
1163 "keyword": keyword,
1163 "keyword": keyword,
1164 "last": last,
1164 "last": last,
1165 "limit": limit,
1165 "limit": limit,
1166 "_matchfiles": _matchfiles,
1166 "_matchfiles": _matchfiles,
1167 "max": maxrev,
1167 "max": maxrev,
1168 "merge": merge,
1168 "merge": merge,
1169 "min": minrev,
1169 "min": minrev,
1170 "modifies": modifies,
1170 "modifies": modifies,
1171 "outgoing": outgoing,
1171 "outgoing": outgoing,
1172 "p1": p1,
1172 "p1": p1,
1173 "p2": p2,
1173 "p2": p2,
1174 "parents": parents,
1174 "parents": parents,
1175 "present": present,
1175 "present": present,
1176 "public": public,
1176 "public": public,
1177 "remote": remote,
1177 "remote": remote,
1178 "removes": removes,
1178 "removes": removes,
1179 "rev": rev,
1179 "rev": rev,
1180 "reverse": reverse,
1180 "reverse": reverse,
1181 "roots": roots,
1181 "roots": roots,
1182 "sort": sort,
1182 "sort": sort,
1183 "secret": secret,
1183 "secret": secret,
1184 "matching": matching,
1184 "matching": matching,
1185 "tag": tag,
1185 "tag": tag,
1186 "tagged": tagged,
1186 "tagged": tagged,
1187 "user": user,
1187 "user": user,
1188 "_list": _list,
1188 "_list": _list,
1189 }
1189 }
1190
1190
1191 methods = {
1191 methods = {
1192 "range": rangeset,
1192 "range": rangeset,
1193 "string": stringset,
1193 "string": stringset,
1194 "symbol": symbolset,
1194 "symbol": symbolset,
1195 "and": andset,
1195 "and": andset,
1196 "or": orset,
1196 "or": orset,
1197 "not": notset,
1197 "not": notset,
1198 "list": listset,
1198 "list": listset,
1199 "func": func,
1199 "func": func,
1200 "ancestor": ancestorspec,
1200 "ancestor": ancestorspec,
1201 "parent": parentspec,
1201 "parent": parentspec,
1202 "parentpost": p1,
1202 "parentpost": p1,
1203 }
1203 }
1204
1204
1205 def optimize(x, small):
1205 def optimize(x, small):
1206 if x is None:
1206 if x is None:
1207 return 0, x
1207 return 0, x
1208
1208
1209 smallbonus = 1
1209 smallbonus = 1
1210 if small:
1210 if small:
1211 smallbonus = .5
1211 smallbonus = .5
1212
1212
1213 op = x[0]
1213 op = x[0]
1214 if op == 'minus':
1214 if op == 'minus':
1215 return optimize(('and', x[1], ('not', x[2])), small)
1215 return optimize(('and', x[1], ('not', x[2])), small)
1216 elif op == 'dagrange':
1216 elif op == 'dagrange':
1217 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1217 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1218 ('func', ('symbol', 'ancestors'), x[2])), small)
1218 ('func', ('symbol', 'ancestors'), x[2])), small)
1219 elif op == 'dagrangepre':
1219 elif op == 'dagrangepre':
1220 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1220 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1221 elif op == 'dagrangepost':
1221 elif op == 'dagrangepost':
1222 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1222 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1223 elif op == 'rangepre':
1223 elif op == 'rangepre':
1224 return optimize(('range', ('string', '0'), x[1]), small)
1224 return optimize(('range', ('string', '0'), x[1]), small)
1225 elif op == 'rangepost':
1225 elif op == 'rangepost':
1226 return optimize(('range', x[1], ('string', 'tip')), small)
1226 return optimize(('range', x[1], ('string', 'tip')), small)
1227 elif op == 'negate':
1227 elif op == 'negate':
1228 return optimize(('string',
1228 return optimize(('string',
1229 '-' + getstring(x[1], _("can't negate that"))), small)
1229 '-' + getstring(x[1], _("can't negate that"))), small)
1230 elif op in 'string symbol negate':
1230 elif op in 'string symbol negate':
1231 return smallbonus, x # single revisions are small
1231 return smallbonus, x # single revisions are small
1232 elif op == 'and' or op == 'dagrange':
1232 elif op == 'and' or op == 'dagrange':
1233 wa, ta = optimize(x[1], True)
1233 wa, ta = optimize(x[1], True)
1234 wb, tb = optimize(x[2], True)
1234 wb, tb = optimize(x[2], True)
1235 w = min(wa, wb)
1235 w = min(wa, wb)
1236 if wa > wb:
1236 if wa > wb:
1237 return w, (op, tb, ta)
1237 return w, (op, tb, ta)
1238 return w, (op, ta, tb)
1238 return w, (op, ta, tb)
1239 elif op == 'or':
1239 elif op == 'or':
1240 wa, ta = optimize(x[1], False)
1240 wa, ta = optimize(x[1], False)
1241 wb, tb = optimize(x[2], False)
1241 wb, tb = optimize(x[2], False)
1242 if wb < wa:
1242 if wb < wa:
1243 wb, wa = wa, wb
1243 wb, wa = wa, wb
1244 return max(wa, wb), (op, ta, tb)
1244 return max(wa, wb), (op, ta, tb)
1245 elif op == 'not':
1245 elif op == 'not':
1246 o = optimize(x[1], not small)
1246 o = optimize(x[1], not small)
1247 return o[0], (op, o[1])
1247 return o[0], (op, o[1])
1248 elif op == 'parentpost':
1248 elif op == 'parentpost':
1249 o = optimize(x[1], small)
1249 o = optimize(x[1], small)
1250 return o[0], (op, o[1])
1250 return o[0], (op, o[1])
1251 elif op == 'group':
1251 elif op == 'group':
1252 return optimize(x[1], small)
1252 return optimize(x[1], small)
1253 elif op in 'range list parent ancestorspec':
1253 elif op in 'range list parent ancestorspec':
1254 if op == 'parent':
1254 if op == 'parent':
1255 # x^:y means (x^) : y, not x ^ (:y)
1255 # x^:y means (x^) : y, not x ^ (:y)
1256 post = ('parentpost', x[1])
1256 post = ('parentpost', x[1])
1257 if x[2][0] == 'dagrangepre':
1257 if x[2][0] == 'dagrangepre':
1258 return optimize(('dagrange', post, x[2][1]), small)
1258 return optimize(('dagrange', post, x[2][1]), small)
1259 elif x[2][0] == 'rangepre':
1259 elif x[2][0] == 'rangepre':
1260 return optimize(('range', post, x[2][1]), small)
1260 return optimize(('range', post, x[2][1]), small)
1261
1261
1262 wa, ta = optimize(x[1], small)
1262 wa, ta = optimize(x[1], small)
1263 wb, tb = optimize(x[2], small)
1263 wb, tb = optimize(x[2], small)
1264 return wa + wb, (op, ta, tb)
1264 return wa + wb, (op, ta, tb)
1265 elif op == 'func':
1265 elif op == 'func':
1266 f = getstring(x[1], _("not a symbol"))
1266 f = getstring(x[1], _("not a symbol"))
1267 wa, ta = optimize(x[2], small)
1267 wa, ta = optimize(x[2], small)
1268 if f in ("author branch closed date desc file grep keyword "
1268 if f in ("author branch closed date desc file grep keyword "
1269 "outgoing user"):
1269 "outgoing user"):
1270 w = 10 # slow
1270 w = 10 # slow
1271 elif f in "modifies adds removes":
1271 elif f in "modifies adds removes":
1272 w = 30 # slower
1272 w = 30 # slower
1273 elif f == "contains":
1273 elif f == "contains":
1274 w = 100 # very slow
1274 w = 100 # very slow
1275 elif f == "ancestor":
1275 elif f == "ancestor":
1276 w = 1 * smallbonus
1276 w = 1 * smallbonus
1277 elif f in "reverse limit first":
1277 elif f in "reverse limit first":
1278 w = 0
1278 w = 0
1279 elif f in "sort":
1279 elif f in "sort":
1280 w = 10 # assume most sorts look at changelog
1280 w = 10 # assume most sorts look at changelog
1281 else:
1281 else:
1282 w = 1
1282 w = 1
1283 return w + wa, (op, x[1], ta)
1283 return w + wa, (op, x[1], ta)
1284 return 1, x
1284 return 1, x
1285
1285
1286 _aliasarg = ('func', ('symbol', '_aliasarg'))
1286 _aliasarg = ('func', ('symbol', '_aliasarg'))
1287 def _getaliasarg(tree):
1287 def _getaliasarg(tree):
1288 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1288 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1289 return X, None otherwise.
1289 return X, None otherwise.
1290 """
1290 """
1291 if (len(tree) == 3 and tree[:2] == _aliasarg
1291 if (len(tree) == 3 and tree[:2] == _aliasarg
1292 and tree[2][0] == 'string'):
1292 and tree[2][0] == 'string'):
1293 return tree[2][1]
1293 return tree[2][1]
1294 return None
1294 return None
1295
1295
1296 def _checkaliasarg(tree, known=None):
1296 def _checkaliasarg(tree, known=None):
1297 """Check tree contains no _aliasarg construct or only ones which
1297 """Check tree contains no _aliasarg construct or only ones which
1298 value is in known. Used to avoid alias placeholders injection.
1298 value is in known. Used to avoid alias placeholders injection.
1299 """
1299 """
1300 if isinstance(tree, tuple):
1300 if isinstance(tree, tuple):
1301 arg = _getaliasarg(tree)
1301 arg = _getaliasarg(tree)
1302 if arg is not None and (not known or arg not in known):
1302 if arg is not None and (not known or arg not in known):
1303 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1303 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1304 for t in tree:
1304 for t in tree:
1305 _checkaliasarg(t, known)
1305 _checkaliasarg(t, known)
1306
1306
1307 class revsetalias(object):
1307 class revsetalias(object):
1308 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1308 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1309 args = None
1309 args = None
1310
1310
1311 def __init__(self, name, value):
1311 def __init__(self, name, value):
1312 '''Aliases like:
1312 '''Aliases like:
1313
1313
1314 h = heads(default)
1314 h = heads(default)
1315 b($1) = ancestors($1) - ancestors(default)
1315 b($1) = ancestors($1) - ancestors(default)
1316 '''
1316 '''
1317 m = self.funcre.search(name)
1317 m = self.funcre.search(name)
1318 if m:
1318 if m:
1319 self.name = m.group(1)
1319 self.name = m.group(1)
1320 self.tree = ('func', ('symbol', m.group(1)))
1320 self.tree = ('func', ('symbol', m.group(1)))
1321 self.args = [x.strip() for x in m.group(2).split(',')]
1321 self.args = [x.strip() for x in m.group(2).split(',')]
1322 for arg in self.args:
1322 for arg in self.args:
1323 # _aliasarg() is an unknown symbol only used separate
1323 # _aliasarg() is an unknown symbol only used separate
1324 # alias argument placeholders from regular strings.
1324 # alias argument placeholders from regular strings.
1325 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1325 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1326 else:
1326 else:
1327 self.name = name
1327 self.name = name
1328 self.tree = ('symbol', name)
1328 self.tree = ('symbol', name)
1329
1329
1330 self.replacement, pos = parse(value)
1330 self.replacement, pos = parse(value)
1331 if pos != len(value):
1331 if pos != len(value):
1332 raise error.ParseError(_('invalid token'), pos)
1332 raise error.ParseError(_('invalid token'), pos)
1333 # Check for placeholder injection
1333 # Check for placeholder injection
1334 _checkaliasarg(self.replacement, self.args)
1334 _checkaliasarg(self.replacement, self.args)
1335
1335
1336 def _getalias(aliases, tree):
1336 def _getalias(aliases, tree):
1337 """If tree looks like an unexpanded alias, return it. Return None
1337 """If tree looks like an unexpanded alias, return it. Return None
1338 otherwise.
1338 otherwise.
1339 """
1339 """
1340 if isinstance(tree, tuple) and tree:
1340 if isinstance(tree, tuple) and tree:
1341 if tree[0] == 'symbol' and len(tree) == 2:
1341 if tree[0] == 'symbol' and len(tree) == 2:
1342 name = tree[1]
1342 name = tree[1]
1343 alias = aliases.get(name)
1343 alias = aliases.get(name)
1344 if alias and alias.args is None and alias.tree == tree:
1344 if alias and alias.args is None and alias.tree == tree:
1345 return alias
1345 return alias
1346 if tree[0] == 'func' and len(tree) > 1:
1346 if tree[0] == 'func' and len(tree) > 1:
1347 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1347 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1348 name = tree[1][1]
1348 name = tree[1][1]
1349 alias = aliases.get(name)
1349 alias = aliases.get(name)
1350 if alias and alias.args is not None and alias.tree == tree[:2]:
1350 if alias and alias.args is not None and alias.tree == tree[:2]:
1351 return alias
1351 return alias
1352 return None
1352 return None
1353
1353
1354 def _expandargs(tree, args):
1354 def _expandargs(tree, args):
1355 """Replace _aliasarg instances with the substitution value of the
1355 """Replace _aliasarg instances with the substitution value of the
1356 same name in args, recursively.
1356 same name in args, recursively.
1357 """
1357 """
1358 if not tree or not isinstance(tree, tuple):
1358 if not tree or not isinstance(tree, tuple):
1359 return tree
1359 return tree
1360 arg = _getaliasarg(tree)
1360 arg = _getaliasarg(tree)
1361 if arg is not None:
1361 if arg is not None:
1362 return args[arg]
1362 return args[arg]
1363 return tuple(_expandargs(t, args) for t in tree)
1363 return tuple(_expandargs(t, args) for t in tree)
1364
1364
1365 def _expandaliases(aliases, tree, expanding):
1365 def _expandaliases(aliases, tree, expanding):
1366 """Expand aliases in tree, recursively.
1366 """Expand aliases in tree, recursively.
1367
1367
1368 'aliases' is a dictionary mapping user defined aliases to
1368 'aliases' is a dictionary mapping user defined aliases to
1369 revsetalias objects.
1369 revsetalias objects.
1370 """
1370 """
1371 if not isinstance(tree, tuple):
1371 if not isinstance(tree, tuple):
1372 # Do not expand raw strings
1372 # Do not expand raw strings
1373 return tree
1373 return tree
1374 alias = _getalias(aliases, tree)
1374 alias = _getalias(aliases, tree)
1375 if alias is not None:
1375 if alias is not None:
1376 if alias in expanding:
1376 if alias in expanding:
1377 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1377 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1378 'detected') % alias.name)
1378 'detected') % alias.name)
1379 expanding.append(alias)
1379 expanding.append(alias)
1380 result = alias.replacement
1380 result = _expandaliases(aliases, alias.replacement, expanding)
1381 expanding.pop()
1381 if alias.args is not None:
1382 if alias.args is not None:
1382 l = getlist(tree[2])
1383 l = getlist(tree[2])
1383 if len(l) != len(alias.args):
1384 if len(l) != len(alias.args):
1384 raise error.ParseError(
1385 raise error.ParseError(
1385 _('invalid number of arguments: %s') % len(l))
1386 _('invalid number of arguments: %s') % len(l))
1387 l = [_expandaliases(aliases, a, []) for a in l]
1386 result = _expandargs(result, dict(zip(alias.args, l)))
1388 result = _expandargs(result, dict(zip(alias.args, l)))
1387 # Recurse in place, the base expression may have been rewritten
1388 result = _expandaliases(aliases, result, expanding)
1389 expanding.pop()
1390 else:
1389 else:
1391 result = tuple(_expandaliases(aliases, t, expanding)
1390 result = tuple(_expandaliases(aliases, t, expanding)
1392 for t in tree)
1391 for t in tree)
1393 return result
1392 return result
1394
1393
1395 def findaliases(ui, tree):
1394 def findaliases(ui, tree):
1396 _checkaliasarg(tree)
1395 _checkaliasarg(tree)
1397 aliases = {}
1396 aliases = {}
1398 for k, v in ui.configitems('revsetalias'):
1397 for k, v in ui.configitems('revsetalias'):
1399 alias = revsetalias(k, v)
1398 alias = revsetalias(k, v)
1400 aliases[alias.name] = alias
1399 aliases[alias.name] = alias
1401 return _expandaliases(aliases, tree, [])
1400 return _expandaliases(aliases, tree, [])
1402
1401
1403 parse = parser.parser(tokenize, elements).parse
1402 parse = parser.parser(tokenize, elements).parse
1404
1403
1405 def match(ui, spec):
1404 def match(ui, spec):
1406 if not spec:
1405 if not spec:
1407 raise error.ParseError(_("empty query"))
1406 raise error.ParseError(_("empty query"))
1408 tree, pos = parse(spec)
1407 tree, pos = parse(spec)
1409 if (pos != len(spec)):
1408 if (pos != len(spec)):
1410 raise error.ParseError(_("invalid token"), pos)
1409 raise error.ParseError(_("invalid token"), pos)
1411 if ui:
1410 if ui:
1412 tree = findaliases(ui, tree)
1411 tree = findaliases(ui, tree)
1413 weight, tree = optimize(tree, True)
1412 weight, tree = optimize(tree, True)
1414 def mfunc(repo, subset):
1413 def mfunc(repo, subset):
1415 return getset(repo, subset, tree)
1414 return getset(repo, subset, tree)
1416 return mfunc
1415 return mfunc
1417
1416
1418 def formatspec(expr, *args):
1417 def formatspec(expr, *args):
1419 '''
1418 '''
1420 This is a convenience function for using revsets internally, and
1419 This is a convenience function for using revsets internally, and
1421 escapes arguments appropriately. Aliases are intentionally ignored
1420 escapes arguments appropriately. Aliases are intentionally ignored
1422 so that intended expression behavior isn't accidentally subverted.
1421 so that intended expression behavior isn't accidentally subverted.
1423
1422
1424 Supported arguments:
1423 Supported arguments:
1425
1424
1426 %r = revset expression, parenthesized
1425 %r = revset expression, parenthesized
1427 %d = int(arg), no quoting
1426 %d = int(arg), no quoting
1428 %s = string(arg), escaped and single-quoted
1427 %s = string(arg), escaped and single-quoted
1429 %b = arg.branch(), escaped and single-quoted
1428 %b = arg.branch(), escaped and single-quoted
1430 %n = hex(arg), single-quoted
1429 %n = hex(arg), single-quoted
1431 %% = a literal '%'
1430 %% = a literal '%'
1432
1431
1433 Prefixing the type with 'l' specifies a parenthesized list of that type.
1432 Prefixing the type with 'l' specifies a parenthesized list of that type.
1434
1433
1435 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1434 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1436 '(10 or 11):: and ((this()) or (that()))'
1435 '(10 or 11):: and ((this()) or (that()))'
1437 >>> formatspec('%d:: and not %d::', 10, 20)
1436 >>> formatspec('%d:: and not %d::', 10, 20)
1438 '10:: and not 20::'
1437 '10:: and not 20::'
1439 >>> formatspec('%ld or %ld', [], [1])
1438 >>> formatspec('%ld or %ld', [], [1])
1440 "_list('') or 1"
1439 "_list('') or 1"
1441 >>> formatspec('keyword(%s)', 'foo\\xe9')
1440 >>> formatspec('keyword(%s)', 'foo\\xe9')
1442 "keyword('foo\\\\xe9')"
1441 "keyword('foo\\\\xe9')"
1443 >>> b = lambda: 'default'
1442 >>> b = lambda: 'default'
1444 >>> b.branch = b
1443 >>> b.branch = b
1445 >>> formatspec('branch(%b)', b)
1444 >>> formatspec('branch(%b)', b)
1446 "branch('default')"
1445 "branch('default')"
1447 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1446 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1448 "root(_list('a\\x00b\\x00c\\x00d'))"
1447 "root(_list('a\\x00b\\x00c\\x00d'))"
1449 '''
1448 '''
1450
1449
1451 def quote(s):
1450 def quote(s):
1452 return repr(str(s))
1451 return repr(str(s))
1453
1452
1454 def argtype(c, arg):
1453 def argtype(c, arg):
1455 if c == 'd':
1454 if c == 'd':
1456 return str(int(arg))
1455 return str(int(arg))
1457 elif c == 's':
1456 elif c == 's':
1458 return quote(arg)
1457 return quote(arg)
1459 elif c == 'r':
1458 elif c == 'r':
1460 parse(arg) # make sure syntax errors are confined
1459 parse(arg) # make sure syntax errors are confined
1461 return '(%s)' % arg
1460 return '(%s)' % arg
1462 elif c == 'n':
1461 elif c == 'n':
1463 return quote(node.hex(arg))
1462 return quote(node.hex(arg))
1464 elif c == 'b':
1463 elif c == 'b':
1465 return quote(arg.branch())
1464 return quote(arg.branch())
1466
1465
1467 def listexp(s, t):
1466 def listexp(s, t):
1468 l = len(s)
1467 l = len(s)
1469 if l == 0:
1468 if l == 0:
1470 return "_list('')"
1469 return "_list('')"
1471 elif l == 1:
1470 elif l == 1:
1472 return argtype(t, s[0])
1471 return argtype(t, s[0])
1473 elif t == 'd':
1472 elif t == 'd':
1474 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1473 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1475 elif t == 's':
1474 elif t == 's':
1476 return "_list('%s')" % "\0".join(s)
1475 return "_list('%s')" % "\0".join(s)
1477 elif t == 'n':
1476 elif t == 'n':
1478 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1477 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1479 elif t == 'b':
1478 elif t == 'b':
1480 return "_list('%s')" % "\0".join(a.branch() for a in s)
1479 return "_list('%s')" % "\0".join(a.branch() for a in s)
1481
1480
1482 m = l // 2
1481 m = l // 2
1483 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1482 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1484
1483
1485 ret = ''
1484 ret = ''
1486 pos = 0
1485 pos = 0
1487 arg = 0
1486 arg = 0
1488 while pos < len(expr):
1487 while pos < len(expr):
1489 c = expr[pos]
1488 c = expr[pos]
1490 if c == '%':
1489 if c == '%':
1491 pos += 1
1490 pos += 1
1492 d = expr[pos]
1491 d = expr[pos]
1493 if d == '%':
1492 if d == '%':
1494 ret += d
1493 ret += d
1495 elif d in 'dsnbr':
1494 elif d in 'dsnbr':
1496 ret += argtype(d, args[arg])
1495 ret += argtype(d, args[arg])
1497 arg += 1
1496 arg += 1
1498 elif d == 'l':
1497 elif d == 'l':
1499 # a list of some type
1498 # a list of some type
1500 pos += 1
1499 pos += 1
1501 d = expr[pos]
1500 d = expr[pos]
1502 ret += listexp(list(args[arg]), d)
1501 ret += listexp(list(args[arg]), d)
1503 arg += 1
1502 arg += 1
1504 else:
1503 else:
1505 raise util.Abort('unexpected revspec format character %s' % d)
1504 raise util.Abort('unexpected revspec format character %s' % d)
1506 else:
1505 else:
1507 ret += c
1506 ret += c
1508 pos += 1
1507 pos += 1
1509
1508
1510 return ret
1509 return ret
1511
1510
1512 def prettyformat(tree):
1511 def prettyformat(tree):
1513 def _prettyformat(tree, level, lines):
1512 def _prettyformat(tree, level, lines):
1514 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1513 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1515 lines.append((level, str(tree)))
1514 lines.append((level, str(tree)))
1516 else:
1515 else:
1517 lines.append((level, '(%s' % tree[0]))
1516 lines.append((level, '(%s' % tree[0]))
1518 for s in tree[1:]:
1517 for s in tree[1:]:
1519 _prettyformat(s, level + 1, lines)
1518 _prettyformat(s, level + 1, lines)
1520 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1519 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1521
1520
1522 lines = []
1521 lines = []
1523 _prettyformat(tree, 0, lines)
1522 _prettyformat(tree, 0, lines)
1524 output = '\n'.join((' '*l + s) for l, s in lines)
1523 output = '\n'.join((' '*l + s) for l, s in lines)
1525 return output
1524 return output
1526
1525
1527 # tell hggettext to extract docstrings from these functions:
1526 # tell hggettext to extract docstrings from these functions:
1528 i18nfunctions = symbols.values()
1527 i18nfunctions = symbols.values()
@@ -1,740 +1,761 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
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)'
413 $ log 'matching(1 or 2 or 3) and (2 or 3 or 1)'
414 2
414 2
415 3
415 3
416 1
416 1
417
417
418 issue2437
418 issue2437
419
419
420 $ log '3 and p1(5)'
420 $ log '3 and p1(5)'
421 3
421 3
422 $ log '4 and p2(6)'
422 $ log '4 and p2(6)'
423 4
423 4
424 $ log '1 and parents(:2)'
424 $ log '1 and parents(:2)'
425 1
425 1
426 $ log '2 and children(1:)'
426 $ log '2 and children(1:)'
427 2
427 2
428 $ log 'roots(all()) or roots(all())'
428 $ log 'roots(all()) or roots(all())'
429 0
429 0
430 $ hg debugrevspec 'roots(all()) or roots(all())'
430 $ hg debugrevspec 'roots(all()) or roots(all())'
431 0
431 0
432 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
432 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
433 9
433 9
434 $ 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(Γ©)))'
435 4
435 4
436
436
437 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
438
438
439 $ log '1 OR 2'
439 $ log '1 OR 2'
440 hg: parse error at 2: invalid token
440 hg: parse error at 2: invalid token
441 [255]
441 [255]
442
442
443 or operator should preserve ordering:
443 or operator should preserve ordering:
444 $ log 'reverse(2::4) or tip'
444 $ log 'reverse(2::4) or tip'
445 4
445 4
446 2
446 2
447 9
447 9
448
448
449 parentrevspec
449 parentrevspec
450
450
451 $ log 'merge()^0'
451 $ log 'merge()^0'
452 6
452 6
453 $ log 'merge()^'
453 $ log 'merge()^'
454 5
454 5
455 $ log 'merge()^1'
455 $ log 'merge()^1'
456 5
456 5
457 $ log 'merge()^2'
457 $ log 'merge()^2'
458 4
458 4
459 $ log 'merge()^^'
459 $ log 'merge()^^'
460 3
460 3
461 $ log 'merge()^1^'
461 $ log 'merge()^1^'
462 3
462 3
463 $ log 'merge()^^^'
463 $ log 'merge()^^^'
464 1
464 1
465
465
466 $ log 'merge()~0'
466 $ log 'merge()~0'
467 6
467 6
468 $ log 'merge()~1'
468 $ log 'merge()~1'
469 5
469 5
470 $ log 'merge()~2'
470 $ log 'merge()~2'
471 3
471 3
472 $ log 'merge()~2^1'
472 $ log 'merge()~2^1'
473 1
473 1
474 $ log 'merge()~3'
474 $ log 'merge()~3'
475 1
475 1
476
476
477 $ log '(-3:tip)^'
477 $ log '(-3:tip)^'
478 4
478 4
479 6
479 6
480 8
480 8
481
481
482 $ log 'tip^foo'
482 $ log 'tip^foo'
483 hg: parse error: ^ expects a number 0, 1, or 2
483 hg: parse error: ^ expects a number 0, 1, or 2
484 [255]
484 [255]
485
485
486 aliases:
486 aliases:
487
487
488 $ echo '[revsetalias]' >> .hg/hgrc
488 $ echo '[revsetalias]' >> .hg/hgrc
489 $ echo 'm = merge()' >> .hg/hgrc
489 $ echo 'm = merge()' >> .hg/hgrc
490 $ echo 'sincem = descendants(m)' >> .hg/hgrc
490 $ echo 'sincem = descendants(m)' >> .hg/hgrc
491 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
491 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
492 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
492 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
493 $ 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
494
494
495 $ try m
495 $ try m
496 ('symbol', 'm')
496 ('symbol', 'm')
497 (func
497 (func
498 ('symbol', 'merge')
498 ('symbol', 'merge')
499 None)
499 None)
500 6
500 6
501
501
502 test alias recursion
502 test alias recursion
503
503
504 $ try sincem
504 $ try sincem
505 ('symbol', 'sincem')
505 ('symbol', 'sincem')
506 (func
506 (func
507 ('symbol', 'descendants')
507 ('symbol', 'descendants')
508 (func
508 (func
509 ('symbol', 'merge')
509 ('symbol', 'merge')
510 None))
510 None))
511 6
511 6
512 7
512 7
513
513
514 test infinite recursion
514 test infinite recursion
515
515
516 $ echo 'recurse1 = recurse2' >> .hg/hgrc
516 $ echo 'recurse1 = recurse2' >> .hg/hgrc
517 $ echo 'recurse2 = recurse1' >> .hg/hgrc
517 $ echo 'recurse2 = recurse1' >> .hg/hgrc
518 $ try recurse1
518 $ try recurse1
519 ('symbol', 'recurse1')
519 ('symbol', 'recurse1')
520 hg: parse error: infinite expansion of revset alias "recurse1" detected
520 hg: parse error: infinite expansion of revset alias "recurse1" detected
521 [255]
521 [255]
522
522
523 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
524 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
525 $ try "level2(level1(1, 2), 3)"
526 (func
527 ('symbol', 'level2')
528 (list
529 (func
530 ('symbol', 'level1')
531 (list
532 ('symbol', '1')
533 ('symbol', '2')))
534 ('symbol', '3')))
535 (or
536 ('symbol', '3')
537 (or
538 ('symbol', '1')
539 ('symbol', '2')))
540 3
541 1
542 2
543
523 test nesting and variable passing
544 test nesting and variable passing
524
545
525 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
546 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
526 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
547 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
527 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
548 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
528 $ try 'nested(2:5)'
549 $ try 'nested(2:5)'
529 (func
550 (func
530 ('symbol', 'nested')
551 ('symbol', 'nested')
531 (range
552 (range
532 ('symbol', '2')
553 ('symbol', '2')
533 ('symbol', '5')))
554 ('symbol', '5')))
534 (func
555 (func
535 ('symbol', 'max')
556 ('symbol', 'max')
536 (range
557 (range
537 ('symbol', '2')
558 ('symbol', '2')
538 ('symbol', '5')))
559 ('symbol', '5')))
539 5
560 5
540
561
541 test variable isolation, variable placeholders are rewritten as string
562 test variable isolation, variable placeholders are rewritten as string
542 then parsed and matched again as string. Check they do not leak too
563 then parsed and matched again as string. Check they do not leak too
543 far away.
564 far away.
544
565
545 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
566 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
546 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
567 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
547 $ try 'callinjection(2:5)'
568 $ try 'callinjection(2:5)'
548 (func
569 (func
549 ('symbol', 'callinjection')
570 ('symbol', 'callinjection')
550 (range
571 (range
551 ('symbol', '2')
572 ('symbol', '2')
552 ('symbol', '5')))
573 ('symbol', '5')))
553 (func
574 (func
554 ('symbol', 'descendants')
575 ('symbol', 'descendants')
555 (func
576 (func
556 ('symbol', 'max')
577 ('symbol', 'max')
557 ('string', '$1')))
578 ('string', '$1')))
558 abort: unknown revision '$1'!
579 abort: unknown revision '$1'!
559 [255]
580 [255]
560
581
561 $ echo 'injectparamasstring2 = max(_aliasarg("$1"))' >> .hg/hgrc
582 $ echo 'injectparamasstring2 = max(_aliasarg("$1"))' >> .hg/hgrc
562 $ echo 'callinjection2($1) = descendants(injectparamasstring2)' >> .hg/hgrc
583 $ echo 'callinjection2($1) = descendants(injectparamasstring2)' >> .hg/hgrc
563 $ try 'callinjection2(2:5)'
584 $ try 'callinjection2(2:5)'
564 (func
585 (func
565 ('symbol', 'callinjection2')
586 ('symbol', 'callinjection2')
566 (range
587 (range
567 ('symbol', '2')
588 ('symbol', '2')
568 ('symbol', '5')))
589 ('symbol', '5')))
569 hg: parse error: not a function: _aliasarg
590 hg: parse error: not a function: _aliasarg
570 [255]
591 [255]
571 >>> data = file('.hg/hgrc', 'rb').read()
592 >>> data = file('.hg/hgrc', 'rb').read()
572 >>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
593 >>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
573
594
574 $ try 'd(2:5)'
595 $ try 'd(2:5)'
575 (func
596 (func
576 ('symbol', 'd')
597 ('symbol', 'd')
577 (range
598 (range
578 ('symbol', '2')
599 ('symbol', '2')
579 ('symbol', '5')))
600 ('symbol', '5')))
580 (func
601 (func
581 ('symbol', 'reverse')
602 ('symbol', 'reverse')
582 (func
603 (func
583 ('symbol', 'sort')
604 ('symbol', 'sort')
584 (list
605 (list
585 (range
606 (range
586 ('symbol', '2')
607 ('symbol', '2')
587 ('symbol', '5'))
608 ('symbol', '5'))
588 ('symbol', 'date'))))
609 ('symbol', 'date'))))
589 4
610 4
590 5
611 5
591 3
612 3
592 2
613 2
593 $ try 'rs(2 or 3, date)'
614 $ try 'rs(2 or 3, date)'
594 (func
615 (func
595 ('symbol', 'rs')
616 ('symbol', 'rs')
596 (list
617 (list
597 (or
618 (or
598 ('symbol', '2')
619 ('symbol', '2')
599 ('symbol', '3'))
620 ('symbol', '3'))
600 ('symbol', 'date')))
621 ('symbol', 'date')))
601 (func
622 (func
602 ('symbol', 'reverse')
623 ('symbol', 'reverse')
603 (func
624 (func
604 ('symbol', 'sort')
625 ('symbol', 'sort')
605 (list
626 (list
606 (or
627 (or
607 ('symbol', '2')
628 ('symbol', '2')
608 ('symbol', '3'))
629 ('symbol', '3'))
609 ('symbol', 'date'))))
630 ('symbol', 'date'))))
610 3
631 3
611 2
632 2
612 $ try 'rs()'
633 $ try 'rs()'
613 (func
634 (func
614 ('symbol', 'rs')
635 ('symbol', 'rs')
615 None)
636 None)
616 hg: parse error: invalid number of arguments: 0
637 hg: parse error: invalid number of arguments: 0
617 [255]
638 [255]
618 $ try 'rs(2)'
639 $ try 'rs(2)'
619 (func
640 (func
620 ('symbol', 'rs')
641 ('symbol', 'rs')
621 ('symbol', '2'))
642 ('symbol', '2'))
622 hg: parse error: invalid number of arguments: 1
643 hg: parse error: invalid number of arguments: 1
623 [255]
644 [255]
624 $ try 'rs(2, data, 7)'
645 $ try 'rs(2, data, 7)'
625 (func
646 (func
626 ('symbol', 'rs')
647 ('symbol', 'rs')
627 (list
648 (list
628 (list
649 (list
629 ('symbol', '2')
650 ('symbol', '2')
630 ('symbol', 'data'))
651 ('symbol', 'data'))
631 ('symbol', '7')))
652 ('symbol', '7')))
632 hg: parse error: invalid number of arguments: 3
653 hg: parse error: invalid number of arguments: 3
633 [255]
654 [255]
634 $ try 'rs4(2 or 3, x, x, date)'
655 $ try 'rs4(2 or 3, x, x, date)'
635 (func
656 (func
636 ('symbol', 'rs4')
657 ('symbol', 'rs4')
637 (list
658 (list
638 (list
659 (list
639 (list
660 (list
640 (or
661 (or
641 ('symbol', '2')
662 ('symbol', '2')
642 ('symbol', '3'))
663 ('symbol', '3'))
643 ('symbol', 'x'))
664 ('symbol', 'x'))
644 ('symbol', 'x'))
665 ('symbol', 'x'))
645 ('symbol', 'date')))
666 ('symbol', 'date')))
646 (func
667 (func
647 ('symbol', 'reverse')
668 ('symbol', 'reverse')
648 (func
669 (func
649 ('symbol', 'sort')
670 ('symbol', 'sort')
650 (list
671 (list
651 (or
672 (or
652 ('symbol', '2')
673 ('symbol', '2')
653 ('symbol', '3'))
674 ('symbol', '3'))
654 ('symbol', 'date'))))
675 ('symbol', 'date'))))
655 3
676 3
656 2
677 2
657
678
658 issue2549 - correct optimizations
679 issue2549 - correct optimizations
659
680
660 $ log 'limit(1 or 2 or 3, 2) and not 2'
681 $ log 'limit(1 or 2 or 3, 2) and not 2'
661 1
682 1
662 $ log 'max(1 or 2) and not 2'
683 $ log 'max(1 or 2) and not 2'
663 $ log 'min(1 or 2) and not 1'
684 $ log 'min(1 or 2) and not 1'
664 $ log 'last(1 or 2, 1) and not 2'
685 $ log 'last(1 or 2, 1) and not 2'
665
686
666 tests for 'remote()' predicate:
687 tests for 'remote()' predicate:
667 #. (csets in remote) (id) (remote)
688 #. (csets in remote) (id) (remote)
668 1. less than local current branch "default"
689 1. less than local current branch "default"
669 2. same with local specified "default"
690 2. same with local specified "default"
670 3. more than local specified specified
691 3. more than local specified specified
671
692
672 $ hg clone --quiet -U . ../remote3
693 $ hg clone --quiet -U . ../remote3
673 $ cd ../remote3
694 $ cd ../remote3
674 $ hg update -q 7
695 $ hg update -q 7
675 $ echo r > r
696 $ echo r > r
676 $ hg ci -Aqm 10
697 $ hg ci -Aqm 10
677 $ log 'remote()'
698 $ log 'remote()'
678 7
699 7
679 $ log 'remote("a-b-c-")'
700 $ log 'remote("a-b-c-")'
680 2
701 2
681 $ cd ../repo
702 $ cd ../repo
682 $ log 'remote(".a.b.c.", "../remote3")'
703 $ log 'remote(".a.b.c.", "../remote3")'
683
704
684 $ cd ..
705 $ cd ..
685
706
686 test author/desc/keyword in problematic encoding
707 test author/desc/keyword in problematic encoding
687 # unicode: cp932:
708 # unicode: cp932:
688 # u30A2 0x83 0x41(= 'A')
709 # u30A2 0x83 0x41(= 'A')
689 # u30C2 0x83 0x61(= 'a')
710 # u30C2 0x83 0x61(= 'a')
690
711
691 $ hg init problematicencoding
712 $ hg init problematicencoding
692 $ cd problematicencoding
713 $ cd problematicencoding
693
714
694 $ python > setup.sh <<EOF
715 $ python > setup.sh <<EOF
695 > print u'''
716 > print u'''
696 > echo a > text
717 > echo a > text
697 > hg add text
718 > hg add text
698 > hg --encoding utf-8 commit -u '\u30A2' -m none
719 > hg --encoding utf-8 commit -u '\u30A2' -m none
699 > echo b > text
720 > echo b > text
700 > hg --encoding utf-8 commit -u '\u30C2' -m none
721 > hg --encoding utf-8 commit -u '\u30C2' -m none
701 > echo c > text
722 > echo c > text
702 > hg --encoding utf-8 commit -u none -m '\u30A2'
723 > hg --encoding utf-8 commit -u none -m '\u30A2'
703 > echo d > text
724 > echo d > text
704 > hg --encoding utf-8 commit -u none -m '\u30C2'
725 > hg --encoding utf-8 commit -u none -m '\u30C2'
705 > '''.encode('utf-8')
726 > '''.encode('utf-8')
706 > EOF
727 > EOF
707 $ sh < setup.sh
728 $ sh < setup.sh
708
729
709 test in problematic encoding
730 test in problematic encoding
710 $ python > test.sh <<EOF
731 $ python > test.sh <<EOF
711 > print u'''
732 > print u'''
712 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
733 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
713 > echo ====
734 > echo ====
714 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
735 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
715 > echo ====
736 > echo ====
716 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
737 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
717 > echo ====
738 > echo ====
718 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
739 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
719 > echo ====
740 > echo ====
720 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
741 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
721 > echo ====
742 > echo ====
722 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
743 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
723 > '''.encode('cp932')
744 > '''.encode('cp932')
724 > EOF
745 > EOF
725 $ sh < test.sh
746 $ sh < test.sh
726 0
747 0
727 ====
748 ====
728 1
749 1
729 ====
750 ====
730 2
751 2
731 ====
752 ====
732 3
753 3
733 ====
754 ====
734 0
755 0
735 2
756 2
736 ====
757 ====
737 1
758 1
738 3
759 3
739
760
740 $ cd ..
761 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now