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