##// END OF EJS Templates
phases: implements simple revset symbol...
Pierre-Yves David -
r15819:33ca11b0 default
parent child Browse files
Show More
@@ -1,1142 +1,1163
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
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 = matchmod.match(repo.root, repo.getcwd(), [pat])
299 m = matchmod.match(repo.root, repo.getcwd(), [pat])
300 s = []
300 s = []
301 fast = (m.files() == [pat])
301 fast = (m.files() == [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 for f in c.files():
308 for f in c.files():
309 if m(f):
309 if m(f):
310 break
310 break
311 else:
311 else:
312 continue
312 continue
313 files = repo.status(c.p1().node(), c.node())[field]
313 files = repo.status(c.p1().node(), c.node())[field]
314 if fast:
314 if fast:
315 if pat in files:
315 if pat in files:
316 s.append(r)
316 s.append(r)
317 else:
317 else:
318 for f in files:
318 for f in files:
319 if m(f):
319 if m(f):
320 s.append(r)
320 s.append(r)
321 break
321 break
322 return s
322 return s
323
323
324 def children(repo, subset, x):
324 def children(repo, subset, x):
325 """``children(set)``
325 """``children(set)``
326 Child changesets of changesets in set.
326 Child changesets of changesets in set.
327 """
327 """
328 cs = set()
328 cs = set()
329 cl = repo.changelog
329 cl = repo.changelog
330 s = set(getset(repo, range(len(repo)), x))
330 s = set(getset(repo, range(len(repo)), x))
331 for r in xrange(0, len(repo)):
331 for r in xrange(0, len(repo)):
332 for p in cl.parentrevs(r):
332 for p in cl.parentrevs(r):
333 if p in s:
333 if p in s:
334 cs.add(r)
334 cs.add(r)
335 return [r for r in subset if r in cs]
335 return [r for r in subset if r in cs]
336
336
337 def closed(repo, subset, x):
337 def closed(repo, subset, x):
338 """``closed()``
338 """``closed()``
339 Changeset is closed.
339 Changeset is closed.
340 """
340 """
341 # i18n: "closed" is a keyword
341 # i18n: "closed" is a keyword
342 getargs(x, 0, 0, _("closed takes no arguments"))
342 getargs(x, 0, 0, _("closed takes no arguments"))
343 return [r for r in subset if repo[r].extra().get('close')]
343 return [r for r in subset if repo[r].extra().get('close')]
344
344
345 def contains(repo, subset, x):
345 def contains(repo, subset, x):
346 """``contains(pattern)``
346 """``contains(pattern)``
347 Revision contains a file matching pattern. See :hg:`help patterns`
347 Revision contains a file matching pattern. See :hg:`help patterns`
348 for information about file patterns.
348 for information about file patterns.
349 """
349 """
350 # i18n: "contains" is a keyword
350 # i18n: "contains" is a keyword
351 pat = getstring(x, _("contains requires a pattern"))
351 pat = getstring(x, _("contains requires a pattern"))
352 m = matchmod.match(repo.root, repo.getcwd(), [pat])
352 m = matchmod.match(repo.root, repo.getcwd(), [pat])
353 s = []
353 s = []
354 if m.files() == [pat]:
354 if m.files() == [pat]:
355 for r in subset:
355 for r in subset:
356 if pat in repo[r]:
356 if pat in repo[r]:
357 s.append(r)
357 s.append(r)
358 else:
358 else:
359 for r in subset:
359 for r in subset:
360 for f in repo[r].manifest():
360 for f in repo[r].manifest():
361 if m(f):
361 if m(f):
362 s.append(r)
362 s.append(r)
363 break
363 break
364 return s
364 return s
365
365
366 def date(repo, subset, x):
366 def date(repo, subset, x):
367 """``date(interval)``
367 """``date(interval)``
368 Changesets within the interval, see :hg:`help dates`.
368 Changesets within the interval, see :hg:`help dates`.
369 """
369 """
370 # i18n: "date" is a keyword
370 # i18n: "date" is a keyword
371 ds = getstring(x, _("date requires a string"))
371 ds = getstring(x, _("date requires a string"))
372 dm = util.matchdate(ds)
372 dm = util.matchdate(ds)
373 return [r for r in subset if dm(repo[r].date()[0])]
373 return [r for r in subset if dm(repo[r].date()[0])]
374
374
375 def desc(repo, subset, x):
375 def desc(repo, subset, x):
376 """``desc(string)``
376 """``desc(string)``
377 Search commit message for string. The match is case-insensitive.
377 Search commit message for string. The match is case-insensitive.
378 """
378 """
379 # i18n: "desc" is a keyword
379 # i18n: "desc" is a keyword
380 ds = encoding.lower(getstring(x, _("desc requires a string")))
380 ds = encoding.lower(getstring(x, _("desc requires a string")))
381 l = []
381 l = []
382 for r in subset:
382 for r in subset:
383 c = repo[r]
383 c = repo[r]
384 if ds in encoding.lower(c.description()):
384 if ds in encoding.lower(c.description()):
385 l.append(r)
385 l.append(r)
386 return l
386 return l
387
387
388 def descendants(repo, subset, x):
388 def descendants(repo, subset, x):
389 """``descendants(set)``
389 """``descendants(set)``
390 Changesets which are descendants of changesets in set.
390 Changesets which are descendants of changesets in set.
391 """
391 """
392 args = getset(repo, range(len(repo)), x)
392 args = getset(repo, range(len(repo)), x)
393 if not args:
393 if not args:
394 return []
394 return []
395 s = set(repo.changelog.descendants(*args)) | set(args)
395 s = set(repo.changelog.descendants(*args)) | set(args)
396 return [r for r in subset if r in s]
396 return [r for r in subset if r in s]
397
397
398 def draft(repo, subset, x):
399 """``draft()``
400 Changeset in draft phase."""
401 getargs(x, 0, 0, _("draft takes no arguments"))
402 return [r for r in subset if repo._phaserev[r] == phases.draft]
403
398 def filelog(repo, subset, x):
404 def filelog(repo, subset, x):
399 """``filelog(pattern)``
405 """``filelog(pattern)``
400 Changesets connected to the specified filelog.
406 Changesets connected to the specified filelog.
401 """
407 """
402
408
403 pat = getstring(x, _("filelog requires a pattern"))
409 pat = getstring(x, _("filelog requires a pattern"))
404 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
410 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
405 s = set()
411 s = set()
406
412
407 if not m.anypats():
413 if not m.anypats():
408 for f in m.files():
414 for f in m.files():
409 fl = repo.file(f)
415 fl = repo.file(f)
410 for fr in fl:
416 for fr in fl:
411 s.add(fl.linkrev(fr))
417 s.add(fl.linkrev(fr))
412 else:
418 else:
413 for f in repo[None]:
419 for f in repo[None]:
414 if m(f):
420 if m(f):
415 fl = repo.file(f)
421 fl = repo.file(f)
416 for fr in fl:
422 for fr in fl:
417 s.add(fl.linkrev(fr))
423 s.add(fl.linkrev(fr))
418
424
419 return [r for r in subset if r in s]
425 return [r for r in subset if r in s]
420
426
421 def first(repo, subset, x):
427 def first(repo, subset, x):
422 """``first(set, [n])``
428 """``first(set, [n])``
423 An alias for limit().
429 An alias for limit().
424 """
430 """
425 return limit(repo, subset, x)
431 return limit(repo, subset, x)
426
432
427 def follow(repo, subset, x):
433 def follow(repo, subset, x):
428 """``follow([file])``
434 """``follow([file])``
429 An alias for ``::.`` (ancestors of the working copy's first parent).
435 An alias for ``::.`` (ancestors of the working copy's first parent).
430 If a filename is specified, the history of the given file is followed,
436 If a filename is specified, the history of the given file is followed,
431 including copies.
437 including copies.
432 """
438 """
433 # i18n: "follow" is a keyword
439 # i18n: "follow" is a keyword
434 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
440 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
435 p = repo['.'].rev()
441 p = repo['.'].rev()
436 if l:
442 if l:
437 x = getstring(l[0], _("follow expected a filename"))
443 x = getstring(l[0], _("follow expected a filename"))
438 if x in repo['.']:
444 if x in repo['.']:
439 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
445 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
440 else:
446 else:
441 return []
447 return []
442 else:
448 else:
443 s = set(repo.changelog.ancestors(p))
449 s = set(repo.changelog.ancestors(p))
444
450
445 s |= set([p])
451 s |= set([p])
446 return [r for r in subset if r in s]
452 return [r for r in subset if r in s]
447
453
448 def followfile(repo, subset, x):
454 def followfile(repo, subset, x):
449 """``follow()``
455 """``follow()``
450 An alias for ``::.`` (ancestors of the working copy's first parent).
456 An alias for ``::.`` (ancestors of the working copy's first parent).
451 """
457 """
452 # i18n: "follow" is a keyword
458 # i18n: "follow" is a keyword
453 getargs(x, 0, 0, _("follow takes no arguments"))
459 getargs(x, 0, 0, _("follow takes no arguments"))
454 p = repo['.'].rev()
460 p = repo['.'].rev()
455 s = set(repo.changelog.ancestors(p)) | set([p])
461 s = set(repo.changelog.ancestors(p)) | set([p])
456 return [r for r in subset if r in s]
462 return [r for r in subset if r in s]
457
463
458 def getall(repo, subset, x):
464 def getall(repo, subset, x):
459 """``all()``
465 """``all()``
460 All changesets, the same as ``0:tip``.
466 All changesets, the same as ``0:tip``.
461 """
467 """
462 # i18n: "all" is a keyword
468 # i18n: "all" is a keyword
463 getargs(x, 0, 0, _("all takes no arguments"))
469 getargs(x, 0, 0, _("all takes no arguments"))
464 return subset
470 return subset
465
471
466 def grep(repo, subset, x):
472 def grep(repo, subset, x):
467 """``grep(regex)``
473 """``grep(regex)``
468 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
474 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
469 to ensure special escape characters are handled correctly. Unlike
475 to ensure special escape characters are handled correctly. Unlike
470 ``keyword(string)``, the match is case-sensitive.
476 ``keyword(string)``, the match is case-sensitive.
471 """
477 """
472 try:
478 try:
473 # i18n: "grep" is a keyword
479 # i18n: "grep" is a keyword
474 gr = re.compile(getstring(x, _("grep requires a string")))
480 gr = re.compile(getstring(x, _("grep requires a string")))
475 except re.error, e:
481 except re.error, e:
476 raise error.ParseError(_('invalid match pattern: %s') % e)
482 raise error.ParseError(_('invalid match pattern: %s') % e)
477 l = []
483 l = []
478 for r in subset:
484 for r in subset:
479 c = repo[r]
485 c = repo[r]
480 for e in c.files() + [c.user(), c.description()]:
486 for e in c.files() + [c.user(), c.description()]:
481 if gr.search(e):
487 if gr.search(e):
482 l.append(r)
488 l.append(r)
483 break
489 break
484 return l
490 return l
485
491
486 def hasfile(repo, subset, x):
492 def hasfile(repo, subset, x):
487 """``file(pattern)``
493 """``file(pattern)``
488 Changesets affecting files matched by pattern.
494 Changesets affecting files matched by pattern.
489 """
495 """
490 # i18n: "file" is a keyword
496 # i18n: "file" is a keyword
491 pat = getstring(x, _("file requires a pattern"))
497 pat = getstring(x, _("file requires a pattern"))
492 m = matchmod.match(repo.root, repo.getcwd(), [pat])
498 m = matchmod.match(repo.root, repo.getcwd(), [pat])
493 s = []
499 s = []
494 for r in subset:
500 for r in subset:
495 for f in repo[r].files():
501 for f in repo[r].files():
496 if m(f):
502 if m(f):
497 s.append(r)
503 s.append(r)
498 break
504 break
499 return s
505 return s
500
506
501 def head(repo, subset, x):
507 def head(repo, subset, x):
502 """``head()``
508 """``head()``
503 Changeset is a named branch head.
509 Changeset is a named branch head.
504 """
510 """
505 # i18n: "head" is a keyword
511 # i18n: "head" is a keyword
506 getargs(x, 0, 0, _("head takes no arguments"))
512 getargs(x, 0, 0, _("head takes no arguments"))
507 hs = set()
513 hs = set()
508 for b, ls in repo.branchmap().iteritems():
514 for b, ls in repo.branchmap().iteritems():
509 hs.update(repo[h].rev() for h in ls)
515 hs.update(repo[h].rev() for h in ls)
510 return [r for r in subset if r in hs]
516 return [r for r in subset if r in hs]
511
517
512 def heads(repo, subset, x):
518 def heads(repo, subset, x):
513 """``heads(set)``
519 """``heads(set)``
514 Members of set with no children in set.
520 Members of set with no children in set.
515 """
521 """
516 s = getset(repo, subset, x)
522 s = getset(repo, subset, x)
517 ps = set(parents(repo, subset, x))
523 ps = set(parents(repo, subset, x))
518 return [r for r in s if r not in ps]
524 return [r for r in s if r not in ps]
519
525
520 def keyword(repo, subset, x):
526 def keyword(repo, subset, x):
521 """``keyword(string)``
527 """``keyword(string)``
522 Search commit message, user name, and names of changed files for
528 Search commit message, user name, and names of changed files for
523 string. The match is case-insensitive.
529 string. The match is case-insensitive.
524 """
530 """
525 # i18n: "keyword" is a keyword
531 # i18n: "keyword" is a keyword
526 kw = encoding.lower(getstring(x, _("keyword requires a string")))
532 kw = encoding.lower(getstring(x, _("keyword requires a string")))
527 l = []
533 l = []
528 for r in subset:
534 for r in subset:
529 c = repo[r]
535 c = repo[r]
530 t = " ".join(c.files() + [c.user(), c.description()])
536 t = " ".join(c.files() + [c.user(), c.description()])
531 if kw in encoding.lower(t):
537 if kw in encoding.lower(t):
532 l.append(r)
538 l.append(r)
533 return l
539 return l
534
540
535 def limit(repo, subset, x):
541 def limit(repo, subset, x):
536 """``limit(set, [n])``
542 """``limit(set, [n])``
537 First n members of set, defaulting to 1.
543 First n members of set, defaulting to 1.
538 """
544 """
539 # i18n: "limit" is a keyword
545 # i18n: "limit" is a keyword
540 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
546 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
541 try:
547 try:
542 lim = 1
548 lim = 1
543 if len(l) == 2:
549 if len(l) == 2:
544 # i18n: "limit" is a keyword
550 # i18n: "limit" is a keyword
545 lim = int(getstring(l[1], _("limit requires a number")))
551 lim = int(getstring(l[1], _("limit requires a number")))
546 except (TypeError, ValueError):
552 except (TypeError, ValueError):
547 # i18n: "limit" is a keyword
553 # i18n: "limit" is a keyword
548 raise error.ParseError(_("limit expects a number"))
554 raise error.ParseError(_("limit expects a number"))
549 ss = set(subset)
555 ss = set(subset)
550 os = getset(repo, range(len(repo)), l[0])[:lim]
556 os = getset(repo, range(len(repo)), l[0])[:lim]
551 return [r for r in os if r in ss]
557 return [r for r in os if r in ss]
552
558
553 def last(repo, subset, x):
559 def last(repo, subset, x):
554 """``last(set, [n])``
560 """``last(set, [n])``
555 Last n members of set, defaulting to 1.
561 Last n members of set, defaulting to 1.
556 """
562 """
557 # i18n: "last" is a keyword
563 # i18n: "last" is a keyword
558 l = getargs(x, 1, 2, _("last requires one or two arguments"))
564 l = getargs(x, 1, 2, _("last requires one or two arguments"))
559 try:
565 try:
560 lim = 1
566 lim = 1
561 if len(l) == 2:
567 if len(l) == 2:
562 # i18n: "last" is a keyword
568 # i18n: "last" is a keyword
563 lim = int(getstring(l[1], _("last requires a number")))
569 lim = int(getstring(l[1], _("last requires a number")))
564 except (TypeError, ValueError):
570 except (TypeError, ValueError):
565 # i18n: "last" is a keyword
571 # i18n: "last" is a keyword
566 raise error.ParseError(_("last expects a number"))
572 raise error.ParseError(_("last expects a number"))
567 ss = set(subset)
573 ss = set(subset)
568 os = getset(repo, range(len(repo)), l[0])[-lim:]
574 os = getset(repo, range(len(repo)), l[0])[-lim:]
569 return [r for r in os if r in ss]
575 return [r for r in os if r in ss]
570
576
571 def maxrev(repo, subset, x):
577 def maxrev(repo, subset, x):
572 """``max(set)``
578 """``max(set)``
573 Changeset with highest revision number in set.
579 Changeset with highest revision number in set.
574 """
580 """
575 os = getset(repo, range(len(repo)), x)
581 os = getset(repo, range(len(repo)), x)
576 if os:
582 if os:
577 m = max(os)
583 m = max(os)
578 if m in subset:
584 if m in subset:
579 return [m]
585 return [m]
580 return []
586 return []
581
587
582 def merge(repo, subset, x):
588 def merge(repo, subset, x):
583 """``merge()``
589 """``merge()``
584 Changeset is a merge changeset.
590 Changeset is a merge changeset.
585 """
591 """
586 # i18n: "merge" is a keyword
592 # i18n: "merge" is a keyword
587 getargs(x, 0, 0, _("merge takes no arguments"))
593 getargs(x, 0, 0, _("merge takes no arguments"))
588 cl = repo.changelog
594 cl = repo.changelog
589 return [r for r in subset if cl.parentrevs(r)[1] != -1]
595 return [r for r in subset if cl.parentrevs(r)[1] != -1]
590
596
591 def minrev(repo, subset, x):
597 def minrev(repo, subset, x):
592 """``min(set)``
598 """``min(set)``
593 Changeset with lowest revision number in set.
599 Changeset with lowest revision number in set.
594 """
600 """
595 os = getset(repo, range(len(repo)), x)
601 os = getset(repo, range(len(repo)), x)
596 if os:
602 if os:
597 m = min(os)
603 m = min(os)
598 if m in subset:
604 if m in subset:
599 return [m]
605 return [m]
600 return []
606 return []
601
607
602 def modifies(repo, subset, x):
608 def modifies(repo, subset, x):
603 """``modifies(pattern)``
609 """``modifies(pattern)``
604 Changesets modifying files matched by pattern.
610 Changesets modifying files matched by pattern.
605 """
611 """
606 # i18n: "modifies" is a keyword
612 # i18n: "modifies" is a keyword
607 pat = getstring(x, _("modifies requires a pattern"))
613 pat = getstring(x, _("modifies requires a pattern"))
608 return checkstatus(repo, subset, pat, 0)
614 return checkstatus(repo, subset, pat, 0)
609
615
610 def node(repo, subset, x):
616 def node(repo, subset, x):
611 """``id(string)``
617 """``id(string)``
612 Revision non-ambiguously specified by the given hex string prefix.
618 Revision non-ambiguously specified by the given hex string prefix.
613 """
619 """
614 # i18n: "id" is a keyword
620 # i18n: "id" is a keyword
615 l = getargs(x, 1, 1, _("id requires one argument"))
621 l = getargs(x, 1, 1, _("id requires one argument"))
616 # i18n: "id" is a keyword
622 # i18n: "id" is a keyword
617 n = getstring(l[0], _("id requires a string"))
623 n = getstring(l[0], _("id requires a string"))
618 if len(n) == 40:
624 if len(n) == 40:
619 rn = repo[n].rev()
625 rn = repo[n].rev()
620 else:
626 else:
621 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
627 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
622 return [r for r in subset if r == rn]
628 return [r for r in subset if r == rn]
623
629
624 def outgoing(repo, subset, x):
630 def outgoing(repo, subset, x):
625 """``outgoing([path])``
631 """``outgoing([path])``
626 Changesets not found in the specified destination repository, or the
632 Changesets not found in the specified destination repository, or the
627 default push location.
633 default push location.
628 """
634 """
629 import hg # avoid start-up nasties
635 import hg # avoid start-up nasties
630 # i18n: "outgoing" is a keyword
636 # i18n: "outgoing" is a keyword
631 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
637 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
632 # i18n: "outgoing" is a keyword
638 # i18n: "outgoing" is a keyword
633 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
639 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
634 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
640 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
635 dest, branches = hg.parseurl(dest)
641 dest, branches = hg.parseurl(dest)
636 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
642 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
637 if revs:
643 if revs:
638 revs = [repo.lookup(rev) for rev in revs]
644 revs = [repo.lookup(rev) for rev in revs]
639 other = hg.peer(repo, {}, dest)
645 other = hg.peer(repo, {}, dest)
640 repo.ui.pushbuffer()
646 repo.ui.pushbuffer()
641 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
647 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
642 repo.ui.popbuffer()
648 repo.ui.popbuffer()
643 cl = repo.changelog
649 cl = repo.changelog
644 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
650 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
645 return [r for r in subset if r in o]
651 return [r for r in subset if r in o]
646
652
647 def p1(repo, subset, x):
653 def p1(repo, subset, x):
648 """``p1([set])``
654 """``p1([set])``
649 First parent of changesets in set, or the working directory.
655 First parent of changesets in set, or the working directory.
650 """
656 """
651 if x is None:
657 if x is None:
652 p = repo[x].p1().rev()
658 p = repo[x].p1().rev()
653 return [r for r in subset if r == p]
659 return [r for r in subset if r == p]
654
660
655 ps = set()
661 ps = set()
656 cl = repo.changelog
662 cl = repo.changelog
657 for r in getset(repo, range(len(repo)), x):
663 for r in getset(repo, range(len(repo)), x):
658 ps.add(cl.parentrevs(r)[0])
664 ps.add(cl.parentrevs(r)[0])
659 return [r for r in subset if r in ps]
665 return [r for r in subset if r in ps]
660
666
661 def p2(repo, subset, x):
667 def p2(repo, subset, x):
662 """``p2([set])``
668 """``p2([set])``
663 Second parent of changesets in set, or the working directory.
669 Second parent of changesets in set, or the working directory.
664 """
670 """
665 if x is None:
671 if x is None:
666 ps = repo[x].parents()
672 ps = repo[x].parents()
667 try:
673 try:
668 p = ps[1].rev()
674 p = ps[1].rev()
669 return [r for r in subset if r == p]
675 return [r for r in subset if r == p]
670 except IndexError:
676 except IndexError:
671 return []
677 return []
672
678
673 ps = set()
679 ps = set()
674 cl = repo.changelog
680 cl = repo.changelog
675 for r in getset(repo, range(len(repo)), x):
681 for r in getset(repo, range(len(repo)), x):
676 ps.add(cl.parentrevs(r)[1])
682 ps.add(cl.parentrevs(r)[1])
677 return [r for r in subset if r in ps]
683 return [r for r in subset if r in ps]
678
684
679 def parents(repo, subset, x):
685 def parents(repo, subset, x):
680 """``parents([set])``
686 """``parents([set])``
681 The set of all parents for all changesets in set, or the working directory.
687 The set of all parents for all changesets in set, or the working directory.
682 """
688 """
683 if x is None:
689 if x is None:
684 ps = tuple(p.rev() for p in repo[x].parents())
690 ps = tuple(p.rev() for p in repo[x].parents())
685 return [r for r in subset if r in ps]
691 return [r for r in subset if r in ps]
686
692
687 ps = set()
693 ps = set()
688 cl = repo.changelog
694 cl = repo.changelog
689 for r in getset(repo, range(len(repo)), x):
695 for r in getset(repo, range(len(repo)), x):
690 ps.update(cl.parentrevs(r))
696 ps.update(cl.parentrevs(r))
691 return [r for r in subset if r in ps]
697 return [r for r in subset if r in ps]
692
698
693 def parentspec(repo, subset, x, n):
699 def parentspec(repo, subset, x, n):
694 """``set^0``
700 """``set^0``
695 The set.
701 The set.
696 ``set^1`` (or ``set^``), ``set^2``
702 ``set^1`` (or ``set^``), ``set^2``
697 First or second parent, respectively, of all changesets in set.
703 First or second parent, respectively, of all changesets in set.
698 """
704 """
699 try:
705 try:
700 n = int(n[1])
706 n = int(n[1])
701 if n not in (0, 1, 2):
707 if n not in (0, 1, 2):
702 raise ValueError
708 raise ValueError
703 except (TypeError, ValueError):
709 except (TypeError, ValueError):
704 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
710 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
705 ps = set()
711 ps = set()
706 cl = repo.changelog
712 cl = repo.changelog
707 for r in getset(repo, subset, x):
713 for r in getset(repo, subset, x):
708 if n == 0:
714 if n == 0:
709 ps.add(r)
715 ps.add(r)
710 elif n == 1:
716 elif n == 1:
711 ps.add(cl.parentrevs(r)[0])
717 ps.add(cl.parentrevs(r)[0])
712 elif n == 2:
718 elif n == 2:
713 parents = cl.parentrevs(r)
719 parents = cl.parentrevs(r)
714 if len(parents) > 1:
720 if len(parents) > 1:
715 ps.add(parents[1])
721 ps.add(parents[1])
716 return [r for r in subset if r in ps]
722 return [r for r in subset if r in ps]
717
723
718 def present(repo, subset, x):
724 def present(repo, subset, x):
719 """``present(set)``
725 """``present(set)``
720 An empty set, if any revision in set isn't found; otherwise,
726 An empty set, if any revision in set isn't found; otherwise,
721 all revisions in set.
727 all revisions in set.
722 """
728 """
723 try:
729 try:
724 return getset(repo, subset, x)
730 return getset(repo, subset, x)
725 except error.RepoLookupError:
731 except error.RepoLookupError:
726 return []
732 return []
727
733
734 def public(repo, subset, x):
735 """``public()``
736 Changeset in public phase."""
737 getargs(x, 0, 0, _("public takes no arguments"))
738 return [r for r in subset if repo._phaserev[r] == phases.public]
739
728 def removes(repo, subset, x):
740 def removes(repo, subset, x):
729 """``removes(pattern)``
741 """``removes(pattern)``
730 Changesets which remove files matching pattern.
742 Changesets which remove files matching pattern.
731 """
743 """
732 # i18n: "removes" is a keyword
744 # i18n: "removes" is a keyword
733 pat = getstring(x, _("removes requires a pattern"))
745 pat = getstring(x, _("removes requires a pattern"))
734 return checkstatus(repo, subset, pat, 2)
746 return checkstatus(repo, subset, pat, 2)
735
747
736 def rev(repo, subset, x):
748 def rev(repo, subset, x):
737 """``rev(number)``
749 """``rev(number)``
738 Revision with the given numeric identifier.
750 Revision with the given numeric identifier.
739 """
751 """
740 # i18n: "rev" is a keyword
752 # i18n: "rev" is a keyword
741 l = getargs(x, 1, 1, _("rev requires one argument"))
753 l = getargs(x, 1, 1, _("rev requires one argument"))
742 try:
754 try:
743 # i18n: "rev" is a keyword
755 # i18n: "rev" is a keyword
744 l = int(getstring(l[0], _("rev requires a number")))
756 l = int(getstring(l[0], _("rev requires a number")))
745 except (TypeError, ValueError):
757 except (TypeError, ValueError):
746 # i18n: "rev" is a keyword
758 # i18n: "rev" is a keyword
747 raise error.ParseError(_("rev expects a number"))
759 raise error.ParseError(_("rev expects a number"))
748 return [r for r in subset if r == l]
760 return [r for r in subset if r == l]
749
761
750 def reverse(repo, subset, x):
762 def reverse(repo, subset, x):
751 """``reverse(set)``
763 """``reverse(set)``
752 Reverse order of set.
764 Reverse order of set.
753 """
765 """
754 l = getset(repo, subset, x)
766 l = getset(repo, subset, x)
755 l.reverse()
767 l.reverse()
756 return l
768 return l
757
769
758 def roots(repo, subset, x):
770 def roots(repo, subset, x):
759 """``roots(set)``
771 """``roots(set)``
760 Changesets with no parent changeset in set.
772 Changesets with no parent changeset in set.
761 """
773 """
762 s = getset(repo, subset, x)
774 s = getset(repo, subset, x)
763 cs = set(children(repo, subset, x))
775 cs = set(children(repo, subset, x))
764 return [r for r in s if r not in cs]
776 return [r for r in s if r not in cs]
765
777
778 def secret(repo, subset, x):
779 """``secret()``
780 Changeset in secret phase."""
781 getargs(x, 0, 0, _("secret takes no arguments"))
782 return [r for r in subset if repo._phaserev[r] == phases.secret]
783
766 def sort(repo, subset, x):
784 def sort(repo, subset, x):
767 """``sort(set[, [-]key...])``
785 """``sort(set[, [-]key...])``
768 Sort set by keys. The default sort order is ascending, specify a key
786 Sort set by keys. The default sort order is ascending, specify a key
769 as ``-key`` to sort in descending order.
787 as ``-key`` to sort in descending order.
770
788
771 The keys can be:
789 The keys can be:
772
790
773 - ``rev`` for the revision number,
791 - ``rev`` for the revision number,
774 - ``branch`` for the branch name,
792 - ``branch`` for the branch name,
775 - ``desc`` for the commit message (description),
793 - ``desc`` for the commit message (description),
776 - ``user`` for user name (``author`` can be used as an alias),
794 - ``user`` for user name (``author`` can be used as an alias),
777 - ``date`` for the commit date
795 - ``date`` for the commit date
778 """
796 """
779 # i18n: "sort" is a keyword
797 # i18n: "sort" is a keyword
780 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
798 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
781 keys = "rev"
799 keys = "rev"
782 if len(l) == 2:
800 if len(l) == 2:
783 keys = getstring(l[1], _("sort spec must be a string"))
801 keys = getstring(l[1], _("sort spec must be a string"))
784
802
785 s = l[0]
803 s = l[0]
786 keys = keys.split()
804 keys = keys.split()
787 l = []
805 l = []
788 def invert(s):
806 def invert(s):
789 return "".join(chr(255 - ord(c)) for c in s)
807 return "".join(chr(255 - ord(c)) for c in s)
790 for r in getset(repo, subset, s):
808 for r in getset(repo, subset, s):
791 c = repo[r]
809 c = repo[r]
792 e = []
810 e = []
793 for k in keys:
811 for k in keys:
794 if k == 'rev':
812 if k == 'rev':
795 e.append(r)
813 e.append(r)
796 elif k == '-rev':
814 elif k == '-rev':
797 e.append(-r)
815 e.append(-r)
798 elif k == 'branch':
816 elif k == 'branch':
799 e.append(c.branch())
817 e.append(c.branch())
800 elif k == '-branch':
818 elif k == '-branch':
801 e.append(invert(c.branch()))
819 e.append(invert(c.branch()))
802 elif k == 'desc':
820 elif k == 'desc':
803 e.append(c.description())
821 e.append(c.description())
804 elif k == '-desc':
822 elif k == '-desc':
805 e.append(invert(c.description()))
823 e.append(invert(c.description()))
806 elif k in 'user author':
824 elif k in 'user author':
807 e.append(c.user())
825 e.append(c.user())
808 elif k in '-user -author':
826 elif k in '-user -author':
809 e.append(invert(c.user()))
827 e.append(invert(c.user()))
810 elif k == 'date':
828 elif k == 'date':
811 e.append(c.date()[0])
829 e.append(c.date()[0])
812 elif k == '-date':
830 elif k == '-date':
813 e.append(-c.date()[0])
831 e.append(-c.date()[0])
814 else:
832 else:
815 raise error.ParseError(_("unknown sort key %r") % k)
833 raise error.ParseError(_("unknown sort key %r") % k)
816 e.append(r)
834 e.append(r)
817 l.append(e)
835 l.append(e)
818 l.sort()
836 l.sort()
819 return [e[-1] for e in l]
837 return [e[-1] for e in l]
820
838
821 def tag(repo, subset, x):
839 def tag(repo, subset, x):
822 """``tag([name])``
840 """``tag([name])``
823 The specified tag by name, or all tagged revisions if no name is given.
841 The specified tag by name, or all tagged revisions if no name is given.
824 """
842 """
825 # i18n: "tag" is a keyword
843 # i18n: "tag" is a keyword
826 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
844 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
827 cl = repo.changelog
845 cl = repo.changelog
828 if args:
846 if args:
829 tn = getstring(args[0],
847 tn = getstring(args[0],
830 # i18n: "tag" is a keyword
848 # i18n: "tag" is a keyword
831 _('the argument to tag must be a string'))
849 _('the argument to tag must be a string'))
832 if not repo.tags().get(tn, None):
850 if not repo.tags().get(tn, None):
833 raise util.Abort(_("tag '%s' does not exist") % tn)
851 raise util.Abort(_("tag '%s' does not exist") % tn)
834 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
852 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
835 else:
853 else:
836 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
854 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
837 return [r for r in subset if r in s]
855 return [r for r in subset if r in s]
838
856
839 def tagged(repo, subset, x):
857 def tagged(repo, subset, x):
840 return tag(repo, subset, x)
858 return tag(repo, subset, x)
841
859
842 def user(repo, subset, x):
860 def user(repo, subset, x):
843 """``user(string)``
861 """``user(string)``
844 User name contains string. The match is case-insensitive.
862 User name contains string. The match is case-insensitive.
845 """
863 """
846 return author(repo, subset, x)
864 return author(repo, subset, x)
847
865
848 symbols = {
866 symbols = {
849 "adds": adds,
867 "adds": adds,
850 "all": getall,
868 "all": getall,
851 "ancestor": ancestor,
869 "ancestor": ancestor,
852 "ancestors": ancestors,
870 "ancestors": ancestors,
853 "author": author,
871 "author": author,
854 "bisect": bisect,
872 "bisect": bisect,
855 "bisected": bisected,
873 "bisected": bisected,
856 "bookmark": bookmark,
874 "bookmark": bookmark,
857 "branch": branch,
875 "branch": branch,
858 "children": children,
876 "children": children,
859 "closed": closed,
877 "closed": closed,
860 "contains": contains,
878 "contains": contains,
861 "date": date,
879 "date": date,
862 "desc": desc,
880 "desc": desc,
863 "descendants": descendants,
881 "descendants": descendants,
882 "draft": draft,
864 "file": hasfile,
883 "file": hasfile,
865 "filelog": filelog,
884 "filelog": filelog,
866 "first": first,
885 "first": first,
867 "follow": follow,
886 "follow": follow,
868 "grep": grep,
887 "grep": grep,
869 "head": head,
888 "head": head,
870 "heads": heads,
889 "heads": heads,
871 "id": node,
890 "id": node,
872 "keyword": keyword,
891 "keyword": keyword,
873 "last": last,
892 "last": last,
874 "limit": limit,
893 "limit": limit,
875 "max": maxrev,
894 "max": maxrev,
876 "merge": merge,
895 "merge": merge,
877 "min": minrev,
896 "min": minrev,
878 "modifies": modifies,
897 "modifies": modifies,
879 "outgoing": outgoing,
898 "outgoing": outgoing,
880 "p1": p1,
899 "p1": p1,
881 "p2": p2,
900 "p2": p2,
882 "parents": parents,
901 "parents": parents,
883 "present": present,
902 "present": present,
903 "public": public,
884 "removes": removes,
904 "removes": removes,
885 "rev": rev,
905 "rev": rev,
886 "reverse": reverse,
906 "reverse": reverse,
887 "roots": roots,
907 "roots": roots,
888 "sort": sort,
908 "sort": sort,
909 "secret": secret,
889 "tag": tag,
910 "tag": tag,
890 "tagged": tagged,
911 "tagged": tagged,
891 "user": user,
912 "user": user,
892 }
913 }
893
914
894 methods = {
915 methods = {
895 "range": rangeset,
916 "range": rangeset,
896 "string": stringset,
917 "string": stringset,
897 "symbol": symbolset,
918 "symbol": symbolset,
898 "and": andset,
919 "and": andset,
899 "or": orset,
920 "or": orset,
900 "not": notset,
921 "not": notset,
901 "list": listset,
922 "list": listset,
902 "func": func,
923 "func": func,
903 "ancestor": ancestorspec,
924 "ancestor": ancestorspec,
904 "parent": parentspec,
925 "parent": parentspec,
905 "parentpost": p1,
926 "parentpost": p1,
906 }
927 }
907
928
908 def optimize(x, small):
929 def optimize(x, small):
909 if x is None:
930 if x is None:
910 return 0, x
931 return 0, x
911
932
912 smallbonus = 1
933 smallbonus = 1
913 if small:
934 if small:
914 smallbonus = .5
935 smallbonus = .5
915
936
916 op = x[0]
937 op = x[0]
917 if op == 'minus':
938 if op == 'minus':
918 return optimize(('and', x[1], ('not', x[2])), small)
939 return optimize(('and', x[1], ('not', x[2])), small)
919 elif op == 'dagrange':
940 elif op == 'dagrange':
920 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
941 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
921 ('func', ('symbol', 'ancestors'), x[2])), small)
942 ('func', ('symbol', 'ancestors'), x[2])), small)
922 elif op == 'dagrangepre':
943 elif op == 'dagrangepre':
923 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
944 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
924 elif op == 'dagrangepost':
945 elif op == 'dagrangepost':
925 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
946 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
926 elif op == 'rangepre':
947 elif op == 'rangepre':
927 return optimize(('range', ('string', '0'), x[1]), small)
948 return optimize(('range', ('string', '0'), x[1]), small)
928 elif op == 'rangepost':
949 elif op == 'rangepost':
929 return optimize(('range', x[1], ('string', 'tip')), small)
950 return optimize(('range', x[1], ('string', 'tip')), small)
930 elif op == 'negate':
951 elif op == 'negate':
931 return optimize(('string',
952 return optimize(('string',
932 '-' + getstring(x[1], _("can't negate that"))), small)
953 '-' + getstring(x[1], _("can't negate that"))), small)
933 elif op in 'string symbol negate':
954 elif op in 'string symbol negate':
934 return smallbonus, x # single revisions are small
955 return smallbonus, x # single revisions are small
935 elif op == 'and' or op == 'dagrange':
956 elif op == 'and' or op == 'dagrange':
936 wa, ta = optimize(x[1], True)
957 wa, ta = optimize(x[1], True)
937 wb, tb = optimize(x[2], True)
958 wb, tb = optimize(x[2], True)
938 w = min(wa, wb)
959 w = min(wa, wb)
939 if wa > wb:
960 if wa > wb:
940 return w, (op, tb, ta)
961 return w, (op, tb, ta)
941 return w, (op, ta, tb)
962 return w, (op, ta, tb)
942 elif op == 'or':
963 elif op == 'or':
943 wa, ta = optimize(x[1], False)
964 wa, ta = optimize(x[1], False)
944 wb, tb = optimize(x[2], False)
965 wb, tb = optimize(x[2], False)
945 if wb < wa:
966 if wb < wa:
946 wb, wa = wa, wb
967 wb, wa = wa, wb
947 return max(wa, wb), (op, ta, tb)
968 return max(wa, wb), (op, ta, tb)
948 elif op == 'not':
969 elif op == 'not':
949 o = optimize(x[1], not small)
970 o = optimize(x[1], not small)
950 return o[0], (op, o[1])
971 return o[0], (op, o[1])
951 elif op == 'parentpost':
972 elif op == 'parentpost':
952 o = optimize(x[1], small)
973 o = optimize(x[1], small)
953 return o[0], (op, o[1])
974 return o[0], (op, o[1])
954 elif op == 'group':
975 elif op == 'group':
955 return optimize(x[1], small)
976 return optimize(x[1], small)
956 elif op in 'range list parent ancestorspec':
977 elif op in 'range list parent ancestorspec':
957 if op == 'parent':
978 if op == 'parent':
958 # x^:y means (x^) : y, not x ^ (:y)
979 # x^:y means (x^) : y, not x ^ (:y)
959 post = ('parentpost', x[1])
980 post = ('parentpost', x[1])
960 if x[2][0] == 'dagrangepre':
981 if x[2][0] == 'dagrangepre':
961 return optimize(('dagrange', post, x[2][1]), small)
982 return optimize(('dagrange', post, x[2][1]), small)
962 elif x[2][0] == 'rangepre':
983 elif x[2][0] == 'rangepre':
963 return optimize(('range', post, x[2][1]), small)
984 return optimize(('range', post, x[2][1]), small)
964
985
965 wa, ta = optimize(x[1], small)
986 wa, ta = optimize(x[1], small)
966 wb, tb = optimize(x[2], small)
987 wb, tb = optimize(x[2], small)
967 return wa + wb, (op, ta, tb)
988 return wa + wb, (op, ta, tb)
968 elif op == 'func':
989 elif op == 'func':
969 f = getstring(x[1], _("not a symbol"))
990 f = getstring(x[1], _("not a symbol"))
970 wa, ta = optimize(x[2], small)
991 wa, ta = optimize(x[2], small)
971 if f in ("author branch closed date desc file grep keyword "
992 if f in ("author branch closed date desc file grep keyword "
972 "outgoing user"):
993 "outgoing user"):
973 w = 10 # slow
994 w = 10 # slow
974 elif f in "modifies adds removes":
995 elif f in "modifies adds removes":
975 w = 30 # slower
996 w = 30 # slower
976 elif f == "contains":
997 elif f == "contains":
977 w = 100 # very slow
998 w = 100 # very slow
978 elif f == "ancestor":
999 elif f == "ancestor":
979 w = 1 * smallbonus
1000 w = 1 * smallbonus
980 elif f in "reverse limit first":
1001 elif f in "reverse limit first":
981 w = 0
1002 w = 0
982 elif f in "sort":
1003 elif f in "sort":
983 w = 10 # assume most sorts look at changelog
1004 w = 10 # assume most sorts look at changelog
984 else:
1005 else:
985 w = 1
1006 w = 1
986 return w + wa, (op, x[1], ta)
1007 return w + wa, (op, x[1], ta)
987 return 1, x
1008 return 1, x
988
1009
989 class revsetalias(object):
1010 class revsetalias(object):
990 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1011 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
991 args = None
1012 args = None
992
1013
993 def __init__(self, name, value):
1014 def __init__(self, name, value):
994 '''Aliases like:
1015 '''Aliases like:
995
1016
996 h = heads(default)
1017 h = heads(default)
997 b($1) = ancestors($1) - ancestors(default)
1018 b($1) = ancestors($1) - ancestors(default)
998 '''
1019 '''
999 if isinstance(name, tuple): # parameter substitution
1020 if isinstance(name, tuple): # parameter substitution
1000 self.tree = name
1021 self.tree = name
1001 self.replacement = value
1022 self.replacement = value
1002 else: # alias definition
1023 else: # alias definition
1003 m = self.funcre.search(name)
1024 m = self.funcre.search(name)
1004 if m:
1025 if m:
1005 self.tree = ('func', ('symbol', m.group(1)))
1026 self.tree = ('func', ('symbol', m.group(1)))
1006 self.args = [x.strip() for x in m.group(2).split(',')]
1027 self.args = [x.strip() for x in m.group(2).split(',')]
1007 for arg in self.args:
1028 for arg in self.args:
1008 value = value.replace(arg, repr(arg))
1029 value = value.replace(arg, repr(arg))
1009 else:
1030 else:
1010 self.tree = ('symbol', name)
1031 self.tree = ('symbol', name)
1011
1032
1012 self.replacement, pos = parse(value)
1033 self.replacement, pos = parse(value)
1013 if pos != len(value):
1034 if pos != len(value):
1014 raise error.ParseError(_('invalid token'), pos)
1035 raise error.ParseError(_('invalid token'), pos)
1015
1036
1016 def process(self, tree):
1037 def process(self, tree):
1017 if isinstance(tree, tuple):
1038 if isinstance(tree, tuple):
1018 if self.args is None:
1039 if self.args is None:
1019 if tree == self.tree:
1040 if tree == self.tree:
1020 return self.replacement
1041 return self.replacement
1021 elif tree[:2] == self.tree:
1042 elif tree[:2] == self.tree:
1022 l = getlist(tree[2])
1043 l = getlist(tree[2])
1023 if len(l) != len(self.args):
1044 if len(l) != len(self.args):
1024 raise error.ParseError(
1045 raise error.ParseError(
1025 _('invalid number of arguments: %s') % len(l))
1046 _('invalid number of arguments: %s') % len(l))
1026 result = self.replacement
1047 result = self.replacement
1027 for a, v in zip(self.args, l):
1048 for a, v in zip(self.args, l):
1028 valalias = revsetalias(('string', a), v)
1049 valalias = revsetalias(('string', a), v)
1029 result = valalias.process(result)
1050 result = valalias.process(result)
1030 return result
1051 return result
1031 return tuple(map(self.process, tree))
1052 return tuple(map(self.process, tree))
1032 return tree
1053 return tree
1033
1054
1034 def findaliases(ui, tree):
1055 def findaliases(ui, tree):
1035 for k, v in ui.configitems('revsetalias'):
1056 for k, v in ui.configitems('revsetalias'):
1036 alias = revsetalias(k, v)
1057 alias = revsetalias(k, v)
1037 tree = alias.process(tree)
1058 tree = alias.process(tree)
1038 return tree
1059 return tree
1039
1060
1040 parse = parser.parser(tokenize, elements).parse
1061 parse = parser.parser(tokenize, elements).parse
1041
1062
1042 def match(ui, spec):
1063 def match(ui, spec):
1043 if not spec:
1064 if not spec:
1044 raise error.ParseError(_("empty query"))
1065 raise error.ParseError(_("empty query"))
1045 tree, pos = parse(spec)
1066 tree, pos = parse(spec)
1046 if (pos != len(spec)):
1067 if (pos != len(spec)):
1047 raise error.ParseError(_("invalid token"), pos)
1068 raise error.ParseError(_("invalid token"), pos)
1048 if ui:
1069 if ui:
1049 tree = findaliases(ui, tree)
1070 tree = findaliases(ui, tree)
1050 weight, tree = optimize(tree, True)
1071 weight, tree = optimize(tree, True)
1051 def mfunc(repo, subset):
1072 def mfunc(repo, subset):
1052 return getset(repo, subset, tree)
1073 return getset(repo, subset, tree)
1053 return mfunc
1074 return mfunc
1054
1075
1055 def formatspec(expr, *args):
1076 def formatspec(expr, *args):
1056 '''
1077 '''
1057 This is a convenience function for using revsets internally, and
1078 This is a convenience function for using revsets internally, and
1058 escapes arguments appropriately. Aliases are intentionally ignored
1079 escapes arguments appropriately. Aliases are intentionally ignored
1059 so that intended expression behavior isn't accidentally subverted.
1080 so that intended expression behavior isn't accidentally subverted.
1060
1081
1061 Supported arguments:
1082 Supported arguments:
1062
1083
1063 %r = revset expression, parenthesized
1084 %r = revset expression, parenthesized
1064 %d = int(arg), no quoting
1085 %d = int(arg), no quoting
1065 %s = string(arg), escaped and single-quoted
1086 %s = string(arg), escaped and single-quoted
1066 %b = arg.branch(), escaped and single-quoted
1087 %b = arg.branch(), escaped and single-quoted
1067 %n = hex(arg), single-quoted
1088 %n = hex(arg), single-quoted
1068 %% = a literal '%'
1089 %% = a literal '%'
1069
1090
1070 Prefixing the type with 'l' specifies a parenthesized list of that type.
1091 Prefixing the type with 'l' specifies a parenthesized list of that type.
1071
1092
1072 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1093 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1073 '(10 or 11):: and ((this()) or (that()))'
1094 '(10 or 11):: and ((this()) or (that()))'
1074 >>> formatspec('%d:: and not %d::', 10, 20)
1095 >>> formatspec('%d:: and not %d::', 10, 20)
1075 '10:: and not 20::'
1096 '10:: and not 20::'
1076 >>> formatspec('%ld or %ld', [], [1])
1097 >>> formatspec('%ld or %ld', [], [1])
1077 '(0-0) or 1'
1098 '(0-0) or 1'
1078 >>> formatspec('keyword(%s)', 'foo\\xe9')
1099 >>> formatspec('keyword(%s)', 'foo\\xe9')
1079 "keyword('foo\\\\xe9')"
1100 "keyword('foo\\\\xe9')"
1080 >>> b = lambda: 'default'
1101 >>> b = lambda: 'default'
1081 >>> b.branch = b
1102 >>> b.branch = b
1082 >>> formatspec('branch(%b)', b)
1103 >>> formatspec('branch(%b)', b)
1083 "branch('default')"
1104 "branch('default')"
1084 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1105 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1085 "root((('a' or 'b') or ('c' or 'd')))"
1106 "root((('a' or 'b') or ('c' or 'd')))"
1086 '''
1107 '''
1087
1108
1088 def quote(s):
1109 def quote(s):
1089 return repr(str(s))
1110 return repr(str(s))
1090
1111
1091 def argtype(c, arg):
1112 def argtype(c, arg):
1092 if c == 'd':
1113 if c == 'd':
1093 return str(int(arg))
1114 return str(int(arg))
1094 elif c == 's':
1115 elif c == 's':
1095 return quote(arg)
1116 return quote(arg)
1096 elif c == 'r':
1117 elif c == 'r':
1097 parse(arg) # make sure syntax errors are confined
1118 parse(arg) # make sure syntax errors are confined
1098 return '(%s)' % arg
1119 return '(%s)' % arg
1099 elif c == 'n':
1120 elif c == 'n':
1100 return quote(nodemod.hex(arg))
1121 return quote(nodemod.hex(arg))
1101 elif c == 'b':
1122 elif c == 'b':
1102 return quote(arg.branch())
1123 return quote(arg.branch())
1103
1124
1104 def listexp(s, t):
1125 def listexp(s, t):
1105 "balance a list s of type t to limit parse tree depth"
1126 "balance a list s of type t to limit parse tree depth"
1106 l = len(s)
1127 l = len(s)
1107 if l == 0:
1128 if l == 0:
1108 return '(0-0)' # a minimal way to represent an empty set
1129 return '(0-0)' # a minimal way to represent an empty set
1109 if l == 1:
1130 if l == 1:
1110 return argtype(t, s[0])
1131 return argtype(t, s[0])
1111 m = l // 2
1132 m = l // 2
1112 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1133 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1113
1134
1114 ret = ''
1135 ret = ''
1115 pos = 0
1136 pos = 0
1116 arg = 0
1137 arg = 0
1117 while pos < len(expr):
1138 while pos < len(expr):
1118 c = expr[pos]
1139 c = expr[pos]
1119 if c == '%':
1140 if c == '%':
1120 pos += 1
1141 pos += 1
1121 d = expr[pos]
1142 d = expr[pos]
1122 if d == '%':
1143 if d == '%':
1123 ret += d
1144 ret += d
1124 elif d in 'dsnbr':
1145 elif d in 'dsnbr':
1125 ret += argtype(d, args[arg])
1146 ret += argtype(d, args[arg])
1126 arg += 1
1147 arg += 1
1127 elif d == 'l':
1148 elif d == 'l':
1128 # a list of some type
1149 # a list of some type
1129 pos += 1
1150 pos += 1
1130 d = expr[pos]
1151 d = expr[pos]
1131 ret += listexp(list(args[arg]), d)
1152 ret += listexp(list(args[arg]), d)
1132 arg += 1
1153 arg += 1
1133 else:
1154 else:
1134 raise util.Abort('unexpected revspec format character %s' % d)
1155 raise util.Abort('unexpected revspec format character %s' % d)
1135 else:
1156 else:
1136 ret += c
1157 ret += c
1137 pos += 1
1158 pos += 1
1138
1159
1139 return ret
1160 return ret
1140
1161
1141 # tell hggettext to extract docstrings from these functions:
1162 # tell hggettext to extract docstrings from these functions:
1142 i18nfunctions = symbols.values()
1163 i18nfunctions = symbols.values()
@@ -1,136 +1,152
1 $ alias hglog='hg log --template "{rev} {phase} {desc}\n"'
1 $ alias hglog='hg log --template "{rev} {phase} {desc}\n"'
2 $ mkcommit() {
2 $ mkcommit() {
3 > echo "$1" > "$1"
3 > echo "$1" > "$1"
4 > hg add "$1"
4 > hg add "$1"
5 > message="$1"
5 > message="$1"
6 > shift
6 > shift
7 > hg ci -m "$message" $*
7 > hg ci -m "$message" $*
8 > }
8 > }
9
9
10 $ hg init initialrepo
10 $ hg init initialrepo
11 $ cd initialrepo
11 $ cd initialrepo
12 $ mkcommit A
12 $ mkcommit A
13
13
14 New commit are draft by default
14 New commit are draft by default
15
15
16 $ hglog
16 $ hglog
17 0 1 A
17 0 1 A
18
18
19 Following commit are draft too
19 Following commit are draft too
20
20
21 $ mkcommit B
21 $ mkcommit B
22
22
23 $ hglog
23 $ hglog
24 1 1 B
24 1 1 B
25 0 1 A
25 0 1 A
26
26
27 Draft commit are properly created over public one:
27 Draft commit are properly created over public one:
28
28
29 $ hg pull -q . # XXX use the dedicated phase command once available
29 $ hg pull -q . # XXX use the dedicated phase command once available
30 $ hglog
30 $ hglog
31 1 0 B
31 1 0 B
32 0 0 A
32 0 0 A
33
33
34 $ mkcommit C
34 $ mkcommit C
35 $ mkcommit D
35 $ mkcommit D
36
36
37 $ hglog
37 $ hglog
38 3 1 D
38 3 1 D
39 2 1 C
39 2 1 C
40 1 0 B
40 1 0 B
41 0 0 A
41 0 0 A
42
42
43 Test creating changeset as secret
43 Test creating changeset as secret
44
44
45 $ mkcommit E --config phases.new-commit=2
45 $ mkcommit E --config phases.new-commit=2
46 $ hglog
46 $ hglog
47 4 2 E
47 4 2 E
48 3 1 D
48 3 1 D
49 2 1 C
49 2 1 C
50 1 0 B
50 1 0 B
51 0 0 A
51 0 0 A
52
52
53 Test the secret property is inherited
53 Test the secret property is inherited
54
54
55 $ mkcommit H
55 $ mkcommit H
56 $ hglog
56 $ hglog
57 5 2 H
57 5 2 H
58 4 2 E
58 4 2 E
59 3 1 D
59 3 1 D
60 2 1 C
60 2 1 C
61 1 0 B
61 1 0 B
62 0 0 A
62 0 0 A
63
63
64 Even on merge
64 Even on merge
65
65
66 $ hg up -q 1
66 $ hg up -q 1
67 $ mkcommit "B'"
67 $ mkcommit "B'"
68 created new head
68 created new head
69 $ hglog
69 $ hglog
70 6 1 B'
70 6 1 B'
71 5 2 H
71 5 2 H
72 4 2 E
72 4 2 E
73 3 1 D
73 3 1 D
74 2 1 C
74 2 1 C
75 1 0 B
75 1 0 B
76 0 0 A
76 0 0 A
77 $ hg merge 4 # E
77 $ hg merge 4 # E
78 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 (branch merge, don't forget to commit)
79 (branch merge, don't forget to commit)
80 $ hg ci -m "merge B' and E"
80 $ hg ci -m "merge B' and E"
81 $ hglog
81 $ hglog
82 7 2 merge B' and E
82 7 2 merge B' and E
83 6 1 B'
83 6 1 B'
84 5 2 H
84 5 2 H
85 4 2 E
85 4 2 E
86 3 1 D
86 3 1 D
87 2 1 C
87 2 1 C
88 1 0 B
88 1 0 B
89 0 0 A
89 0 0 A
90
90
91 Test secret changeset are not pushed
91 Test secret changeset are not pushed
92
92
93 $ hg init ../push-dest
93 $ hg init ../push-dest
94 $ hg push ../push-dest -f # force because we push multiple heads
94 $ hg push ../push-dest -f # force because we push multiple heads
95 pushing to ../push-dest
95 pushing to ../push-dest
96 searching for changes
96 searching for changes
97 adding changesets
97 adding changesets
98 adding manifests
98 adding manifests
99 adding file changes
99 adding file changes
100 added 5 changesets with 5 changes to 5 files (+1 heads)
100 added 5 changesets with 5 changes to 5 files (+1 heads)
101 $ hglog
101 $ hglog
102 7 2 merge B' and E
102 7 2 merge B' and E
103 6 0 B'
103 6 0 B'
104 5 2 H
104 5 2 H
105 4 2 E
105 4 2 E
106 3 0 D
106 3 0 D
107 2 0 C
107 2 0 C
108 1 0 B
108 1 0 B
109 0 0 A
109 0 0 A
110 $ cd ../push-dest
110 $ cd ../push-dest
111 $ hglog
111 $ hglog
112 4 0 B'
112 4 0 B'
113 3 0 D
113 3 0 D
114 2 0 C
114 2 0 C
115 1 0 B
115 1 0 B
116 0 0 A
116 0 0 A
117 $ cd ..
117 $ cd ..
118
118
119 Test secret changeset are not pull
119 Test secret changeset are not pull
120
120
121 $ hg init pull-dest
121 $ hg init pull-dest
122 $ cd pull-dest
122 $ cd pull-dest
123 $ hg pull ../initialrepo
123 $ hg pull ../initialrepo
124 pulling from ../initialrepo
124 pulling from ../initialrepo
125 requesting all changes
125 requesting all changes
126 adding changesets
126 adding changesets
127 adding manifests
127 adding manifests
128 adding file changes
128 adding file changes
129 added 5 changesets with 5 changes to 5 files (+1 heads)
129 added 5 changesets with 5 changes to 5 files (+1 heads)
130 (run 'hg heads' to see heads, 'hg merge' to merge)
130 (run 'hg heads' to see heads, 'hg merge' to merge)
131 $ hglog
131 $ hglog
132 4 0 B'
132 4 0 B'
133 3 0 D
133 3 0 D
134 2 0 C
134 2 0 C
135 1 0 B
135 1 0 B
136 0 0 A
136 0 0 A
137 $ cd ..
138
139 Test revset
140
141 $ cd initialrepo
142 $ hglog -r 'public()'
143 0 0 A
144 1 0 B
145 2 0 C
146 3 0 D
147 6 0 B'
148 $ hglog -r 'draft()'
149 $ hglog -r 'secret()'
150 4 2 E
151 5 2 H
152 7 2 merge B' and E
General Comments 0
You need to be logged in to leave comments. Login now