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