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