##// END OF EJS Templates
revset: include the correct first ancestor change for follow(file)...
Matt Mackall -
r15966:610c4434 stable
parent child Browse files
Show More
@@ -1,1222 +1,1225
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, s):
327 cs = set()
327 cs = set()
328 pr = repo.changelog.parentrevs
328 pr = repo.changelog.parentrevs
329 s = set(s)
329 s = set(s)
330 for r in xrange(len(repo)):
330 for r in xrange(len(repo)):
331 for p in pr(r):
331 for p in pr(r):
332 if p in s:
332 if p in s:
333 cs.add(r)
333 cs.add(r)
334 return cs
334 return cs
335
335
336 def children(repo, subset, x):
336 def children(repo, subset, x):
337 """``children(set)``
337 """``children(set)``
338 Child changesets of changesets in set.
338 Child changesets of changesets in set.
339 """
339 """
340 s = getset(repo, range(len(repo)), x)
340 s = getset(repo, range(len(repo)), x)
341 cs = _children(repo, subset, s)
341 cs = _children(repo, subset, s)
342 return [r for r in subset if r in cs]
342 return [r for r in subset if r in cs]
343
343
344 def closed(repo, subset, x):
344 def closed(repo, subset, x):
345 """``closed()``
345 """``closed()``
346 Changeset is closed.
346 Changeset is closed.
347 """
347 """
348 # i18n: "closed" is a keyword
348 # i18n: "closed" is a keyword
349 getargs(x, 0, 0, _("closed takes no arguments"))
349 getargs(x, 0, 0, _("closed takes no arguments"))
350 return [r for r in subset if repo[r].extra().get('close')]
350 return [r for r in subset if repo[r].extra().get('close')]
351
351
352 def contains(repo, subset, x):
352 def contains(repo, subset, x):
353 """``contains(pattern)``
353 """``contains(pattern)``
354 Revision contains a file matching pattern. See :hg:`help patterns`
354 Revision contains a file matching pattern. See :hg:`help patterns`
355 for information about file patterns.
355 for information about file patterns.
356 """
356 """
357 # i18n: "contains" is a keyword
357 # i18n: "contains" is a keyword
358 pat = getstring(x, _("contains requires a pattern"))
358 pat = getstring(x, _("contains requires a pattern"))
359 m = None
359 m = None
360 s = []
360 s = []
361 if not matchmod.patkind(pat):
361 if not matchmod.patkind(pat):
362 for r in subset:
362 for r in subset:
363 if pat in repo[r]:
363 if pat in repo[r]:
364 s.append(r)
364 s.append(r)
365 else:
365 else:
366 for r in subset:
366 for r in subset:
367 c = repo[r]
367 c = repo[r]
368 if not m or matchmod.patkind(pat) == 'set':
368 if not m or matchmod.patkind(pat) == 'set':
369 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
369 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
370 for f in c.manifest():
370 for f in c.manifest():
371 if m(f):
371 if m(f):
372 s.append(r)
372 s.append(r)
373 break
373 break
374 return s
374 return s
375
375
376 def date(repo, subset, x):
376 def date(repo, subset, x):
377 """``date(interval)``
377 """``date(interval)``
378 Changesets within the interval, see :hg:`help dates`.
378 Changesets within the interval, see :hg:`help dates`.
379 """
379 """
380 # i18n: "date" is a keyword
380 # i18n: "date" is a keyword
381 ds = getstring(x, _("date requires a string"))
381 ds = getstring(x, _("date requires a string"))
382 dm = util.matchdate(ds)
382 dm = util.matchdate(ds)
383 return [r for r in subset if dm(repo[r].date()[0])]
383 return [r for r in subset if dm(repo[r].date()[0])]
384
384
385 def desc(repo, subset, x):
385 def desc(repo, subset, x):
386 """``desc(string)``
386 """``desc(string)``
387 Search commit message for string. The match is case-insensitive.
387 Search commit message for string. The match is case-insensitive.
388 """
388 """
389 # i18n: "desc" is a keyword
389 # i18n: "desc" is a keyword
390 ds = encoding.lower(getstring(x, _("desc requires a string")))
390 ds = encoding.lower(getstring(x, _("desc requires a string")))
391 l = []
391 l = []
392 for r in subset:
392 for r in subset:
393 c = repo[r]
393 c = repo[r]
394 if ds in encoding.lower(c.description()):
394 if ds in encoding.lower(c.description()):
395 l.append(r)
395 l.append(r)
396 return l
396 return l
397
397
398 def descendants(repo, subset, x):
398 def descendants(repo, subset, x):
399 """``descendants(set)``
399 """``descendants(set)``
400 Changesets which are descendants of changesets in set.
400 Changesets which are descendants of changesets in set.
401 """
401 """
402 args = getset(repo, range(len(repo)), x)
402 args = getset(repo, range(len(repo)), x)
403 if not args:
403 if not args:
404 return []
404 return []
405 s = set(repo.changelog.descendants(*args)) | set(args)
405 s = set(repo.changelog.descendants(*args)) | set(args)
406 return [r for r in subset if r in s]
406 return [r for r in subset if r in s]
407
407
408 def draft(repo, subset, x):
408 def draft(repo, subset, x):
409 """``draft()``
409 """``draft()``
410 Changeset in draft phase."""
410 Changeset in draft phase."""
411 getargs(x, 0, 0, _("draft takes no arguments"))
411 getargs(x, 0, 0, _("draft takes no arguments"))
412 return [r for r in subset if repo._phaserev[r] == phases.draft]
412 return [r for r in subset if repo._phaserev[r] == phases.draft]
413
413
414 def filelog(repo, subset, x):
414 def filelog(repo, subset, x):
415 """``filelog(pattern)``
415 """``filelog(pattern)``
416 Changesets connected to the specified filelog.
416 Changesets connected to the specified filelog.
417 """
417 """
418
418
419 pat = getstring(x, _("filelog requires a pattern"))
419 pat = getstring(x, _("filelog requires a pattern"))
420 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
420 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
421 ctx=repo[None])
421 ctx=repo[None])
422 s = set()
422 s = set()
423
423
424 if not matchmod.patkind(pat):
424 if not matchmod.patkind(pat):
425 for f in m.files():
425 for f in m.files():
426 fl = repo.file(f)
426 fl = repo.file(f)
427 for fr in fl:
427 for fr in fl:
428 s.add(fl.linkrev(fr))
428 s.add(fl.linkrev(fr))
429 else:
429 else:
430 for f in repo[None]:
430 for f in repo[None]:
431 if m(f):
431 if m(f):
432 fl = repo.file(f)
432 fl = repo.file(f)
433 for fr in fl:
433 for fr in fl:
434 s.add(fl.linkrev(fr))
434 s.add(fl.linkrev(fr))
435
435
436 return [r for r in subset if r in s]
436 return [r for r in subset if r in s]
437
437
438 def first(repo, subset, x):
438 def first(repo, subset, x):
439 """``first(set, [n])``
439 """``first(set, [n])``
440 An alias for limit().
440 An alias for limit().
441 """
441 """
442 return limit(repo, subset, x)
442 return limit(repo, subset, x)
443
443
444 def follow(repo, subset, x):
444 def follow(repo, subset, x):
445 """``follow([file])``
445 """``follow([file])``
446 An alias for ``::.`` (ancestors of the working copy's first parent).
446 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,
447 If a filename is specified, the history of the given file is followed,
448 including copies.
448 including copies.
449 """
449 """
450 # i18n: "follow" is a keyword
450 # i18n: "follow" is a keyword
451 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
451 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
452 p = repo['.'].rev()
452 c = repo['.']
453 if l:
453 if l:
454 x = getstring(l[0], _("follow expected a filename"))
454 x = getstring(l[0], _("follow expected a filename"))
455 if x in repo['.']:
455 if x in c:
456 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
456 cx = c[x]
457 s = set(ctx.rev() for ctx in cx.ancestors())
458 # include the revision responsible for the most recent version
459 s.add(cx.linkrev())
457 else:
460 else:
458 return []
461 return []
459 else:
462 else:
460 s = set(repo.changelog.ancestors(p))
463 s = set(repo.changelog.ancestors(c.rev()))
464 s.add(c.rev())
461
465
462 s |= set([p])
463 return [r for r in subset if r in s]
466 return [r for r in subset if r in s]
464
467
465 def getall(repo, subset, x):
468 def getall(repo, subset, x):
466 """``all()``
469 """``all()``
467 All changesets, the same as ``0:tip``.
470 All changesets, the same as ``0:tip``.
468 """
471 """
469 # i18n: "all" is a keyword
472 # i18n: "all" is a keyword
470 getargs(x, 0, 0, _("all takes no arguments"))
473 getargs(x, 0, 0, _("all takes no arguments"))
471 return subset
474 return subset
472
475
473 def grep(repo, subset, x):
476 def grep(repo, subset, x):
474 """``grep(regex)``
477 """``grep(regex)``
475 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
478 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
476 to ensure special escape characters are handled correctly. Unlike
479 to ensure special escape characters are handled correctly. Unlike
477 ``keyword(string)``, the match is case-sensitive.
480 ``keyword(string)``, the match is case-sensitive.
478 """
481 """
479 try:
482 try:
480 # i18n: "grep" is a keyword
483 # i18n: "grep" is a keyword
481 gr = re.compile(getstring(x, _("grep requires a string")))
484 gr = re.compile(getstring(x, _("grep requires a string")))
482 except re.error, e:
485 except re.error, e:
483 raise error.ParseError(_('invalid match pattern: %s') % e)
486 raise error.ParseError(_('invalid match pattern: %s') % e)
484 l = []
487 l = []
485 for r in subset:
488 for r in subset:
486 c = repo[r]
489 c = repo[r]
487 for e in c.files() + [c.user(), c.description()]:
490 for e in c.files() + [c.user(), c.description()]:
488 if gr.search(e):
491 if gr.search(e):
489 l.append(r)
492 l.append(r)
490 break
493 break
491 return l
494 return l
492
495
493 def hasfile(repo, subset, x):
496 def hasfile(repo, subset, x):
494 """``file(pattern)``
497 """``file(pattern)``
495 Changesets affecting files matched by pattern.
498 Changesets affecting files matched by pattern.
496 """
499 """
497 # i18n: "file" is a keyword
500 # i18n: "file" is a keyword
498 pat = getstring(x, _("file requires a pattern"))
501 pat = getstring(x, _("file requires a pattern"))
499 m = None
502 m = None
500 s = []
503 s = []
501 for r in subset:
504 for r in subset:
502 c = repo[r]
505 c = repo[r]
503 if not m or matchmod.patkind(pat) == 'set':
506 if not m or matchmod.patkind(pat) == 'set':
504 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
507 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
505 for f in c.files():
508 for f in c.files():
506 if m(f):
509 if m(f):
507 s.append(r)
510 s.append(r)
508 break
511 break
509 return s
512 return s
510
513
511 def head(repo, subset, x):
514 def head(repo, subset, x):
512 """``head()``
515 """``head()``
513 Changeset is a named branch head.
516 Changeset is a named branch head.
514 """
517 """
515 # i18n: "head" is a keyword
518 # i18n: "head" is a keyword
516 getargs(x, 0, 0, _("head takes no arguments"))
519 getargs(x, 0, 0, _("head takes no arguments"))
517 hs = set()
520 hs = set()
518 for b, ls in repo.branchmap().iteritems():
521 for b, ls in repo.branchmap().iteritems():
519 hs.update(repo[h].rev() for h in ls)
522 hs.update(repo[h].rev() for h in ls)
520 return [r for r in subset if r in hs]
523 return [r for r in subset if r in hs]
521
524
522 def heads(repo, subset, x):
525 def heads(repo, subset, x):
523 """``heads(set)``
526 """``heads(set)``
524 Members of set with no children in set.
527 Members of set with no children in set.
525 """
528 """
526 s = getset(repo, subset, x)
529 s = getset(repo, subset, x)
527 ps = set(parents(repo, subset, x))
530 ps = set(parents(repo, subset, x))
528 return [r for r in s if r not in ps]
531 return [r for r in s if r not in ps]
529
532
530 def keyword(repo, subset, x):
533 def keyword(repo, subset, x):
531 """``keyword(string)``
534 """``keyword(string)``
532 Search commit message, user name, and names of changed files for
535 Search commit message, user name, and names of changed files for
533 string. The match is case-insensitive.
536 string. The match is case-insensitive.
534 """
537 """
535 # i18n: "keyword" is a keyword
538 # i18n: "keyword" is a keyword
536 kw = encoding.lower(getstring(x, _("keyword requires a string")))
539 kw = encoding.lower(getstring(x, _("keyword requires a string")))
537 l = []
540 l = []
538 for r in subset:
541 for r in subset:
539 c = repo[r]
542 c = repo[r]
540 t = " ".join(c.files() + [c.user(), c.description()])
543 t = " ".join(c.files() + [c.user(), c.description()])
541 if kw in encoding.lower(t):
544 if kw in encoding.lower(t):
542 l.append(r)
545 l.append(r)
543 return l
546 return l
544
547
545 def limit(repo, subset, x):
548 def limit(repo, subset, x):
546 """``limit(set, [n])``
549 """``limit(set, [n])``
547 First n members of set, defaulting to 1.
550 First n members of set, defaulting to 1.
548 """
551 """
549 # i18n: "limit" is a keyword
552 # i18n: "limit" is a keyword
550 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
553 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
551 try:
554 try:
552 lim = 1
555 lim = 1
553 if len(l) == 2:
556 if len(l) == 2:
554 # i18n: "limit" is a keyword
557 # i18n: "limit" is a keyword
555 lim = int(getstring(l[1], _("limit requires a number")))
558 lim = int(getstring(l[1], _("limit requires a number")))
556 except (TypeError, ValueError):
559 except (TypeError, ValueError):
557 # i18n: "limit" is a keyword
560 # i18n: "limit" is a keyword
558 raise error.ParseError(_("limit expects a number"))
561 raise error.ParseError(_("limit expects a number"))
559 ss = set(subset)
562 ss = set(subset)
560 os = getset(repo, range(len(repo)), l[0])[:lim]
563 os = getset(repo, range(len(repo)), l[0])[:lim]
561 return [r for r in os if r in ss]
564 return [r for r in os if r in ss]
562
565
563 def last(repo, subset, x):
566 def last(repo, subset, x):
564 """``last(set, [n])``
567 """``last(set, [n])``
565 Last n members of set, defaulting to 1.
568 Last n members of set, defaulting to 1.
566 """
569 """
567 # i18n: "last" is a keyword
570 # i18n: "last" is a keyword
568 l = getargs(x, 1, 2, _("last requires one or two arguments"))
571 l = getargs(x, 1, 2, _("last requires one or two arguments"))
569 try:
572 try:
570 lim = 1
573 lim = 1
571 if len(l) == 2:
574 if len(l) == 2:
572 # i18n: "last" is a keyword
575 # i18n: "last" is a keyword
573 lim = int(getstring(l[1], _("last requires a number")))
576 lim = int(getstring(l[1], _("last requires a number")))
574 except (TypeError, ValueError):
577 except (TypeError, ValueError):
575 # i18n: "last" is a keyword
578 # i18n: "last" is a keyword
576 raise error.ParseError(_("last expects a number"))
579 raise error.ParseError(_("last expects a number"))
577 ss = set(subset)
580 ss = set(subset)
578 os = getset(repo, range(len(repo)), l[0])[-lim:]
581 os = getset(repo, range(len(repo)), l[0])[-lim:]
579 return [r for r in os if r in ss]
582 return [r for r in os if r in ss]
580
583
581 def maxrev(repo, subset, x):
584 def maxrev(repo, subset, x):
582 """``max(set)``
585 """``max(set)``
583 Changeset with highest revision number in set.
586 Changeset with highest revision number in set.
584 """
587 """
585 os = getset(repo, range(len(repo)), x)
588 os = getset(repo, range(len(repo)), x)
586 if os:
589 if os:
587 m = max(os)
590 m = max(os)
588 if m in subset:
591 if m in subset:
589 return [m]
592 return [m]
590 return []
593 return []
591
594
592 def merge(repo, subset, x):
595 def merge(repo, subset, x):
593 """``merge()``
596 """``merge()``
594 Changeset is a merge changeset.
597 Changeset is a merge changeset.
595 """
598 """
596 # i18n: "merge" is a keyword
599 # i18n: "merge" is a keyword
597 getargs(x, 0, 0, _("merge takes no arguments"))
600 getargs(x, 0, 0, _("merge takes no arguments"))
598 cl = repo.changelog
601 cl = repo.changelog
599 return [r for r in subset if cl.parentrevs(r)[1] != -1]
602 return [r for r in subset if cl.parentrevs(r)[1] != -1]
600
603
601 def minrev(repo, subset, x):
604 def minrev(repo, subset, x):
602 """``min(set)``
605 """``min(set)``
603 Changeset with lowest revision number in set.
606 Changeset with lowest revision number in set.
604 """
607 """
605 os = getset(repo, range(len(repo)), x)
608 os = getset(repo, range(len(repo)), x)
606 if os:
609 if os:
607 m = min(os)
610 m = min(os)
608 if m in subset:
611 if m in subset:
609 return [m]
612 return [m]
610 return []
613 return []
611
614
612 def modifies(repo, subset, x):
615 def modifies(repo, subset, x):
613 """``modifies(pattern)``
616 """``modifies(pattern)``
614 Changesets modifying files matched by pattern.
617 Changesets modifying files matched by pattern.
615 """
618 """
616 # i18n: "modifies" is a keyword
619 # i18n: "modifies" is a keyword
617 pat = getstring(x, _("modifies requires a pattern"))
620 pat = getstring(x, _("modifies requires a pattern"))
618 return checkstatus(repo, subset, pat, 0)
621 return checkstatus(repo, subset, pat, 0)
619
622
620 def node(repo, subset, x):
623 def node(repo, subset, x):
621 """``id(string)``
624 """``id(string)``
622 Revision non-ambiguously specified by the given hex string prefix.
625 Revision non-ambiguously specified by the given hex string prefix.
623 """
626 """
624 # i18n: "id" is a keyword
627 # i18n: "id" is a keyword
625 l = getargs(x, 1, 1, _("id requires one argument"))
628 l = getargs(x, 1, 1, _("id requires one argument"))
626 # i18n: "id" is a keyword
629 # i18n: "id" is a keyword
627 n = getstring(l[0], _("id requires a string"))
630 n = getstring(l[0], _("id requires a string"))
628 if len(n) == 40:
631 if len(n) == 40:
629 rn = repo[n].rev()
632 rn = repo[n].rev()
630 else:
633 else:
631 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
634 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
632 return [r for r in subset if r == rn]
635 return [r for r in subset if r == rn]
633
636
634 def outgoing(repo, subset, x):
637 def outgoing(repo, subset, x):
635 """``outgoing([path])``
638 """``outgoing([path])``
636 Changesets not found in the specified destination repository, or the
639 Changesets not found in the specified destination repository, or the
637 default push location.
640 default push location.
638 """
641 """
639 import hg # avoid start-up nasties
642 import hg # avoid start-up nasties
640 # i18n: "outgoing" is a keyword
643 # i18n: "outgoing" is a keyword
641 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
644 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
642 # i18n: "outgoing" is a keyword
645 # i18n: "outgoing" is a keyword
643 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
646 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
644 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
647 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
645 dest, branches = hg.parseurl(dest)
648 dest, branches = hg.parseurl(dest)
646 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
649 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
647 if revs:
650 if revs:
648 revs = [repo.lookup(rev) for rev in revs]
651 revs = [repo.lookup(rev) for rev in revs]
649 other = hg.peer(repo, {}, dest)
652 other = hg.peer(repo, {}, dest)
650 repo.ui.pushbuffer()
653 repo.ui.pushbuffer()
651 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
654 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
652 repo.ui.popbuffer()
655 repo.ui.popbuffer()
653 cl = repo.changelog
656 cl = repo.changelog
654 o = set([cl.rev(r) for r in outgoing.missing])
657 o = set([cl.rev(r) for r in outgoing.missing])
655 return [r for r in subset if r in o]
658 return [r for r in subset if r in o]
656
659
657 def p1(repo, subset, x):
660 def p1(repo, subset, x):
658 """``p1([set])``
661 """``p1([set])``
659 First parent of changesets in set, or the working directory.
662 First parent of changesets in set, or the working directory.
660 """
663 """
661 if x is None:
664 if x is None:
662 p = repo[x].p1().rev()
665 p = repo[x].p1().rev()
663 return [r for r in subset if r == p]
666 return [r for r in subset if r == p]
664
667
665 ps = set()
668 ps = set()
666 cl = repo.changelog
669 cl = repo.changelog
667 for r in getset(repo, range(len(repo)), x):
670 for r in getset(repo, range(len(repo)), x):
668 ps.add(cl.parentrevs(r)[0])
671 ps.add(cl.parentrevs(r)[0])
669 return [r for r in subset if r in ps]
672 return [r for r in subset if r in ps]
670
673
671 def p2(repo, subset, x):
674 def p2(repo, subset, x):
672 """``p2([set])``
675 """``p2([set])``
673 Second parent of changesets in set, or the working directory.
676 Second parent of changesets in set, or the working directory.
674 """
677 """
675 if x is None:
678 if x is None:
676 ps = repo[x].parents()
679 ps = repo[x].parents()
677 try:
680 try:
678 p = ps[1].rev()
681 p = ps[1].rev()
679 return [r for r in subset if r == p]
682 return [r for r in subset if r == p]
680 except IndexError:
683 except IndexError:
681 return []
684 return []
682
685
683 ps = set()
686 ps = set()
684 cl = repo.changelog
687 cl = repo.changelog
685 for r in getset(repo, range(len(repo)), x):
688 for r in getset(repo, range(len(repo)), x):
686 ps.add(cl.parentrevs(r)[1])
689 ps.add(cl.parentrevs(r)[1])
687 return [r for r in subset if r in ps]
690 return [r for r in subset if r in ps]
688
691
689 def parents(repo, subset, x):
692 def parents(repo, subset, x):
690 """``parents([set])``
693 """``parents([set])``
691 The set of all parents for all changesets in set, or the working directory.
694 The set of all parents for all changesets in set, or the working directory.
692 """
695 """
693 if x is None:
696 if x is None:
694 ps = tuple(p.rev() for p in repo[x].parents())
697 ps = tuple(p.rev() for p in repo[x].parents())
695 return [r for r in subset if r in ps]
698 return [r for r in subset if r in ps]
696
699
697 ps = set()
700 ps = set()
698 cl = repo.changelog
701 cl = repo.changelog
699 for r in getset(repo, range(len(repo)), x):
702 for r in getset(repo, range(len(repo)), x):
700 ps.update(cl.parentrevs(r))
703 ps.update(cl.parentrevs(r))
701 return [r for r in subset if r in ps]
704 return [r for r in subset if r in ps]
702
705
703 def parentspec(repo, subset, x, n):
706 def parentspec(repo, subset, x, n):
704 """``set^0``
707 """``set^0``
705 The set.
708 The set.
706 ``set^1`` (or ``set^``), ``set^2``
709 ``set^1`` (or ``set^``), ``set^2``
707 First or second parent, respectively, of all changesets in set.
710 First or second parent, respectively, of all changesets in set.
708 """
711 """
709 try:
712 try:
710 n = int(n[1])
713 n = int(n[1])
711 if n not in (0, 1, 2):
714 if n not in (0, 1, 2):
712 raise ValueError
715 raise ValueError
713 except (TypeError, ValueError):
716 except (TypeError, ValueError):
714 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
717 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
715 ps = set()
718 ps = set()
716 cl = repo.changelog
719 cl = repo.changelog
717 for r in getset(repo, subset, x):
720 for r in getset(repo, subset, x):
718 if n == 0:
721 if n == 0:
719 ps.add(r)
722 ps.add(r)
720 elif n == 1:
723 elif n == 1:
721 ps.add(cl.parentrevs(r)[0])
724 ps.add(cl.parentrevs(r)[0])
722 elif n == 2:
725 elif n == 2:
723 parents = cl.parentrevs(r)
726 parents = cl.parentrevs(r)
724 if len(parents) > 1:
727 if len(parents) > 1:
725 ps.add(parents[1])
728 ps.add(parents[1])
726 return [r for r in subset if r in ps]
729 return [r for r in subset if r in ps]
727
730
728 def present(repo, subset, x):
731 def present(repo, subset, x):
729 """``present(set)``
732 """``present(set)``
730 An empty set, if any revision in set isn't found; otherwise,
733 An empty set, if any revision in set isn't found; otherwise,
731 all revisions in set.
734 all revisions in set.
732 """
735 """
733 try:
736 try:
734 return getset(repo, subset, x)
737 return getset(repo, subset, x)
735 except error.RepoLookupError:
738 except error.RepoLookupError:
736 return []
739 return []
737
740
738 def public(repo, subset, x):
741 def public(repo, subset, x):
739 """``public()``
742 """``public()``
740 Changeset in public phase."""
743 Changeset in public phase."""
741 getargs(x, 0, 0, _("public takes no arguments"))
744 getargs(x, 0, 0, _("public takes no arguments"))
742 return [r for r in subset if repo._phaserev[r] == phases.public]
745 return [r for r in subset if repo._phaserev[r] == phases.public]
743
746
744 def remote(repo, subset, x):
747 def remote(repo, subset, x):
745 """``remote([id], [path])``
748 """``remote([id], [path])``
746 Local revision that corresponds to the given identifier in a
749 Local revision that corresponds to the given identifier in a
747 remote repository, if present. Here, the '.' identifier is a
750 remote repository, if present. Here, the '.' identifier is a
748 synonym for the current local branch.
751 synonym for the current local branch.
749 """
752 """
750
753
751 import hg # avoid start-up nasties
754 import hg # avoid start-up nasties
752 # i18n: "remote" is a keyword
755 # i18n: "remote" is a keyword
753 l = getargs(x, 0, 2, _("remote takes one or two arguments"))
756 l = getargs(x, 0, 2, _("remote takes one or two arguments"))
754
757
755 q = '.'
758 q = '.'
756 if len(l) > 0:
759 if len(l) > 0:
757 # i18n: "remote" is a keyword
760 # i18n: "remote" is a keyword
758 q = getstring(l[0], _("remote requires a string id"))
761 q = getstring(l[0], _("remote requires a string id"))
759 if q == '.':
762 if q == '.':
760 q = repo['.'].branch()
763 q = repo['.'].branch()
761
764
762 dest = ''
765 dest = ''
763 if len(l) > 1:
766 if len(l) > 1:
764 # i18n: "remote" is a keyword
767 # i18n: "remote" is a keyword
765 dest = getstring(l[1], _("remote requires a repository path"))
768 dest = getstring(l[1], _("remote requires a repository path"))
766 dest = repo.ui.expandpath(dest or 'default')
769 dest = repo.ui.expandpath(dest or 'default')
767 dest, branches = hg.parseurl(dest)
770 dest, branches = hg.parseurl(dest)
768 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
771 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
769 if revs:
772 if revs:
770 revs = [repo.lookup(rev) for rev in revs]
773 revs = [repo.lookup(rev) for rev in revs]
771 other = hg.peer(repo, {}, dest)
774 other = hg.peer(repo, {}, dest)
772 n = other.lookup(q)
775 n = other.lookup(q)
773 if n in repo:
776 if n in repo:
774 r = repo[n].rev()
777 r = repo[n].rev()
775 if r in subset:
778 if r in subset:
776 return [r]
779 return [r]
777 return []
780 return []
778
781
779 def removes(repo, subset, x):
782 def removes(repo, subset, x):
780 """``removes(pattern)``
783 """``removes(pattern)``
781 Changesets which remove files matching pattern.
784 Changesets which remove files matching pattern.
782 """
785 """
783 # i18n: "removes" is a keyword
786 # i18n: "removes" is a keyword
784 pat = getstring(x, _("removes requires a pattern"))
787 pat = getstring(x, _("removes requires a pattern"))
785 return checkstatus(repo, subset, pat, 2)
788 return checkstatus(repo, subset, pat, 2)
786
789
787 def rev(repo, subset, x):
790 def rev(repo, subset, x):
788 """``rev(number)``
791 """``rev(number)``
789 Revision with the given numeric identifier.
792 Revision with the given numeric identifier.
790 """
793 """
791 # i18n: "rev" is a keyword
794 # i18n: "rev" is a keyword
792 l = getargs(x, 1, 1, _("rev requires one argument"))
795 l = getargs(x, 1, 1, _("rev requires one argument"))
793 try:
796 try:
794 # i18n: "rev" is a keyword
797 # i18n: "rev" is a keyword
795 l = int(getstring(l[0], _("rev requires a number")))
798 l = int(getstring(l[0], _("rev requires a number")))
796 except (TypeError, ValueError):
799 except (TypeError, ValueError):
797 # i18n: "rev" is a keyword
800 # i18n: "rev" is a keyword
798 raise error.ParseError(_("rev expects a number"))
801 raise error.ParseError(_("rev expects a number"))
799 return [r for r in subset if r == l]
802 return [r for r in subset if r == l]
800
803
801 def reverse(repo, subset, x):
804 def reverse(repo, subset, x):
802 """``reverse(set)``
805 """``reverse(set)``
803 Reverse order of set.
806 Reverse order of set.
804 """
807 """
805 l = getset(repo, subset, x)
808 l = getset(repo, subset, x)
806 l.reverse()
809 l.reverse()
807 return l
810 return l
808
811
809 def roots(repo, subset, x):
812 def roots(repo, subset, x):
810 """``roots(set)``
813 """``roots(set)``
811 Changesets with no parent changeset in set.
814 Changesets with no parent changeset in set.
812 """
815 """
813 s = getset(repo, xrange(len(repo)), x)
816 s = getset(repo, xrange(len(repo)), x)
814 cs = _children(repo, s, s)
817 cs = _children(repo, s, s)
815 return [r for r in s if r not in cs]
818 return [r for r in s if r not in cs]
816
819
817 def secret(repo, subset, x):
820 def secret(repo, subset, x):
818 """``secret()``
821 """``secret()``
819 Changeset in secret phase."""
822 Changeset in secret phase."""
820 getargs(x, 0, 0, _("secret takes no arguments"))
823 getargs(x, 0, 0, _("secret takes no arguments"))
821 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]
822
825
823 def sort(repo, subset, x):
826 def sort(repo, subset, x):
824 """``sort(set[, [-]key...])``
827 """``sort(set[, [-]key...])``
825 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
826 as ``-key`` to sort in descending order.
829 as ``-key`` to sort in descending order.
827
830
828 The keys can be:
831 The keys can be:
829
832
830 - ``rev`` for the revision number,
833 - ``rev`` for the revision number,
831 - ``branch`` for the branch name,
834 - ``branch`` for the branch name,
832 - ``desc`` for the commit message (description),
835 - ``desc`` for the commit message (description),
833 - ``user`` for user name (``author`` can be used as an alias),
836 - ``user`` for user name (``author`` can be used as an alias),
834 - ``date`` for the commit date
837 - ``date`` for the commit date
835 """
838 """
836 # i18n: "sort" is a keyword
839 # i18n: "sort" is a keyword
837 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
840 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
838 keys = "rev"
841 keys = "rev"
839 if len(l) == 2:
842 if len(l) == 2:
840 keys = getstring(l[1], _("sort spec must be a string"))
843 keys = getstring(l[1], _("sort spec must be a string"))
841
844
842 s = l[0]
845 s = l[0]
843 keys = keys.split()
846 keys = keys.split()
844 l = []
847 l = []
845 def invert(s):
848 def invert(s):
846 return "".join(chr(255 - ord(c)) for c in s)
849 return "".join(chr(255 - ord(c)) for c in s)
847 for r in getset(repo, subset, s):
850 for r in getset(repo, subset, s):
848 c = repo[r]
851 c = repo[r]
849 e = []
852 e = []
850 for k in keys:
853 for k in keys:
851 if k == 'rev':
854 if k == 'rev':
852 e.append(r)
855 e.append(r)
853 elif k == '-rev':
856 elif k == '-rev':
854 e.append(-r)
857 e.append(-r)
855 elif k == 'branch':
858 elif k == 'branch':
856 e.append(c.branch())
859 e.append(c.branch())
857 elif k == '-branch':
860 elif k == '-branch':
858 e.append(invert(c.branch()))
861 e.append(invert(c.branch()))
859 elif k == 'desc':
862 elif k == 'desc':
860 e.append(c.description())
863 e.append(c.description())
861 elif k == '-desc':
864 elif k == '-desc':
862 e.append(invert(c.description()))
865 e.append(invert(c.description()))
863 elif k in 'user author':
866 elif k in 'user author':
864 e.append(c.user())
867 e.append(c.user())
865 elif k in '-user -author':
868 elif k in '-user -author':
866 e.append(invert(c.user()))
869 e.append(invert(c.user()))
867 elif k == 'date':
870 elif k == 'date':
868 e.append(c.date()[0])
871 e.append(c.date()[0])
869 elif k == '-date':
872 elif k == '-date':
870 e.append(-c.date()[0])
873 e.append(-c.date()[0])
871 else:
874 else:
872 raise error.ParseError(_("unknown sort key %r") % k)
875 raise error.ParseError(_("unknown sort key %r") % k)
873 e.append(r)
876 e.append(r)
874 l.append(e)
877 l.append(e)
875 l.sort()
878 l.sort()
876 return [e[-1] for e in l]
879 return [e[-1] for e in l]
877
880
878 def tag(repo, subset, x):
881 def tag(repo, subset, x):
879 """``tag([name])``
882 """``tag([name])``
880 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.
881 """
884 """
882 # i18n: "tag" is a keyword
885 # i18n: "tag" is a keyword
883 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
886 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
884 cl = repo.changelog
887 cl = repo.changelog
885 if args:
888 if args:
886 tn = getstring(args[0],
889 tn = getstring(args[0],
887 # i18n: "tag" is a keyword
890 # i18n: "tag" is a keyword
888 _('the argument to tag must be a string'))
891 _('the argument to tag must be a string'))
889 if not repo.tags().get(tn, None):
892 if not repo.tags().get(tn, None):
890 raise util.Abort(_("tag '%s' does not exist") % tn)
893 raise util.Abort(_("tag '%s' does not exist") % tn)
891 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])
892 else:
895 else:
893 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'])
894 return [r for r in subset if r in s]
897 return [r for r in subset if r in s]
895
898
896 def tagged(repo, subset, x):
899 def tagged(repo, subset, x):
897 return tag(repo, subset, x)
900 return tag(repo, subset, x)
898
901
899 def user(repo, subset, x):
902 def user(repo, subset, x):
900 """``user(string)``
903 """``user(string)``
901 User name contains string. The match is case-insensitive.
904 User name contains string. The match is case-insensitive.
902 """
905 """
903 return author(repo, subset, x)
906 return author(repo, subset, x)
904
907
905 # for internal use
908 # for internal use
906 def _list(repo, subset, x):
909 def _list(repo, subset, x):
907 s = getstring(x, "internal error")
910 s = getstring(x, "internal error")
908 if not s:
911 if not s:
909 return []
912 return []
910 if not isinstance(subset, set):
913 if not isinstance(subset, set):
911 subset = set(subset)
914 subset = set(subset)
912 ls = [repo[r].rev() for r in s.split('\0')]
915 ls = [repo[r].rev() for r in s.split('\0')]
913 return [r for r in ls if r in subset]
916 return [r for r in ls if r in subset]
914
917
915 symbols = {
918 symbols = {
916 "adds": adds,
919 "adds": adds,
917 "all": getall,
920 "all": getall,
918 "ancestor": ancestor,
921 "ancestor": ancestor,
919 "ancestors": ancestors,
922 "ancestors": ancestors,
920 "author": author,
923 "author": author,
921 "bisect": bisect,
924 "bisect": bisect,
922 "bisected": bisected,
925 "bisected": bisected,
923 "bookmark": bookmark,
926 "bookmark": bookmark,
924 "branch": branch,
927 "branch": branch,
925 "children": children,
928 "children": children,
926 "closed": closed,
929 "closed": closed,
927 "contains": contains,
930 "contains": contains,
928 "date": date,
931 "date": date,
929 "desc": desc,
932 "desc": desc,
930 "descendants": descendants,
933 "descendants": descendants,
931 "draft": draft,
934 "draft": draft,
932 "file": hasfile,
935 "file": hasfile,
933 "filelog": filelog,
936 "filelog": filelog,
934 "first": first,
937 "first": first,
935 "follow": follow,
938 "follow": follow,
936 "grep": grep,
939 "grep": grep,
937 "head": head,
940 "head": head,
938 "heads": heads,
941 "heads": heads,
939 "id": node,
942 "id": node,
940 "keyword": keyword,
943 "keyword": keyword,
941 "last": last,
944 "last": last,
942 "limit": limit,
945 "limit": limit,
943 "max": maxrev,
946 "max": maxrev,
944 "merge": merge,
947 "merge": merge,
945 "min": minrev,
948 "min": minrev,
946 "modifies": modifies,
949 "modifies": modifies,
947 "outgoing": outgoing,
950 "outgoing": outgoing,
948 "p1": p1,
951 "p1": p1,
949 "p2": p2,
952 "p2": p2,
950 "parents": parents,
953 "parents": parents,
951 "present": present,
954 "present": present,
952 "public": public,
955 "public": public,
953 "remote": remote,
956 "remote": remote,
954 "removes": removes,
957 "removes": removes,
955 "rev": rev,
958 "rev": rev,
956 "reverse": reverse,
959 "reverse": reverse,
957 "roots": roots,
960 "roots": roots,
958 "sort": sort,
961 "sort": sort,
959 "secret": secret,
962 "secret": secret,
960 "tag": tag,
963 "tag": tag,
961 "tagged": tagged,
964 "tagged": tagged,
962 "user": user,
965 "user": user,
963 "_list": _list,
966 "_list": _list,
964 }
967 }
965
968
966 methods = {
969 methods = {
967 "range": rangeset,
970 "range": rangeset,
968 "string": stringset,
971 "string": stringset,
969 "symbol": symbolset,
972 "symbol": symbolset,
970 "and": andset,
973 "and": andset,
971 "or": orset,
974 "or": orset,
972 "not": notset,
975 "not": notset,
973 "list": listset,
976 "list": listset,
974 "func": func,
977 "func": func,
975 "ancestor": ancestorspec,
978 "ancestor": ancestorspec,
976 "parent": parentspec,
979 "parent": parentspec,
977 "parentpost": p1,
980 "parentpost": p1,
978 }
981 }
979
982
980 def optimize(x, small):
983 def optimize(x, small):
981 if x is None:
984 if x is None:
982 return 0, x
985 return 0, x
983
986
984 smallbonus = 1
987 smallbonus = 1
985 if small:
988 if small:
986 smallbonus = .5
989 smallbonus = .5
987
990
988 op = x[0]
991 op = x[0]
989 if op == 'minus':
992 if op == 'minus':
990 return optimize(('and', x[1], ('not', x[2])), small)
993 return optimize(('and', x[1], ('not', x[2])), small)
991 elif op == 'dagrange':
994 elif op == 'dagrange':
992 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
995 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
993 ('func', ('symbol', 'ancestors'), x[2])), small)
996 ('func', ('symbol', 'ancestors'), x[2])), small)
994 elif op == 'dagrangepre':
997 elif op == 'dagrangepre':
995 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
998 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
996 elif op == 'dagrangepost':
999 elif op == 'dagrangepost':
997 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1000 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
998 elif op == 'rangepre':
1001 elif op == 'rangepre':
999 return optimize(('range', ('string', '0'), x[1]), small)
1002 return optimize(('range', ('string', '0'), x[1]), small)
1000 elif op == 'rangepost':
1003 elif op == 'rangepost':
1001 return optimize(('range', x[1], ('string', 'tip')), small)
1004 return optimize(('range', x[1], ('string', 'tip')), small)
1002 elif op == 'negate':
1005 elif op == 'negate':
1003 return optimize(('string',
1006 return optimize(('string',
1004 '-' + getstring(x[1], _("can't negate that"))), small)
1007 '-' + getstring(x[1], _("can't negate that"))), small)
1005 elif op in 'string symbol negate':
1008 elif op in 'string symbol negate':
1006 return smallbonus, x # single revisions are small
1009 return smallbonus, x # single revisions are small
1007 elif op == 'and' or op == 'dagrange':
1010 elif op == 'and' or op == 'dagrange':
1008 wa, ta = optimize(x[1], True)
1011 wa, ta = optimize(x[1], True)
1009 wb, tb = optimize(x[2], True)
1012 wb, tb = optimize(x[2], True)
1010 w = min(wa, wb)
1013 w = min(wa, wb)
1011 if wa > wb:
1014 if wa > wb:
1012 return w, (op, tb, ta)
1015 return w, (op, tb, ta)
1013 return w, (op, ta, tb)
1016 return w, (op, ta, tb)
1014 elif op == 'or':
1017 elif op == 'or':
1015 wa, ta = optimize(x[1], False)
1018 wa, ta = optimize(x[1], False)
1016 wb, tb = optimize(x[2], False)
1019 wb, tb = optimize(x[2], False)
1017 if wb < wa:
1020 if wb < wa:
1018 wb, wa = wa, wb
1021 wb, wa = wa, wb
1019 return max(wa, wb), (op, ta, tb)
1022 return max(wa, wb), (op, ta, tb)
1020 elif op == 'not':
1023 elif op == 'not':
1021 o = optimize(x[1], not small)
1024 o = optimize(x[1], not small)
1022 return o[0], (op, o[1])
1025 return o[0], (op, o[1])
1023 elif op == 'parentpost':
1026 elif op == 'parentpost':
1024 o = optimize(x[1], small)
1027 o = optimize(x[1], small)
1025 return o[0], (op, o[1])
1028 return o[0], (op, o[1])
1026 elif op == 'group':
1029 elif op == 'group':
1027 return optimize(x[1], small)
1030 return optimize(x[1], small)
1028 elif op in 'range list parent ancestorspec':
1031 elif op in 'range list parent ancestorspec':
1029 if op == 'parent':
1032 if op == 'parent':
1030 # x^:y means (x^) : y, not x ^ (:y)
1033 # x^:y means (x^) : y, not x ^ (:y)
1031 post = ('parentpost', x[1])
1034 post = ('parentpost', x[1])
1032 if x[2][0] == 'dagrangepre':
1035 if x[2][0] == 'dagrangepre':
1033 return optimize(('dagrange', post, x[2][1]), small)
1036 return optimize(('dagrange', post, x[2][1]), small)
1034 elif x[2][0] == 'rangepre':
1037 elif x[2][0] == 'rangepre':
1035 return optimize(('range', post, x[2][1]), small)
1038 return optimize(('range', post, x[2][1]), small)
1036
1039
1037 wa, ta = optimize(x[1], small)
1040 wa, ta = optimize(x[1], small)
1038 wb, tb = optimize(x[2], small)
1041 wb, tb = optimize(x[2], small)
1039 return wa + wb, (op, ta, tb)
1042 return wa + wb, (op, ta, tb)
1040 elif op == 'func':
1043 elif op == 'func':
1041 f = getstring(x[1], _("not a symbol"))
1044 f = getstring(x[1], _("not a symbol"))
1042 wa, ta = optimize(x[2], small)
1045 wa, ta = optimize(x[2], small)
1043 if f in ("author branch closed date desc file grep keyword "
1046 if f in ("author branch closed date desc file grep keyword "
1044 "outgoing user"):
1047 "outgoing user"):
1045 w = 10 # slow
1048 w = 10 # slow
1046 elif f in "modifies adds removes":
1049 elif f in "modifies adds removes":
1047 w = 30 # slower
1050 w = 30 # slower
1048 elif f == "contains":
1051 elif f == "contains":
1049 w = 100 # very slow
1052 w = 100 # very slow
1050 elif f == "ancestor":
1053 elif f == "ancestor":
1051 w = 1 * smallbonus
1054 w = 1 * smallbonus
1052 elif f in "reverse limit first":
1055 elif f in "reverse limit first":
1053 w = 0
1056 w = 0
1054 elif f in "sort":
1057 elif f in "sort":
1055 w = 10 # assume most sorts look at changelog
1058 w = 10 # assume most sorts look at changelog
1056 else:
1059 else:
1057 w = 1
1060 w = 1
1058 return w + wa, (op, x[1], ta)
1061 return w + wa, (op, x[1], ta)
1059 return 1, x
1062 return 1, x
1060
1063
1061 class revsetalias(object):
1064 class revsetalias(object):
1062 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1065 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1063 args = None
1066 args = None
1064
1067
1065 def __init__(self, name, value):
1068 def __init__(self, name, value):
1066 '''Aliases like:
1069 '''Aliases like:
1067
1070
1068 h = heads(default)
1071 h = heads(default)
1069 b($1) = ancestors($1) - ancestors(default)
1072 b($1) = ancestors($1) - ancestors(default)
1070 '''
1073 '''
1071 if isinstance(name, tuple): # parameter substitution
1074 if isinstance(name, tuple): # parameter substitution
1072 self.tree = name
1075 self.tree = name
1073 self.replacement = value
1076 self.replacement = value
1074 else: # alias definition
1077 else: # alias definition
1075 m = self.funcre.search(name)
1078 m = self.funcre.search(name)
1076 if m:
1079 if m:
1077 self.tree = ('func', ('symbol', m.group(1)))
1080 self.tree = ('func', ('symbol', m.group(1)))
1078 self.args = [x.strip() for x in m.group(2).split(',')]
1081 self.args = [x.strip() for x in m.group(2).split(',')]
1079 for arg in self.args:
1082 for arg in self.args:
1080 value = value.replace(arg, repr(arg))
1083 value = value.replace(arg, repr(arg))
1081 else:
1084 else:
1082 self.tree = ('symbol', name)
1085 self.tree = ('symbol', name)
1083
1086
1084 self.replacement, pos = parse(value)
1087 self.replacement, pos = parse(value)
1085 if pos != len(value):
1088 if pos != len(value):
1086 raise error.ParseError(_('invalid token'), pos)
1089 raise error.ParseError(_('invalid token'), pos)
1087
1090
1088 def process(self, tree):
1091 def process(self, tree):
1089 if isinstance(tree, tuple):
1092 if isinstance(tree, tuple):
1090 if self.args is None:
1093 if self.args is None:
1091 if tree == self.tree:
1094 if tree == self.tree:
1092 return self.replacement
1095 return self.replacement
1093 elif tree[:2] == self.tree:
1096 elif tree[:2] == self.tree:
1094 l = getlist(tree[2])
1097 l = getlist(tree[2])
1095 if len(l) != len(self.args):
1098 if len(l) != len(self.args):
1096 raise error.ParseError(
1099 raise error.ParseError(
1097 _('invalid number of arguments: %s') % len(l))
1100 _('invalid number of arguments: %s') % len(l))
1098 result = self.replacement
1101 result = self.replacement
1099 for a, v in zip(self.args, l):
1102 for a, v in zip(self.args, l):
1100 valalias = revsetalias(('string', a), v)
1103 valalias = revsetalias(('string', a), v)
1101 result = valalias.process(result)
1104 result = valalias.process(result)
1102 return result
1105 return result
1103 return tuple(map(self.process, tree))
1106 return tuple(map(self.process, tree))
1104 return tree
1107 return tree
1105
1108
1106 def findaliases(ui, tree):
1109 def findaliases(ui, tree):
1107 for k, v in ui.configitems('revsetalias'):
1110 for k, v in ui.configitems('revsetalias'):
1108 alias = revsetalias(k, v)
1111 alias = revsetalias(k, v)
1109 tree = alias.process(tree)
1112 tree = alias.process(tree)
1110 return tree
1113 return tree
1111
1114
1112 parse = parser.parser(tokenize, elements).parse
1115 parse = parser.parser(tokenize, elements).parse
1113
1116
1114 def match(ui, spec):
1117 def match(ui, spec):
1115 if not spec:
1118 if not spec:
1116 raise error.ParseError(_("empty query"))
1119 raise error.ParseError(_("empty query"))
1117 tree, pos = parse(spec)
1120 tree, pos = parse(spec)
1118 if (pos != len(spec)):
1121 if (pos != len(spec)):
1119 raise error.ParseError(_("invalid token"), pos)
1122 raise error.ParseError(_("invalid token"), pos)
1120 if ui:
1123 if ui:
1121 tree = findaliases(ui, tree)
1124 tree = findaliases(ui, tree)
1122 weight, tree = optimize(tree, True)
1125 weight, tree = optimize(tree, True)
1123 def mfunc(repo, subset):
1126 def mfunc(repo, subset):
1124 return getset(repo, subset, tree)
1127 return getset(repo, subset, tree)
1125 return mfunc
1128 return mfunc
1126
1129
1127 def formatspec(expr, *args):
1130 def formatspec(expr, *args):
1128 '''
1131 '''
1129 This is a convenience function for using revsets internally, and
1132 This is a convenience function for using revsets internally, and
1130 escapes arguments appropriately. Aliases are intentionally ignored
1133 escapes arguments appropriately. Aliases are intentionally ignored
1131 so that intended expression behavior isn't accidentally subverted.
1134 so that intended expression behavior isn't accidentally subverted.
1132
1135
1133 Supported arguments:
1136 Supported arguments:
1134
1137
1135 %r = revset expression, parenthesized
1138 %r = revset expression, parenthesized
1136 %d = int(arg), no quoting
1139 %d = int(arg), no quoting
1137 %s = string(arg), escaped and single-quoted
1140 %s = string(arg), escaped and single-quoted
1138 %b = arg.branch(), escaped and single-quoted
1141 %b = arg.branch(), escaped and single-quoted
1139 %n = hex(arg), single-quoted
1142 %n = hex(arg), single-quoted
1140 %% = a literal '%'
1143 %% = a literal '%'
1141
1144
1142 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.
1143
1146
1144 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1147 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1145 '(10 or 11):: and ((this()) or (that()))'
1148 '(10 or 11):: and ((this()) or (that()))'
1146 >>> formatspec('%d:: and not %d::', 10, 20)
1149 >>> formatspec('%d:: and not %d::', 10, 20)
1147 '10:: and not 20::'
1150 '10:: and not 20::'
1148 >>> formatspec('%ld or %ld', [], [1])
1151 >>> formatspec('%ld or %ld', [], [1])
1149 "_list('') or 1"
1152 "_list('') or 1"
1150 >>> formatspec('keyword(%s)', 'foo\\xe9')
1153 >>> formatspec('keyword(%s)', 'foo\\xe9')
1151 "keyword('foo\\\\xe9')"
1154 "keyword('foo\\\\xe9')"
1152 >>> b = lambda: 'default'
1155 >>> b = lambda: 'default'
1153 >>> b.branch = b
1156 >>> b.branch = b
1154 >>> formatspec('branch(%b)', b)
1157 >>> formatspec('branch(%b)', b)
1155 "branch('default')"
1158 "branch('default')"
1156 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1159 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1157 "root(_list('a\\x00b\\x00c\\x00d'))"
1160 "root(_list('a\\x00b\\x00c\\x00d'))"
1158 '''
1161 '''
1159
1162
1160 def quote(s):
1163 def quote(s):
1161 return repr(str(s))
1164 return repr(str(s))
1162
1165
1163 def argtype(c, arg):
1166 def argtype(c, arg):
1164 if c == 'd':
1167 if c == 'd':
1165 return str(int(arg))
1168 return str(int(arg))
1166 elif c == 's':
1169 elif c == 's':
1167 return quote(arg)
1170 return quote(arg)
1168 elif c == 'r':
1171 elif c == 'r':
1169 parse(arg) # make sure syntax errors are confined
1172 parse(arg) # make sure syntax errors are confined
1170 return '(%s)' % arg
1173 return '(%s)' % arg
1171 elif c == 'n':
1174 elif c == 'n':
1172 return quote(nodemod.hex(arg))
1175 return quote(nodemod.hex(arg))
1173 elif c == 'b':
1176 elif c == 'b':
1174 return quote(arg.branch())
1177 return quote(arg.branch())
1175
1178
1176 def listexp(s, t):
1179 def listexp(s, t):
1177 l = len(s)
1180 l = len(s)
1178 if l == 0:
1181 if l == 0:
1179 return "_list('')"
1182 return "_list('')"
1180 elif l == 1:
1183 elif l == 1:
1181 return argtype(t, s[0])
1184 return argtype(t, s[0])
1182 elif t == 'd':
1185 elif t == 'd':
1183 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)
1184 elif t == 's':
1187 elif t == 's':
1185 return "_list('%s')" % "\0".join(s)
1188 return "_list('%s')" % "\0".join(s)
1186 elif t == 'n':
1189 elif t == 'n':
1187 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)
1188 elif t == 'b':
1191 elif t == 'b':
1189 return "_list('%s')" % "\0".join(a.branch() for a in s)
1192 return "_list('%s')" % "\0".join(a.branch() for a in s)
1190
1193
1191 m = l // 2
1194 m = l // 2
1192 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))
1193
1196
1194 ret = ''
1197 ret = ''
1195 pos = 0
1198 pos = 0
1196 arg = 0
1199 arg = 0
1197 while pos < len(expr):
1200 while pos < len(expr):
1198 c = expr[pos]
1201 c = expr[pos]
1199 if c == '%':
1202 if c == '%':
1200 pos += 1
1203 pos += 1
1201 d = expr[pos]
1204 d = expr[pos]
1202 if d == '%':
1205 if d == '%':
1203 ret += d
1206 ret += d
1204 elif d in 'dsnbr':
1207 elif d in 'dsnbr':
1205 ret += argtype(d, args[arg])
1208 ret += argtype(d, args[arg])
1206 arg += 1
1209 arg += 1
1207 elif d == 'l':
1210 elif d == 'l':
1208 # a list of some type
1211 # a list of some type
1209 pos += 1
1212 pos += 1
1210 d = expr[pos]
1213 d = expr[pos]
1211 ret += listexp(list(args[arg]), d)
1214 ret += listexp(list(args[arg]), d)
1212 arg += 1
1215 arg += 1
1213 else:
1216 else:
1214 raise util.Abort('unexpected revspec format character %s' % d)
1217 raise util.Abort('unexpected revspec format character %s' % d)
1215 else:
1218 else:
1216 ret += c
1219 ret += c
1217 pos += 1
1220 pos += 1
1218
1221
1219 return ret
1222 return ret
1220
1223
1221 # tell hggettext to extract docstrings from these functions:
1224 # tell hggettext to extract docstrings from these functions:
1222 i18nfunctions = symbols.values()
1225 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now