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