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