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