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