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