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