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