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