##// END OF EJS Templates
revset: speedup matching() by stopping the match early if a field does not match...
Angel Ezquerra -
r16445:453c8670 default
parent child Browse files
Show More
@@ -1,1471 +1,1474 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 # Each field will be matched with its own "getfield" function
953 # Each field will be matched with its own "getfield" function
954 # which will be added to the getfieldfuncs array of functions
954 # which will be added to the getfieldfuncs array of functions
955 getfieldfuncs = []
955 getfieldfuncs = []
956 _funcs = {
956 _funcs = {
957 'user': lambda r: repo[r].user(),
957 'user': lambda r: repo[r].user(),
958 'branch': lambda r: repo[r].branch(),
958 'branch': lambda r: repo[r].branch(),
959 'date': lambda r: repo[r].date(),
959 'date': lambda r: repo[r].date(),
960 'description': lambda r: repo[r].description(),
960 'description': lambda r: repo[r].description(),
961 'files': lambda r: repo[r].files(),
961 'files': lambda r: repo[r].files(),
962 'parents': lambda r: repo[r].parents(),
962 'parents': lambda r: repo[r].parents(),
963 'phase': lambda r: repo[r].phase(),
963 'phase': lambda r: repo[r].phase(),
964 'substate': lambda r: repo[r].substate,
964 'substate': lambda r: repo[r].substate,
965 'summary': lambda r: repo[r].description().splitlines()[0],
965 'summary': lambda r: repo[r].description().splitlines()[0],
966 }
966 }
967 for info in fields:
967 for info in fields:
968 getfield = _funcs.get(info, None)
968 getfield = _funcs.get(info, None)
969 if getfield is None:
969 if getfield is None:
970 raise error.ParseError(
970 raise error.ParseError(
971 _("unexpected field name passed to matching: %s") % info)
971 _("unexpected field name passed to matching: %s") % info)
972 getfieldfuncs.append(getfield)
972 getfieldfuncs.append(getfield)
973
974 # convert the getfield array of functions into a "getinfo" function
973 # convert the getfield array of functions into a "getinfo" function
975 # which returns an array of field values (or a single value if there
974 # which returns an array of field values (or a single value if there
976 # is only one field to match)
975 # is only one field to match)
977 if len(getfieldfuncs) == 1:
976 getinfo = lambda r: [f(r) for f in getfieldfuncs]
978 getinfo = getfieldfuncs[0]
979 else:
980 getinfo = lambda r: [f(r) for f in getfieldfuncs]
981
977
982 matches = []
978 matches = []
983 for rev in revs:
979 for rev in revs:
984 target = getinfo(rev)
980 target = getinfo(rev)
985 matches += [r for r in subset if getinfo(r) == target]
981 for r in subset:
982 match = True
983 for n, f in enumerate(getfieldfuncs):
984 if target[n] != f(r):
985 match = False
986 break
987 if match:
988 matches.append(r)
986 if len(revs) > 1:
989 if len(revs) > 1:
987 matches = sorted(set(matches))
990 matches = sorted(set(matches))
988 return matches
991 return matches
989
992
990 def reverse(repo, subset, x):
993 def reverse(repo, subset, x):
991 """``reverse(set)``
994 """``reverse(set)``
992 Reverse order of set.
995 Reverse order of set.
993 """
996 """
994 l = getset(repo, subset, x)
997 l = getset(repo, subset, x)
995 l.reverse()
998 l.reverse()
996 return l
999 return l
997
1000
998 def roots(repo, subset, x):
1001 def roots(repo, subset, x):
999 """``roots(set)``
1002 """``roots(set)``
1000 Changesets in set with no parent changeset in set.
1003 Changesets in set with no parent changeset in set.
1001 """
1004 """
1002 s = set(getset(repo, xrange(len(repo)), x))
1005 s = set(getset(repo, xrange(len(repo)), x))
1003 subset = [r for r in subset if r in s]
1006 subset = [r for r in subset if r in s]
1004 cs = _children(repo, subset, s)
1007 cs = _children(repo, subset, s)
1005 return [r for r in subset if r not in cs]
1008 return [r for r in subset if r not in cs]
1006
1009
1007 def secret(repo, subset, x):
1010 def secret(repo, subset, x):
1008 """``secret()``
1011 """``secret()``
1009 Changeset in secret phase."""
1012 Changeset in secret phase."""
1010 getargs(x, 0, 0, _("secret takes no arguments"))
1013 getargs(x, 0, 0, _("secret takes no arguments"))
1011 return [r for r in subset if repo._phaserev[r] == phases.secret]
1014 return [r for r in subset if repo._phaserev[r] == phases.secret]
1012
1015
1013 def sort(repo, subset, x):
1016 def sort(repo, subset, x):
1014 """``sort(set[, [-]key...])``
1017 """``sort(set[, [-]key...])``
1015 Sort set by keys. The default sort order is ascending, specify a key
1018 Sort set by keys. The default sort order is ascending, specify a key
1016 as ``-key`` to sort in descending order.
1019 as ``-key`` to sort in descending order.
1017
1020
1018 The keys can be:
1021 The keys can be:
1019
1022
1020 - ``rev`` for the revision number,
1023 - ``rev`` for the revision number,
1021 - ``branch`` for the branch name,
1024 - ``branch`` for the branch name,
1022 - ``desc`` for the commit message (description),
1025 - ``desc`` for the commit message (description),
1023 - ``user`` for user name (``author`` can be used as an alias),
1026 - ``user`` for user name (``author`` can be used as an alias),
1024 - ``date`` for the commit date
1027 - ``date`` for the commit date
1025 """
1028 """
1026 # i18n: "sort" is a keyword
1029 # i18n: "sort" is a keyword
1027 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1030 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1028 keys = "rev"
1031 keys = "rev"
1029 if len(l) == 2:
1032 if len(l) == 2:
1030 keys = getstring(l[1], _("sort spec must be a string"))
1033 keys = getstring(l[1], _("sort spec must be a string"))
1031
1034
1032 s = l[0]
1035 s = l[0]
1033 keys = keys.split()
1036 keys = keys.split()
1034 l = []
1037 l = []
1035 def invert(s):
1038 def invert(s):
1036 return "".join(chr(255 - ord(c)) for c in s)
1039 return "".join(chr(255 - ord(c)) for c in s)
1037 for r in getset(repo, subset, s):
1040 for r in getset(repo, subset, s):
1038 c = repo[r]
1041 c = repo[r]
1039 e = []
1042 e = []
1040 for k in keys:
1043 for k in keys:
1041 if k == 'rev':
1044 if k == 'rev':
1042 e.append(r)
1045 e.append(r)
1043 elif k == '-rev':
1046 elif k == '-rev':
1044 e.append(-r)
1047 e.append(-r)
1045 elif k == 'branch':
1048 elif k == 'branch':
1046 e.append(c.branch())
1049 e.append(c.branch())
1047 elif k == '-branch':
1050 elif k == '-branch':
1048 e.append(invert(c.branch()))
1051 e.append(invert(c.branch()))
1049 elif k == 'desc':
1052 elif k == 'desc':
1050 e.append(c.description())
1053 e.append(c.description())
1051 elif k == '-desc':
1054 elif k == '-desc':
1052 e.append(invert(c.description()))
1055 e.append(invert(c.description()))
1053 elif k in 'user author':
1056 elif k in 'user author':
1054 e.append(c.user())
1057 e.append(c.user())
1055 elif k in '-user -author':
1058 elif k in '-user -author':
1056 e.append(invert(c.user()))
1059 e.append(invert(c.user()))
1057 elif k == 'date':
1060 elif k == 'date':
1058 e.append(c.date()[0])
1061 e.append(c.date()[0])
1059 elif k == '-date':
1062 elif k == '-date':
1060 e.append(-c.date()[0])
1063 e.append(-c.date()[0])
1061 else:
1064 else:
1062 raise error.ParseError(_("unknown sort key %r") % k)
1065 raise error.ParseError(_("unknown sort key %r") % k)
1063 e.append(r)
1066 e.append(r)
1064 l.append(e)
1067 l.append(e)
1065 l.sort()
1068 l.sort()
1066 return [e[-1] for e in l]
1069 return [e[-1] for e in l]
1067
1070
1068 def tag(repo, subset, x):
1071 def tag(repo, subset, x):
1069 """``tag([name])``
1072 """``tag([name])``
1070 The specified tag by name, or all tagged revisions if no name is given.
1073 The specified tag by name, or all tagged revisions if no name is given.
1071 """
1074 """
1072 # i18n: "tag" is a keyword
1075 # i18n: "tag" is a keyword
1073 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1076 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1074 cl = repo.changelog
1077 cl = repo.changelog
1075 if args:
1078 if args:
1076 tn = getstring(args[0],
1079 tn = getstring(args[0],
1077 # i18n: "tag" is a keyword
1080 # i18n: "tag" is a keyword
1078 _('the argument to tag must be a string'))
1081 _('the argument to tag must be a string'))
1079 if not repo.tags().get(tn, None):
1082 if not repo.tags().get(tn, None):
1080 raise util.Abort(_("tag '%s' does not exist") % tn)
1083 raise util.Abort(_("tag '%s' does not exist") % tn)
1081 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
1084 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
1082 else:
1085 else:
1083 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1086 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1084 return [r for r in subset if r in s]
1087 return [r for r in subset if r in s]
1085
1088
1086 def tagged(repo, subset, x):
1089 def tagged(repo, subset, x):
1087 return tag(repo, subset, x)
1090 return tag(repo, subset, x)
1088
1091
1089 def user(repo, subset, x):
1092 def user(repo, subset, x):
1090 """``user(string)``
1093 """``user(string)``
1091 User name contains string. The match is case-insensitive.
1094 User name contains string. The match is case-insensitive.
1092 """
1095 """
1093 return author(repo, subset, x)
1096 return author(repo, subset, x)
1094
1097
1095 # for internal use
1098 # for internal use
1096 def _list(repo, subset, x):
1099 def _list(repo, subset, x):
1097 s = getstring(x, "internal error")
1100 s = getstring(x, "internal error")
1098 if not s:
1101 if not s:
1099 return []
1102 return []
1100 if not isinstance(subset, set):
1103 if not isinstance(subset, set):
1101 subset = set(subset)
1104 subset = set(subset)
1102 ls = [repo[r].rev() for r in s.split('\0')]
1105 ls = [repo[r].rev() for r in s.split('\0')]
1103 return [r for r in ls if r in subset]
1106 return [r for r in ls if r in subset]
1104
1107
1105 symbols = {
1108 symbols = {
1106 "adds": adds,
1109 "adds": adds,
1107 "all": getall,
1110 "all": getall,
1108 "ancestor": ancestor,
1111 "ancestor": ancestor,
1109 "ancestors": ancestors,
1112 "ancestors": ancestors,
1110 "_firstancestors": _firstancestors,
1113 "_firstancestors": _firstancestors,
1111 "author": author,
1114 "author": author,
1112 "bisect": bisect,
1115 "bisect": bisect,
1113 "bisected": bisected,
1116 "bisected": bisected,
1114 "bookmark": bookmark,
1117 "bookmark": bookmark,
1115 "branch": branch,
1118 "branch": branch,
1116 "children": children,
1119 "children": children,
1117 "closed": closed,
1120 "closed": closed,
1118 "contains": contains,
1121 "contains": contains,
1119 "date": date,
1122 "date": date,
1120 "desc": desc,
1123 "desc": desc,
1121 "descendants": descendants,
1124 "descendants": descendants,
1122 "_firstdescendants": _firstdescendants,
1125 "_firstdescendants": _firstdescendants,
1123 "draft": draft,
1126 "draft": draft,
1124 "file": hasfile,
1127 "file": hasfile,
1125 "filelog": filelog,
1128 "filelog": filelog,
1126 "first": first,
1129 "first": first,
1127 "follow": follow,
1130 "follow": follow,
1128 "_followfirst": _followfirst,
1131 "_followfirst": _followfirst,
1129 "grep": grep,
1132 "grep": grep,
1130 "head": head,
1133 "head": head,
1131 "heads": heads,
1134 "heads": heads,
1132 "id": node_,
1135 "id": node_,
1133 "keyword": keyword,
1136 "keyword": keyword,
1134 "last": last,
1137 "last": last,
1135 "limit": limit,
1138 "limit": limit,
1136 "_matchfiles": _matchfiles,
1139 "_matchfiles": _matchfiles,
1137 "max": maxrev,
1140 "max": maxrev,
1138 "merge": merge,
1141 "merge": merge,
1139 "min": minrev,
1142 "min": minrev,
1140 "modifies": modifies,
1143 "modifies": modifies,
1141 "outgoing": outgoing,
1144 "outgoing": outgoing,
1142 "p1": p1,
1145 "p1": p1,
1143 "p2": p2,
1146 "p2": p2,
1144 "parents": parents,
1147 "parents": parents,
1145 "present": present,
1148 "present": present,
1146 "public": public,
1149 "public": public,
1147 "remote": remote,
1150 "remote": remote,
1148 "removes": removes,
1151 "removes": removes,
1149 "rev": rev,
1152 "rev": rev,
1150 "reverse": reverse,
1153 "reverse": reverse,
1151 "roots": roots,
1154 "roots": roots,
1152 "sort": sort,
1155 "sort": sort,
1153 "secret": secret,
1156 "secret": secret,
1154 "matching": matching,
1157 "matching": matching,
1155 "tag": tag,
1158 "tag": tag,
1156 "tagged": tagged,
1159 "tagged": tagged,
1157 "user": user,
1160 "user": user,
1158 "_list": _list,
1161 "_list": _list,
1159 }
1162 }
1160
1163
1161 methods = {
1164 methods = {
1162 "range": rangeset,
1165 "range": rangeset,
1163 "string": stringset,
1166 "string": stringset,
1164 "symbol": symbolset,
1167 "symbol": symbolset,
1165 "and": andset,
1168 "and": andset,
1166 "or": orset,
1169 "or": orset,
1167 "not": notset,
1170 "not": notset,
1168 "list": listset,
1171 "list": listset,
1169 "func": func,
1172 "func": func,
1170 "ancestor": ancestorspec,
1173 "ancestor": ancestorspec,
1171 "parent": parentspec,
1174 "parent": parentspec,
1172 "parentpost": p1,
1175 "parentpost": p1,
1173 }
1176 }
1174
1177
1175 def optimize(x, small):
1178 def optimize(x, small):
1176 if x is None:
1179 if x is None:
1177 return 0, x
1180 return 0, x
1178
1181
1179 smallbonus = 1
1182 smallbonus = 1
1180 if small:
1183 if small:
1181 smallbonus = .5
1184 smallbonus = .5
1182
1185
1183 op = x[0]
1186 op = x[0]
1184 if op == 'minus':
1187 if op == 'minus':
1185 return optimize(('and', x[1], ('not', x[2])), small)
1188 return optimize(('and', x[1], ('not', x[2])), small)
1186 elif op == 'dagrange':
1189 elif op == 'dagrange':
1187 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1190 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1188 ('func', ('symbol', 'ancestors'), x[2])), small)
1191 ('func', ('symbol', 'ancestors'), x[2])), small)
1189 elif op == 'dagrangepre':
1192 elif op == 'dagrangepre':
1190 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1193 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1191 elif op == 'dagrangepost':
1194 elif op == 'dagrangepost':
1192 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1195 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1193 elif op == 'rangepre':
1196 elif op == 'rangepre':
1194 return optimize(('range', ('string', '0'), x[1]), small)
1197 return optimize(('range', ('string', '0'), x[1]), small)
1195 elif op == 'rangepost':
1198 elif op == 'rangepost':
1196 return optimize(('range', x[1], ('string', 'tip')), small)
1199 return optimize(('range', x[1], ('string', 'tip')), small)
1197 elif op == 'negate':
1200 elif op == 'negate':
1198 return optimize(('string',
1201 return optimize(('string',
1199 '-' + getstring(x[1], _("can't negate that"))), small)
1202 '-' + getstring(x[1], _("can't negate that"))), small)
1200 elif op in 'string symbol negate':
1203 elif op in 'string symbol negate':
1201 return smallbonus, x # single revisions are small
1204 return smallbonus, x # single revisions are small
1202 elif op == 'and' or op == 'dagrange':
1205 elif op == 'and' or op == 'dagrange':
1203 wa, ta = optimize(x[1], True)
1206 wa, ta = optimize(x[1], True)
1204 wb, tb = optimize(x[2], True)
1207 wb, tb = optimize(x[2], True)
1205 w = min(wa, wb)
1208 w = min(wa, wb)
1206 if wa > wb:
1209 if wa > wb:
1207 return w, (op, tb, ta)
1210 return w, (op, tb, ta)
1208 return w, (op, ta, tb)
1211 return w, (op, ta, tb)
1209 elif op == 'or':
1212 elif op == 'or':
1210 wa, ta = optimize(x[1], False)
1213 wa, ta = optimize(x[1], False)
1211 wb, tb = optimize(x[2], False)
1214 wb, tb = optimize(x[2], False)
1212 if wb < wa:
1215 if wb < wa:
1213 wb, wa = wa, wb
1216 wb, wa = wa, wb
1214 return max(wa, wb), (op, ta, tb)
1217 return max(wa, wb), (op, ta, tb)
1215 elif op == 'not':
1218 elif op == 'not':
1216 o = optimize(x[1], not small)
1219 o = optimize(x[1], not small)
1217 return o[0], (op, o[1])
1220 return o[0], (op, o[1])
1218 elif op == 'parentpost':
1221 elif op == 'parentpost':
1219 o = optimize(x[1], small)
1222 o = optimize(x[1], small)
1220 return o[0], (op, o[1])
1223 return o[0], (op, o[1])
1221 elif op == 'group':
1224 elif op == 'group':
1222 return optimize(x[1], small)
1225 return optimize(x[1], small)
1223 elif op in 'range list parent ancestorspec':
1226 elif op in 'range list parent ancestorspec':
1224 if op == 'parent':
1227 if op == 'parent':
1225 # x^:y means (x^) : y, not x ^ (:y)
1228 # x^:y means (x^) : y, not x ^ (:y)
1226 post = ('parentpost', x[1])
1229 post = ('parentpost', x[1])
1227 if x[2][0] == 'dagrangepre':
1230 if x[2][0] == 'dagrangepre':
1228 return optimize(('dagrange', post, x[2][1]), small)
1231 return optimize(('dagrange', post, x[2][1]), small)
1229 elif x[2][0] == 'rangepre':
1232 elif x[2][0] == 'rangepre':
1230 return optimize(('range', post, x[2][1]), small)
1233 return optimize(('range', post, x[2][1]), small)
1231
1234
1232 wa, ta = optimize(x[1], small)
1235 wa, ta = optimize(x[1], small)
1233 wb, tb = optimize(x[2], small)
1236 wb, tb = optimize(x[2], small)
1234 return wa + wb, (op, ta, tb)
1237 return wa + wb, (op, ta, tb)
1235 elif op == 'func':
1238 elif op == 'func':
1236 f = getstring(x[1], _("not a symbol"))
1239 f = getstring(x[1], _("not a symbol"))
1237 wa, ta = optimize(x[2], small)
1240 wa, ta = optimize(x[2], small)
1238 if f in ("author branch closed date desc file grep keyword "
1241 if f in ("author branch closed date desc file grep keyword "
1239 "outgoing user"):
1242 "outgoing user"):
1240 w = 10 # slow
1243 w = 10 # slow
1241 elif f in "modifies adds removes":
1244 elif f in "modifies adds removes":
1242 w = 30 # slower
1245 w = 30 # slower
1243 elif f == "contains":
1246 elif f == "contains":
1244 w = 100 # very slow
1247 w = 100 # very slow
1245 elif f == "ancestor":
1248 elif f == "ancestor":
1246 w = 1 * smallbonus
1249 w = 1 * smallbonus
1247 elif f in "reverse limit first":
1250 elif f in "reverse limit first":
1248 w = 0
1251 w = 0
1249 elif f in "sort":
1252 elif f in "sort":
1250 w = 10 # assume most sorts look at changelog
1253 w = 10 # assume most sorts look at changelog
1251 else:
1254 else:
1252 w = 1
1255 w = 1
1253 return w + wa, (op, x[1], ta)
1256 return w + wa, (op, x[1], ta)
1254 return 1, x
1257 return 1, x
1255
1258
1256 class revsetalias(object):
1259 class revsetalias(object):
1257 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1260 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1258 args = None
1261 args = None
1259
1262
1260 def __init__(self, name, value):
1263 def __init__(self, name, value):
1261 '''Aliases like:
1264 '''Aliases like:
1262
1265
1263 h = heads(default)
1266 h = heads(default)
1264 b($1) = ancestors($1) - ancestors(default)
1267 b($1) = ancestors($1) - ancestors(default)
1265 '''
1268 '''
1266 m = self.funcre.search(name)
1269 m = self.funcre.search(name)
1267 if m:
1270 if m:
1268 self.name = m.group(1)
1271 self.name = m.group(1)
1269 self.tree = ('func', ('symbol', m.group(1)))
1272 self.tree = ('func', ('symbol', m.group(1)))
1270 self.args = [x.strip() for x in m.group(2).split(',')]
1273 self.args = [x.strip() for x in m.group(2).split(',')]
1271 for arg in self.args:
1274 for arg in self.args:
1272 value = value.replace(arg, repr(arg))
1275 value = value.replace(arg, repr(arg))
1273 else:
1276 else:
1274 self.name = name
1277 self.name = name
1275 self.tree = ('symbol', name)
1278 self.tree = ('symbol', name)
1276
1279
1277 self.replacement, pos = parse(value)
1280 self.replacement, pos = parse(value)
1278 if pos != len(value):
1281 if pos != len(value):
1279 raise error.ParseError(_('invalid token'), pos)
1282 raise error.ParseError(_('invalid token'), pos)
1280
1283
1281 def _getalias(aliases, tree):
1284 def _getalias(aliases, tree):
1282 """If tree looks like an unexpanded alias, return it. Return None
1285 """If tree looks like an unexpanded alias, return it. Return None
1283 otherwise.
1286 otherwise.
1284 """
1287 """
1285 if isinstance(tree, tuple) and tree:
1288 if isinstance(tree, tuple) and tree:
1286 if tree[0] == 'symbol' and len(tree) == 2:
1289 if tree[0] == 'symbol' and len(tree) == 2:
1287 name = tree[1]
1290 name = tree[1]
1288 alias = aliases.get(name)
1291 alias = aliases.get(name)
1289 if alias and alias.args is None and alias.tree == tree:
1292 if alias and alias.args is None and alias.tree == tree:
1290 return alias
1293 return alias
1291 if tree[0] == 'func' and len(tree) > 1:
1294 if tree[0] == 'func' and len(tree) > 1:
1292 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1295 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1293 name = tree[1][1]
1296 name = tree[1][1]
1294 alias = aliases.get(name)
1297 alias = aliases.get(name)
1295 if alias and alias.args is not None and alias.tree == tree[:2]:
1298 if alias and alias.args is not None and alias.tree == tree[:2]:
1296 return alias
1299 return alias
1297 return None
1300 return None
1298
1301
1299 def _expandargs(tree, args):
1302 def _expandargs(tree, args):
1300 """Replace all occurences of ('string', name) with the
1303 """Replace all occurences of ('string', name) with the
1301 substitution value of the same name in args, recursively.
1304 substitution value of the same name in args, recursively.
1302 """
1305 """
1303 if not isinstance(tree, tuple):
1306 if not isinstance(tree, tuple):
1304 return tree
1307 return tree
1305 if len(tree) == 2 and tree[0] == 'string':
1308 if len(tree) == 2 and tree[0] == 'string':
1306 return args.get(tree[1], tree)
1309 return args.get(tree[1], tree)
1307 return tuple(_expandargs(t, args) for t in tree)
1310 return tuple(_expandargs(t, args) for t in tree)
1308
1311
1309 def _expandaliases(aliases, tree, expanding):
1312 def _expandaliases(aliases, tree, expanding):
1310 """Expand aliases in tree, recursively.
1313 """Expand aliases in tree, recursively.
1311
1314
1312 'aliases' is a dictionary mapping user defined aliases to
1315 'aliases' is a dictionary mapping user defined aliases to
1313 revsetalias objects.
1316 revsetalias objects.
1314 """
1317 """
1315 if not isinstance(tree, tuple):
1318 if not isinstance(tree, tuple):
1316 # Do not expand raw strings
1319 # Do not expand raw strings
1317 return tree
1320 return tree
1318 alias = _getalias(aliases, tree)
1321 alias = _getalias(aliases, tree)
1319 if alias is not None:
1322 if alias is not None:
1320 if alias in expanding:
1323 if alias in expanding:
1321 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1324 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1322 'detected') % alias.name)
1325 'detected') % alias.name)
1323 expanding.append(alias)
1326 expanding.append(alias)
1324 result = alias.replacement
1327 result = alias.replacement
1325 if alias.args is not None:
1328 if alias.args is not None:
1326 l = getlist(tree[2])
1329 l = getlist(tree[2])
1327 if len(l) != len(alias.args):
1330 if len(l) != len(alias.args):
1328 raise error.ParseError(
1331 raise error.ParseError(
1329 _('invalid number of arguments: %s') % len(l))
1332 _('invalid number of arguments: %s') % len(l))
1330 result = _expandargs(result, dict(zip(alias.args, l)))
1333 result = _expandargs(result, dict(zip(alias.args, l)))
1331 # Recurse in place, the base expression may have been rewritten
1334 # Recurse in place, the base expression may have been rewritten
1332 result = _expandaliases(aliases, result, expanding)
1335 result = _expandaliases(aliases, result, expanding)
1333 expanding.pop()
1336 expanding.pop()
1334 else:
1337 else:
1335 result = tuple(_expandaliases(aliases, t, expanding)
1338 result = tuple(_expandaliases(aliases, t, expanding)
1336 for t in tree)
1339 for t in tree)
1337 return result
1340 return result
1338
1341
1339 def findaliases(ui, tree):
1342 def findaliases(ui, tree):
1340 aliases = {}
1343 aliases = {}
1341 for k, v in ui.configitems('revsetalias'):
1344 for k, v in ui.configitems('revsetalias'):
1342 alias = revsetalias(k, v)
1345 alias = revsetalias(k, v)
1343 aliases[alias.name] = alias
1346 aliases[alias.name] = alias
1344 return _expandaliases(aliases, tree, [])
1347 return _expandaliases(aliases, tree, [])
1345
1348
1346 parse = parser.parser(tokenize, elements).parse
1349 parse = parser.parser(tokenize, elements).parse
1347
1350
1348 def match(ui, spec):
1351 def match(ui, spec):
1349 if not spec:
1352 if not spec:
1350 raise error.ParseError(_("empty query"))
1353 raise error.ParseError(_("empty query"))
1351 tree, pos = parse(spec)
1354 tree, pos = parse(spec)
1352 if (pos != len(spec)):
1355 if (pos != len(spec)):
1353 raise error.ParseError(_("invalid token"), pos)
1356 raise error.ParseError(_("invalid token"), pos)
1354 if ui:
1357 if ui:
1355 tree = findaliases(ui, tree)
1358 tree = findaliases(ui, tree)
1356 weight, tree = optimize(tree, True)
1359 weight, tree = optimize(tree, True)
1357 def mfunc(repo, subset):
1360 def mfunc(repo, subset):
1358 return getset(repo, subset, tree)
1361 return getset(repo, subset, tree)
1359 return mfunc
1362 return mfunc
1360
1363
1361 def formatspec(expr, *args):
1364 def formatspec(expr, *args):
1362 '''
1365 '''
1363 This is a convenience function for using revsets internally, and
1366 This is a convenience function for using revsets internally, and
1364 escapes arguments appropriately. Aliases are intentionally ignored
1367 escapes arguments appropriately. Aliases are intentionally ignored
1365 so that intended expression behavior isn't accidentally subverted.
1368 so that intended expression behavior isn't accidentally subverted.
1366
1369
1367 Supported arguments:
1370 Supported arguments:
1368
1371
1369 %r = revset expression, parenthesized
1372 %r = revset expression, parenthesized
1370 %d = int(arg), no quoting
1373 %d = int(arg), no quoting
1371 %s = string(arg), escaped and single-quoted
1374 %s = string(arg), escaped and single-quoted
1372 %b = arg.branch(), escaped and single-quoted
1375 %b = arg.branch(), escaped and single-quoted
1373 %n = hex(arg), single-quoted
1376 %n = hex(arg), single-quoted
1374 %% = a literal '%'
1377 %% = a literal '%'
1375
1378
1376 Prefixing the type with 'l' specifies a parenthesized list of that type.
1379 Prefixing the type with 'l' specifies a parenthesized list of that type.
1377
1380
1378 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1381 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1379 '(10 or 11):: and ((this()) or (that()))'
1382 '(10 or 11):: and ((this()) or (that()))'
1380 >>> formatspec('%d:: and not %d::', 10, 20)
1383 >>> formatspec('%d:: and not %d::', 10, 20)
1381 '10:: and not 20::'
1384 '10:: and not 20::'
1382 >>> formatspec('%ld or %ld', [], [1])
1385 >>> formatspec('%ld or %ld', [], [1])
1383 "_list('') or 1"
1386 "_list('') or 1"
1384 >>> formatspec('keyword(%s)', 'foo\\xe9')
1387 >>> formatspec('keyword(%s)', 'foo\\xe9')
1385 "keyword('foo\\\\xe9')"
1388 "keyword('foo\\\\xe9')"
1386 >>> b = lambda: 'default'
1389 >>> b = lambda: 'default'
1387 >>> b.branch = b
1390 >>> b.branch = b
1388 >>> formatspec('branch(%b)', b)
1391 >>> formatspec('branch(%b)', b)
1389 "branch('default')"
1392 "branch('default')"
1390 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1393 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1391 "root(_list('a\\x00b\\x00c\\x00d'))"
1394 "root(_list('a\\x00b\\x00c\\x00d'))"
1392 '''
1395 '''
1393
1396
1394 def quote(s):
1397 def quote(s):
1395 return repr(str(s))
1398 return repr(str(s))
1396
1399
1397 def argtype(c, arg):
1400 def argtype(c, arg):
1398 if c == 'd':
1401 if c == 'd':
1399 return str(int(arg))
1402 return str(int(arg))
1400 elif c == 's':
1403 elif c == 's':
1401 return quote(arg)
1404 return quote(arg)
1402 elif c == 'r':
1405 elif c == 'r':
1403 parse(arg) # make sure syntax errors are confined
1406 parse(arg) # make sure syntax errors are confined
1404 return '(%s)' % arg
1407 return '(%s)' % arg
1405 elif c == 'n':
1408 elif c == 'n':
1406 return quote(node.hex(arg))
1409 return quote(node.hex(arg))
1407 elif c == 'b':
1410 elif c == 'b':
1408 return quote(arg.branch())
1411 return quote(arg.branch())
1409
1412
1410 def listexp(s, t):
1413 def listexp(s, t):
1411 l = len(s)
1414 l = len(s)
1412 if l == 0:
1415 if l == 0:
1413 return "_list('')"
1416 return "_list('')"
1414 elif l == 1:
1417 elif l == 1:
1415 return argtype(t, s[0])
1418 return argtype(t, s[0])
1416 elif t == 'd':
1419 elif t == 'd':
1417 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1420 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1418 elif t == 's':
1421 elif t == 's':
1419 return "_list('%s')" % "\0".join(s)
1422 return "_list('%s')" % "\0".join(s)
1420 elif t == 'n':
1423 elif t == 'n':
1421 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1424 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1422 elif t == 'b':
1425 elif t == 'b':
1423 return "_list('%s')" % "\0".join(a.branch() for a in s)
1426 return "_list('%s')" % "\0".join(a.branch() for a in s)
1424
1427
1425 m = l // 2
1428 m = l // 2
1426 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1429 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1427
1430
1428 ret = ''
1431 ret = ''
1429 pos = 0
1432 pos = 0
1430 arg = 0
1433 arg = 0
1431 while pos < len(expr):
1434 while pos < len(expr):
1432 c = expr[pos]
1435 c = expr[pos]
1433 if c == '%':
1436 if c == '%':
1434 pos += 1
1437 pos += 1
1435 d = expr[pos]
1438 d = expr[pos]
1436 if d == '%':
1439 if d == '%':
1437 ret += d
1440 ret += d
1438 elif d in 'dsnbr':
1441 elif d in 'dsnbr':
1439 ret += argtype(d, args[arg])
1442 ret += argtype(d, args[arg])
1440 arg += 1
1443 arg += 1
1441 elif d == 'l':
1444 elif d == 'l':
1442 # a list of some type
1445 # a list of some type
1443 pos += 1
1446 pos += 1
1444 d = expr[pos]
1447 d = expr[pos]
1445 ret += listexp(list(args[arg]), d)
1448 ret += listexp(list(args[arg]), d)
1446 arg += 1
1449 arg += 1
1447 else:
1450 else:
1448 raise util.Abort('unexpected revspec format character %s' % d)
1451 raise util.Abort('unexpected revspec format character %s' % d)
1449 else:
1452 else:
1450 ret += c
1453 ret += c
1451 pos += 1
1454 pos += 1
1452
1455
1453 return ret
1456 return ret
1454
1457
1455 def prettyformat(tree):
1458 def prettyformat(tree):
1456 def _prettyformat(tree, level, lines):
1459 def _prettyformat(tree, level, lines):
1457 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1460 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1458 lines.append((level, str(tree)))
1461 lines.append((level, str(tree)))
1459 else:
1462 else:
1460 lines.append((level, '(%s' % tree[0]))
1463 lines.append((level, '(%s' % tree[0]))
1461 for s in tree[1:]:
1464 for s in tree[1:]:
1462 _prettyformat(s, level + 1, lines)
1465 _prettyformat(s, level + 1, lines)
1463 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1466 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1464
1467
1465 lines = []
1468 lines = []
1466 _prettyformat(tree, 0, lines)
1469 _prettyformat(tree, 0, lines)
1467 output = '\n'.join((' '*l + s) for l, s in lines)
1470 output = '\n'.join((' '*l + s) for l, s in lines)
1468 return output
1471 return output
1469
1472
1470 # tell hggettext to extract docstrings from these functions:
1473 # tell hggettext to extract docstrings from these functions:
1471 i18nfunctions = symbols.values()
1474 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now