##// END OF EJS Templates
revset.bisect: add new 'pruned' set to the bisect keyword...
"Yann E. MORIN" -
r15137:91f93dcd default
parent child Browse files
Show More
@@ -1,195 +1,199 b''
1 # changelog bisection for mercurial
1 # changelog bisection for mercurial
2 #
2 #
3 # Copyright 2007 Matt Mackall
3 # Copyright 2007 Matt Mackall
4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
5 #
5 #
6 # Inspired by git bisect, extension skeleton taken from mq.py.
6 # Inspired by git bisect, extension skeleton taken from mq.py.
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10
10
11 import os, error
11 import os, error
12 from i18n import _
12 from i18n import _
13 from node import short, hex
13 from node import short, hex
14 import util
14 import util
15
15
16 def bisect(changelog, state):
16 def bisect(changelog, state):
17 """find the next node (if any) for testing during a bisect search.
17 """find the next node (if any) for testing during a bisect search.
18 returns a (nodes, number, good) tuple.
18 returns a (nodes, number, good) tuple.
19
19
20 'nodes' is the final result of the bisect if 'number' is 0.
20 'nodes' is the final result of the bisect if 'number' is 0.
21 Otherwise 'number' indicates the remaining possible candidates for
21 Otherwise 'number' indicates the remaining possible candidates for
22 the search and 'nodes' contains the next bisect target.
22 the search and 'nodes' contains the next bisect target.
23 'good' is True if bisect is searching for a first good changeset, False
23 'good' is True if bisect is searching for a first good changeset, False
24 if searching for a first bad one.
24 if searching for a first bad one.
25 """
25 """
26
26
27 clparents = changelog.parentrevs
27 clparents = changelog.parentrevs
28 skip = set([changelog.rev(n) for n in state['skip']])
28 skip = set([changelog.rev(n) for n in state['skip']])
29
29
30 def buildancestors(bad, good):
30 def buildancestors(bad, good):
31 # only the earliest bad revision matters
31 # only the earliest bad revision matters
32 badrev = min([changelog.rev(n) for n in bad])
32 badrev = min([changelog.rev(n) for n in bad])
33 goodrevs = [changelog.rev(n) for n in good]
33 goodrevs = [changelog.rev(n) for n in good]
34 goodrev = min(goodrevs)
34 goodrev = min(goodrevs)
35 # build visit array
35 # build visit array
36 ancestors = [None] * (len(changelog) + 1) # an extra for [-1]
36 ancestors = [None] * (len(changelog) + 1) # an extra for [-1]
37
37
38 # set nodes descended from goodrevs
38 # set nodes descended from goodrevs
39 for rev in goodrevs:
39 for rev in goodrevs:
40 ancestors[rev] = []
40 ancestors[rev] = []
41 for rev in xrange(goodrev + 1, len(changelog)):
41 for rev in xrange(goodrev + 1, len(changelog)):
42 for prev in clparents(rev):
42 for prev in clparents(rev):
43 if ancestors[prev] == []:
43 if ancestors[prev] == []:
44 ancestors[rev] = []
44 ancestors[rev] = []
45
45
46 # clear good revs from array
46 # clear good revs from array
47 for rev in goodrevs:
47 for rev in goodrevs:
48 ancestors[rev] = None
48 ancestors[rev] = None
49 for rev in xrange(len(changelog), goodrev, -1):
49 for rev in xrange(len(changelog), goodrev, -1):
50 if ancestors[rev] is None:
50 if ancestors[rev] is None:
51 for prev in clparents(rev):
51 for prev in clparents(rev):
52 ancestors[prev] = None
52 ancestors[prev] = None
53
53
54 if ancestors[badrev] is None:
54 if ancestors[badrev] is None:
55 return badrev, None
55 return badrev, None
56 return badrev, ancestors
56 return badrev, ancestors
57
57
58 good = False
58 good = False
59 badrev, ancestors = buildancestors(state['bad'], state['good'])
59 badrev, ancestors = buildancestors(state['bad'], state['good'])
60 if not ancestors: # looking for bad to good transition?
60 if not ancestors: # looking for bad to good transition?
61 good = True
61 good = True
62 badrev, ancestors = buildancestors(state['good'], state['bad'])
62 badrev, ancestors = buildancestors(state['good'], state['bad'])
63 bad = changelog.node(badrev)
63 bad = changelog.node(badrev)
64 if not ancestors: # now we're confused
64 if not ancestors: # now we're confused
65 if len(state['bad']) == 1 and len(state['good']) == 1:
65 if len(state['bad']) == 1 and len(state['good']) == 1:
66 raise util.Abort(_("starting revisions are not directly related"))
66 raise util.Abort(_("starting revisions are not directly related"))
67 raise util.Abort(_("inconsistent state, %s:%s is good and bad")
67 raise util.Abort(_("inconsistent state, %s:%s is good and bad")
68 % (badrev, short(bad)))
68 % (badrev, short(bad)))
69
69
70 # build children dict
70 # build children dict
71 children = {}
71 children = {}
72 visit = [badrev]
72 visit = [badrev]
73 candidates = []
73 candidates = []
74 while visit:
74 while visit:
75 rev = visit.pop(0)
75 rev = visit.pop(0)
76 if ancestors[rev] == []:
76 if ancestors[rev] == []:
77 candidates.append(rev)
77 candidates.append(rev)
78 for prev in clparents(rev):
78 for prev in clparents(rev):
79 if prev != -1:
79 if prev != -1:
80 if prev in children:
80 if prev in children:
81 children[prev].append(rev)
81 children[prev].append(rev)
82 else:
82 else:
83 children[prev] = [rev]
83 children[prev] = [rev]
84 visit.append(prev)
84 visit.append(prev)
85
85
86 candidates.sort()
86 candidates.sort()
87 # have we narrowed it down to one entry?
87 # have we narrowed it down to one entry?
88 # or have all other possible candidates besides 'bad' have been skipped?
88 # or have all other possible candidates besides 'bad' have been skipped?
89 tot = len(candidates)
89 tot = len(candidates)
90 unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
90 unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
91 if tot == 1 or not unskipped:
91 if tot == 1 or not unskipped:
92 return ([changelog.node(rev) for rev in candidates], 0, good)
92 return ([changelog.node(rev) for rev in candidates], 0, good)
93 perfect = tot // 2
93 perfect = tot // 2
94
94
95 # find the best node to test
95 # find the best node to test
96 best_rev = None
96 best_rev = None
97 best_len = -1
97 best_len = -1
98 poison = set()
98 poison = set()
99 for rev in candidates:
99 for rev in candidates:
100 if rev in poison:
100 if rev in poison:
101 # poison children
101 # poison children
102 poison.update(children.get(rev, []))
102 poison.update(children.get(rev, []))
103 continue
103 continue
104
104
105 a = ancestors[rev] or [rev]
105 a = ancestors[rev] or [rev]
106 ancestors[rev] = None
106 ancestors[rev] = None
107
107
108 x = len(a) # number of ancestors
108 x = len(a) # number of ancestors
109 y = tot - x # number of non-ancestors
109 y = tot - x # number of non-ancestors
110 value = min(x, y) # how good is this test?
110 value = min(x, y) # how good is this test?
111 if value > best_len and rev not in skip:
111 if value > best_len and rev not in skip:
112 best_len = value
112 best_len = value
113 best_rev = rev
113 best_rev = rev
114 if value == perfect: # found a perfect candidate? quit early
114 if value == perfect: # found a perfect candidate? quit early
115 break
115 break
116
116
117 if y < perfect and rev not in skip: # all downhill from here?
117 if y < perfect and rev not in skip: # all downhill from here?
118 # poison children
118 # poison children
119 poison.update(children.get(rev, []))
119 poison.update(children.get(rev, []))
120 continue
120 continue
121
121
122 for c in children.get(rev, []):
122 for c in children.get(rev, []):
123 if ancestors[c]:
123 if ancestors[c]:
124 ancestors[c] = list(set(ancestors[c] + a))
124 ancestors[c] = list(set(ancestors[c] + a))
125 else:
125 else:
126 ancestors[c] = a + [c]
126 ancestors[c] = a + [c]
127
127
128 assert best_rev is not None
128 assert best_rev is not None
129 best_node = changelog.node(best_rev)
129 best_node = changelog.node(best_rev)
130
130
131 return ([best_node], tot, good)
131 return ([best_node], tot, good)
132
132
133
133
134 def load_state(repo):
134 def load_state(repo):
135 state = {'good': [], 'bad': [], 'skip': []}
135 state = {'good': [], 'bad': [], 'skip': []}
136 if os.path.exists(repo.join("bisect.state")):
136 if os.path.exists(repo.join("bisect.state")):
137 for l in repo.opener("bisect.state"):
137 for l in repo.opener("bisect.state"):
138 kind, node = l[:-1].split()
138 kind, node = l[:-1].split()
139 node = repo.lookup(node)
139 node = repo.lookup(node)
140 if kind not in state:
140 if kind not in state:
141 raise util.Abort(_("unknown bisect kind %s") % kind)
141 raise util.Abort(_("unknown bisect kind %s") % kind)
142 state[kind].append(node)
142 state[kind].append(node)
143 return state
143 return state
144
144
145
145
146 def save_state(repo, state):
146 def save_state(repo, state):
147 f = repo.opener("bisect.state", "w", atomictemp=True)
147 f = repo.opener("bisect.state", "w", atomictemp=True)
148 wlock = repo.wlock()
148 wlock = repo.wlock()
149 try:
149 try:
150 for kind in state:
150 for kind in state:
151 for node in state[kind]:
151 for node in state[kind]:
152 f.write("%s %s\n" % (kind, hex(node)))
152 f.write("%s %s\n" % (kind, hex(node)))
153 f.close()
153 f.close()
154 finally:
154 finally:
155 wlock.release()
155 wlock.release()
156
156
157 def get(repo, status):
157 def get(repo, status):
158 """
158 """
159 Return a list of revision(s) that match the given status:
159 Return a list of revision(s) that match the given status:
160
160
161 - ``good``, ``bad``, ``skip``: as the names imply
161 - ``good``, ``bad``, ``skip``: as the names imply
162 - ``range`` : all csets taking part in the bisection
162 - ``range`` : all csets taking part in the bisection
163 - ``pruned`` : good|bad|skip, excluding out-of-range csets
163 """
164 """
164 state = load_state(repo)
165 state = load_state(repo)
165 if status in ('good', 'bad', 'skip'):
166 if status in ('good', 'bad', 'skip'):
166 return [repo.changelog.rev(n) for n in state[status]]
167 return [repo.changelog.rev(n) for n in state[status]]
167 else:
168 else:
168 # Build sets of good, bad, and skipped csets
169 # Build sets of good, bad, and skipped csets
169 goods = set(repo.changelog.rev(n) for n in state['good'])
170 goods = set(repo.changelog.rev(n) for n in state['good'])
170 bads = set(repo.changelog.rev(n) for n in state['bad'])
171 bads = set(repo.changelog.rev(n) for n in state['bad'])
171 skips = set(repo.changelog.rev(n) for n in state['skip'])
172 skips = set(repo.changelog.rev(n) for n in state['skip'])
172
173
173 # Build kinship of good csets
174 # Build kinship of good csets
174 ga = goods.copy() # Goods' Ancestors
175 ga = goods.copy() # Goods' Ancestors
175 gd = goods.copy() # Goods' Descendants
176 gd = goods.copy() # Goods' Descendants
176 for g in goods:
177 for g in goods:
177 ga |= set(repo.changelog.ancestors(g))
178 ga |= set(repo.changelog.ancestors(g))
178 gd |= set(repo.changelog.descendants(g))
179 gd |= set(repo.changelog.descendants(g))
179
180
180 # Build kinship of bad csets
181 # Build kinship of bad csets
181 ba = bads.copy() # Bads' Ancestors
182 ba = bads.copy() # Bads' Ancestors
182 bd = bads.copy() # Bads' Descendants
183 bd = bads.copy() # Bads' Descendants
183 for b in bads:
184 for b in bads:
184 ba |= set(repo.changelog.ancestors(b))
185 ba |= set(repo.changelog.ancestors(b))
185 bd |= set(repo.changelog.descendants(b))
186 bd |= set(repo.changelog.descendants(b))
186
187
187 # Build the range of the bisection
188 # Build the range of the bisection
188 range = set(c for c in ba if c in gd)
189 range = set(c for c in ba if c in gd)
189 range |= set(c for c in ga if c in bd)
190 range |= set(c for c in ga if c in bd)
190
191
191 if status == 'range':
192 if status == 'range':
192 return [c for c in range]
193 return [c for c in range]
194 elif status == 'pruned':
195 # We do not want skipped csets that are out-of-range
196 return [c for c in range if c in (goods | bads | skips)]
193
197
194 else:
198 else:
195 raise error.ParseError(_('invalid bisect state'))
199 raise error.ParseError(_('invalid bisect state'))
@@ -1,1104 +1,1105 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, node
9 import parser, util, error, discovery, hbisect, node
10 import bookmarks as bookmarksmod
10 import bookmarks as bookmarksmod
11 import match as matchmod
11 import match as matchmod
12 from i18n import _
12 from i18n import _
13
13
14 elements = {
14 elements = {
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 "~": (18, None, ("ancestor", 18)),
16 "~": (18, None, ("ancestor", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
18 "-": (5, ("negate", 19), ("minus", 5)),
18 "-": (5, ("negate", 19), ("minus", 5)),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
20 ("dagrangepost", 17)),
20 ("dagrangepost", 17)),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
22 ("dagrangepost", 17)),
22 ("dagrangepost", 17)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
24 "not": (10, ("not", 10)),
24 "not": (10, ("not", 10)),
25 "!": (10, ("not", 10)),
25 "!": (10, ("not", 10)),
26 "and": (5, None, ("and", 5)),
26 "and": (5, None, ("and", 5)),
27 "&": (5, None, ("and", 5)),
27 "&": (5, None, ("and", 5)),
28 "or": (4, None, ("or", 4)),
28 "or": (4, None, ("or", 4)),
29 "|": (4, None, ("or", 4)),
29 "|": (4, None, ("or", 4)),
30 "+": (4, None, ("or", 4)),
30 "+": (4, None, ("or", 4)),
31 ",": (2, None, ("list", 2)),
31 ",": (2, None, ("list", 2)),
32 ")": (0, None, None),
32 ")": (0, None, None),
33 "symbol": (0, ("symbol",), None),
33 "symbol": (0, ("symbol",), None),
34 "string": (0, ("string",), None),
34 "string": (0, ("string",), None),
35 "end": (0, None, None),
35 "end": (0, None, None),
36 }
36 }
37
37
38 keywords = set(['and', 'or', 'not'])
38 keywords = set(['and', 'or', 'not'])
39
39
40 def tokenize(program):
40 def tokenize(program):
41 pos, l = 0, len(program)
41 pos, l = 0, len(program)
42 while pos < l:
42 while pos < l:
43 c = program[pos]
43 c = program[pos]
44 if c.isspace(): # skip inter-token whitespace
44 if c.isspace(): # skip inter-token whitespace
45 pass
45 pass
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
47 yield ('::', None, pos)
47 yield ('::', None, pos)
48 pos += 1 # skip ahead
48 pos += 1 # skip ahead
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
50 yield ('..', None, pos)
50 yield ('..', None, pos)
51 pos += 1 # skip ahead
51 pos += 1 # skip ahead
52 elif c in "():,-|&+!~^": # handle simple operators
52 elif c in "():,-|&+!~^": # handle simple operators
53 yield (c, None, pos)
53 yield (c, None, pos)
54 elif (c in '"\'' or c == 'r' and
54 elif (c in '"\'' or c == 'r' and
55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
56 if c == 'r':
56 if c == 'r':
57 pos += 1
57 pos += 1
58 c = program[pos]
58 c = program[pos]
59 decode = lambda x: x
59 decode = lambda x: x
60 else:
60 else:
61 decode = lambda x: x.decode('string-escape')
61 decode = lambda x: x.decode('string-escape')
62 pos += 1
62 pos += 1
63 s = pos
63 s = pos
64 while pos < l: # find closing quote
64 while pos < l: # find closing quote
65 d = program[pos]
65 d = program[pos]
66 if d == '\\': # skip over escaped characters
66 if d == '\\': # skip over escaped characters
67 pos += 2
67 pos += 2
68 continue
68 continue
69 if d == c:
69 if d == c:
70 yield ('string', decode(program[s:pos]), s)
70 yield ('string', decode(program[s:pos]), s)
71 break
71 break
72 pos += 1
72 pos += 1
73 else:
73 else:
74 raise error.ParseError(_("unterminated string"), s)
74 raise error.ParseError(_("unterminated string"), s)
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
76 s = pos
76 s = pos
77 pos += 1
77 pos += 1
78 while pos < l: # find end of symbol
78 while pos < l: # find end of symbol
79 d = program[pos]
79 d = program[pos]
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
81 break
81 break
82 if d == '.' and program[pos - 1] == '.': # special case for ..
82 if d == '.' and program[pos - 1] == '.': # special case for ..
83 pos -= 1
83 pos -= 1
84 break
84 break
85 pos += 1
85 pos += 1
86 sym = program[s:pos]
86 sym = program[s:pos]
87 if sym in keywords: # operator keywords
87 if sym in keywords: # operator keywords
88 yield (sym, None, s)
88 yield (sym, None, s)
89 else:
89 else:
90 yield ('symbol', sym, s)
90 yield ('symbol', sym, s)
91 pos -= 1
91 pos -= 1
92 else:
92 else:
93 raise error.ParseError(_("syntax error"), pos)
93 raise error.ParseError(_("syntax error"), pos)
94 pos += 1
94 pos += 1
95 yield ('end', None, pos)
95 yield ('end', None, pos)
96
96
97 # helpers
97 # helpers
98
98
99 def getstring(x, err):
99 def getstring(x, err):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
101 return x[1]
101 return x[1]
102 raise error.ParseError(err)
102 raise error.ParseError(err)
103
103
104 def getlist(x):
104 def getlist(x):
105 if not x:
105 if not x:
106 return []
106 return []
107 if x[0] == 'list':
107 if x[0] == 'list':
108 return getlist(x[1]) + [x[2]]
108 return getlist(x[1]) + [x[2]]
109 return [x]
109 return [x]
110
110
111 def getargs(x, min, max, err):
111 def getargs(x, min, max, err):
112 l = getlist(x)
112 l = getlist(x)
113 if len(l) < min or len(l) > max:
113 if len(l) < min or len(l) > max:
114 raise error.ParseError(err)
114 raise error.ParseError(err)
115 return l
115 return l
116
116
117 def getset(repo, subset, x):
117 def getset(repo, subset, x):
118 if not x:
118 if not x:
119 raise error.ParseError(_("missing argument"))
119 raise error.ParseError(_("missing argument"))
120 return methods[x[0]](repo, subset, *x[1:])
120 return methods[x[0]](repo, subset, *x[1:])
121
121
122 # operator methods
122 # operator methods
123
123
124 def stringset(repo, subset, x):
124 def stringset(repo, subset, x):
125 x = repo[x].rev()
125 x = repo[x].rev()
126 if x == -1 and len(subset) == len(repo):
126 if x == -1 and len(subset) == len(repo):
127 return [-1]
127 return [-1]
128 if len(subset) == len(repo) or x in subset:
128 if len(subset) == len(repo) or x in subset:
129 return [x]
129 return [x]
130 return []
130 return []
131
131
132 def symbolset(repo, subset, x):
132 def symbolset(repo, subset, x):
133 if x in symbols:
133 if x in symbols:
134 raise error.ParseError(_("can't use %s here") % x)
134 raise error.ParseError(_("can't use %s here") % x)
135 return stringset(repo, subset, x)
135 return stringset(repo, subset, x)
136
136
137 def rangeset(repo, subset, x, y):
137 def rangeset(repo, subset, x, y):
138 m = getset(repo, subset, x)
138 m = getset(repo, subset, x)
139 if not m:
139 if not m:
140 m = getset(repo, range(len(repo)), x)
140 m = getset(repo, range(len(repo)), x)
141
141
142 n = getset(repo, subset, y)
142 n = getset(repo, subset, y)
143 if not n:
143 if not n:
144 n = getset(repo, range(len(repo)), y)
144 n = getset(repo, range(len(repo)), y)
145
145
146 if not m or not n:
146 if not m or not n:
147 return []
147 return []
148 m, n = m[0], n[-1]
148 m, n = m[0], n[-1]
149
149
150 if m < n:
150 if m < n:
151 r = range(m, n + 1)
151 r = range(m, n + 1)
152 else:
152 else:
153 r = range(m, n - 1, -1)
153 r = range(m, n - 1, -1)
154 s = set(subset)
154 s = set(subset)
155 return [x for x in r if x in s]
155 return [x for x in r if x in s]
156
156
157 def andset(repo, subset, x, y):
157 def andset(repo, subset, x, y):
158 return getset(repo, getset(repo, subset, x), y)
158 return getset(repo, getset(repo, subset, x), y)
159
159
160 def orset(repo, subset, x, y):
160 def orset(repo, subset, x, y):
161 xl = getset(repo, subset, x)
161 xl = getset(repo, subset, x)
162 s = set(xl)
162 s = set(xl)
163 yl = getset(repo, [r for r in subset if r not in s], y)
163 yl = getset(repo, [r for r in subset if r not in s], y)
164 return xl + yl
164 return xl + yl
165
165
166 def notset(repo, subset, x):
166 def notset(repo, subset, x):
167 s = set(getset(repo, subset, x))
167 s = set(getset(repo, subset, x))
168 return [r for r in subset if r not in s]
168 return [r for r in subset if r not in s]
169
169
170 def listset(repo, subset, a, b):
170 def listset(repo, subset, a, b):
171 raise error.ParseError(_("can't use a list in this context"))
171 raise error.ParseError(_("can't use a list in this context"))
172
172
173 def func(repo, subset, a, b):
173 def func(repo, subset, a, b):
174 if a[0] == 'symbol' and a[1] in symbols:
174 if a[0] == 'symbol' and a[1] in symbols:
175 return symbols[a[1]](repo, subset, b)
175 return symbols[a[1]](repo, subset, b)
176 raise error.ParseError(_("not a function: %s") % a[1])
176 raise error.ParseError(_("not a function: %s") % a[1])
177
177
178 # functions
178 # functions
179
179
180 def adds(repo, subset, x):
180 def adds(repo, subset, x):
181 """``adds(pattern)``
181 """``adds(pattern)``
182 Changesets that add a file matching pattern.
182 Changesets that add a file matching pattern.
183 """
183 """
184 # i18n: "adds" is a keyword
184 # i18n: "adds" is a keyword
185 pat = getstring(x, _("adds requires a pattern"))
185 pat = getstring(x, _("adds requires a pattern"))
186 return checkstatus(repo, subset, pat, 1)
186 return checkstatus(repo, subset, pat, 1)
187
187
188 def ancestor(repo, subset, x):
188 def ancestor(repo, subset, x):
189 """``ancestor(single, single)``
189 """``ancestor(single, single)``
190 Greatest common ancestor of the two changesets.
190 Greatest common ancestor of the two changesets.
191 """
191 """
192 # i18n: "ancestor" is a keyword
192 # i18n: "ancestor" is a keyword
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
194 r = range(len(repo))
194 r = range(len(repo))
195 a = getset(repo, r, l[0])
195 a = getset(repo, r, l[0])
196 b = getset(repo, r, l[1])
196 b = getset(repo, r, l[1])
197 if len(a) != 1 or len(b) != 1:
197 if len(a) != 1 or len(b) != 1:
198 # i18n: "ancestor" is a keyword
198 # i18n: "ancestor" is a keyword
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
201
201
202 return [r for r in an if r in subset]
202 return [r for r in an if r in subset]
203
203
204 def ancestors(repo, subset, x):
204 def ancestors(repo, subset, x):
205 """``ancestors(set)``
205 """``ancestors(set)``
206 Changesets that are ancestors of a changeset in set.
206 Changesets that are ancestors of a changeset in set.
207 """
207 """
208 args = getset(repo, range(len(repo)), x)
208 args = getset(repo, range(len(repo)), x)
209 if not args:
209 if not args:
210 return []
210 return []
211 s = set(repo.changelog.ancestors(*args)) | set(args)
211 s = set(repo.changelog.ancestors(*args)) | set(args)
212 return [r for r in subset if r in s]
212 return [r for r in subset if r in s]
213
213
214 def ancestorspec(repo, subset, x, n):
214 def ancestorspec(repo, subset, x, n):
215 """``set~n``
215 """``set~n``
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
217 """
217 """
218 try:
218 try:
219 n = int(n[1])
219 n = int(n[1])
220 except (TypeError, ValueError):
220 except (TypeError, ValueError):
221 raise error.ParseError(_("~ expects a number"))
221 raise error.ParseError(_("~ expects a number"))
222 ps = set()
222 ps = set()
223 cl = repo.changelog
223 cl = repo.changelog
224 for r in getset(repo, subset, x):
224 for r in getset(repo, subset, x):
225 for i in range(n):
225 for i in range(n):
226 r = cl.parentrevs(r)[0]
226 r = cl.parentrevs(r)[0]
227 ps.add(r)
227 ps.add(r)
228 return [r for r in subset if r in ps]
228 return [r for r in subset if r in ps]
229
229
230 def author(repo, subset, x):
230 def author(repo, subset, x):
231 """``author(string)``
231 """``author(string)``
232 Alias for ``user(string)``.
232 Alias for ``user(string)``.
233 """
233 """
234 # i18n: "author" is a keyword
234 # i18n: "author" is a keyword
235 n = getstring(x, _("author requires a string")).lower()
235 n = getstring(x, _("author requires a string")).lower()
236 return [r for r in subset if n in repo[r].user().lower()]
236 return [r for r in subset if n in repo[r].user().lower()]
237
237
238 def bisect(repo, subset, x):
238 def bisect(repo, subset, x):
239 """``bisect(string)``
239 """``bisect(string)``
240 Changesets marked in the specified bisect status (``good``, ``bad``,
240 Changesets marked in the specified bisect status (``good``, ``bad``,
241 ``skip``), or any of the meta-status:
241 ``skip``), or any of the meta-status:
242
242
243 - ``range`` : all csets taking part in the bisection
243 - ``range`` : all csets taking part in the bisection
244 - ``pruned`` : good|bad|skip, excluding out-of-range csets
244 """
245 """
245 status = getstring(x, _("bisect requires a string")).lower()
246 status = getstring(x, _("bisect requires a string")).lower()
246 return [r for r in subset if r in hbisect.get(repo, status)]
247 return [r for r in subset if r in hbisect.get(repo, status)]
247
248
248 # Backward-compatibility
249 # Backward-compatibility
249 # - no help entry so that we do not advertise it any more
250 # - no help entry so that we do not advertise it any more
250 def bisected(repo, subset, x):
251 def bisected(repo, subset, x):
251 return bisect(repo, subset, x)
252 return bisect(repo, subset, x)
252
253
253 def bookmark(repo, subset, x):
254 def bookmark(repo, subset, x):
254 """``bookmark([name])``
255 """``bookmark([name])``
255 The named bookmark or all bookmarks.
256 The named bookmark or all bookmarks.
256 """
257 """
257 # i18n: "bookmark" is a keyword
258 # i18n: "bookmark" is a keyword
258 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
259 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
259 if args:
260 if args:
260 bm = getstring(args[0],
261 bm = getstring(args[0],
261 # i18n: "bookmark" is a keyword
262 # i18n: "bookmark" is a keyword
262 _('the argument to bookmark must be a string'))
263 _('the argument to bookmark must be a string'))
263 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
264 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
264 if not bmrev:
265 if not bmrev:
265 raise util.Abort(_("bookmark '%s' does not exist") % bm)
266 raise util.Abort(_("bookmark '%s' does not exist") % bm)
266 bmrev = repo[bmrev].rev()
267 bmrev = repo[bmrev].rev()
267 return [r for r in subset if r == bmrev]
268 return [r for r in subset if r == bmrev]
268 bms = set([repo[r].rev()
269 bms = set([repo[r].rev()
269 for r in bookmarksmod.listbookmarks(repo).values()])
270 for r in bookmarksmod.listbookmarks(repo).values()])
270 return [r for r in subset if r in bms]
271 return [r for r in subset if r in bms]
271
272
272 def branch(repo, subset, x):
273 def branch(repo, subset, x):
273 """``branch(string or set)``
274 """``branch(string or set)``
274 All changesets belonging to the given branch or the branches of the given
275 All changesets belonging to the given branch or the branches of the given
275 changesets.
276 changesets.
276 """
277 """
277 try:
278 try:
278 b = getstring(x, '')
279 b = getstring(x, '')
279 if b in repo.branchmap():
280 if b in repo.branchmap():
280 return [r for r in subset if repo[r].branch() == b]
281 return [r for r in subset if repo[r].branch() == b]
281 except error.ParseError:
282 except error.ParseError:
282 # not a string, but another revspec, e.g. tip()
283 # not a string, but another revspec, e.g. tip()
283 pass
284 pass
284
285
285 s = getset(repo, range(len(repo)), x)
286 s = getset(repo, range(len(repo)), x)
286 b = set()
287 b = set()
287 for r in s:
288 for r in s:
288 b.add(repo[r].branch())
289 b.add(repo[r].branch())
289 s = set(s)
290 s = set(s)
290 return [r for r in subset if r in s or repo[r].branch() in b]
291 return [r for r in subset if r in s or repo[r].branch() in b]
291
292
292 def checkstatus(repo, subset, pat, field):
293 def checkstatus(repo, subset, pat, field):
293 m = matchmod.match(repo.root, repo.getcwd(), [pat])
294 m = matchmod.match(repo.root, repo.getcwd(), [pat])
294 s = []
295 s = []
295 fast = (m.files() == [pat])
296 fast = (m.files() == [pat])
296 for r in subset:
297 for r in subset:
297 c = repo[r]
298 c = repo[r]
298 if fast:
299 if fast:
299 if pat not in c.files():
300 if pat not in c.files():
300 continue
301 continue
301 else:
302 else:
302 for f in c.files():
303 for f in c.files():
303 if m(f):
304 if m(f):
304 break
305 break
305 else:
306 else:
306 continue
307 continue
307 files = repo.status(c.p1().node(), c.node())[field]
308 files = repo.status(c.p1().node(), c.node())[field]
308 if fast:
309 if fast:
309 if pat in files:
310 if pat in files:
310 s.append(r)
311 s.append(r)
311 else:
312 else:
312 for f in files:
313 for f in files:
313 if m(f):
314 if m(f):
314 s.append(r)
315 s.append(r)
315 break
316 break
316 return s
317 return s
317
318
318 def children(repo, subset, x):
319 def children(repo, subset, x):
319 """``children(set)``
320 """``children(set)``
320 Child changesets of changesets in set.
321 Child changesets of changesets in set.
321 """
322 """
322 cs = set()
323 cs = set()
323 cl = repo.changelog
324 cl = repo.changelog
324 s = set(getset(repo, range(len(repo)), x))
325 s = set(getset(repo, range(len(repo)), x))
325 for r in xrange(0, len(repo)):
326 for r in xrange(0, len(repo)):
326 for p in cl.parentrevs(r):
327 for p in cl.parentrevs(r):
327 if p in s:
328 if p in s:
328 cs.add(r)
329 cs.add(r)
329 return [r for r in subset if r in cs]
330 return [r for r in subset if r in cs]
330
331
331 def closed(repo, subset, x):
332 def closed(repo, subset, x):
332 """``closed()``
333 """``closed()``
333 Changeset is closed.
334 Changeset is closed.
334 """
335 """
335 # i18n: "closed" is a keyword
336 # i18n: "closed" is a keyword
336 getargs(x, 0, 0, _("closed takes no arguments"))
337 getargs(x, 0, 0, _("closed takes no arguments"))
337 return [r for r in subset if repo[r].extra().get('close')]
338 return [r for r in subset if repo[r].extra().get('close')]
338
339
339 def contains(repo, subset, x):
340 def contains(repo, subset, x):
340 """``contains(pattern)``
341 """``contains(pattern)``
341 Revision contains a file matching pattern. See :hg:`help patterns`
342 Revision contains a file matching pattern. See :hg:`help patterns`
342 for information about file patterns.
343 for information about file patterns.
343 """
344 """
344 # i18n: "contains" is a keyword
345 # i18n: "contains" is a keyword
345 pat = getstring(x, _("contains requires a pattern"))
346 pat = getstring(x, _("contains requires a pattern"))
346 m = matchmod.match(repo.root, repo.getcwd(), [pat])
347 m = matchmod.match(repo.root, repo.getcwd(), [pat])
347 s = []
348 s = []
348 if m.files() == [pat]:
349 if m.files() == [pat]:
349 for r in subset:
350 for r in subset:
350 if pat in repo[r]:
351 if pat in repo[r]:
351 s.append(r)
352 s.append(r)
352 else:
353 else:
353 for r in subset:
354 for r in subset:
354 for f in repo[r].manifest():
355 for f in repo[r].manifest():
355 if m(f):
356 if m(f):
356 s.append(r)
357 s.append(r)
357 break
358 break
358 return s
359 return s
359
360
360 def date(repo, subset, x):
361 def date(repo, subset, x):
361 """``date(interval)``
362 """``date(interval)``
362 Changesets within the interval, see :hg:`help dates`.
363 Changesets within the interval, see :hg:`help dates`.
363 """
364 """
364 # i18n: "date" is a keyword
365 # i18n: "date" is a keyword
365 ds = getstring(x, _("date requires a string"))
366 ds = getstring(x, _("date requires a string"))
366 dm = util.matchdate(ds)
367 dm = util.matchdate(ds)
367 return [r for r in subset if dm(repo[r].date()[0])]
368 return [r for r in subset if dm(repo[r].date()[0])]
368
369
369 def desc(repo, subset, x):
370 def desc(repo, subset, x):
370 """``desc(string)``
371 """``desc(string)``
371 Search commit message for string. The match is case-insensitive.
372 Search commit message for string. The match is case-insensitive.
372 """
373 """
373 # i18n: "desc" is a keyword
374 # i18n: "desc" is a keyword
374 ds = getstring(x, _("desc requires a string")).lower()
375 ds = getstring(x, _("desc requires a string")).lower()
375 l = []
376 l = []
376 for r in subset:
377 for r in subset:
377 c = repo[r]
378 c = repo[r]
378 if ds in c.description().lower():
379 if ds in c.description().lower():
379 l.append(r)
380 l.append(r)
380 return l
381 return l
381
382
382 def descendants(repo, subset, x):
383 def descendants(repo, subset, x):
383 """``descendants(set)``
384 """``descendants(set)``
384 Changesets which are descendants of changesets in set.
385 Changesets which are descendants of changesets in set.
385 """
386 """
386 args = getset(repo, range(len(repo)), x)
387 args = getset(repo, range(len(repo)), x)
387 if not args:
388 if not args:
388 return []
389 return []
389 s = set(repo.changelog.descendants(*args)) | set(args)
390 s = set(repo.changelog.descendants(*args)) | set(args)
390 return [r for r in subset if r in s]
391 return [r for r in subset if r in s]
391
392
392 def filelog(repo, subset, x):
393 def filelog(repo, subset, x):
393 """``filelog(pattern)``
394 """``filelog(pattern)``
394 Changesets connected to the specified filelog.
395 Changesets connected to the specified filelog.
395 """
396 """
396
397
397 pat = getstring(x, _("filelog requires a pattern"))
398 pat = getstring(x, _("filelog requires a pattern"))
398 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
399 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
399 s = set()
400 s = set()
400
401
401 if not m.anypats():
402 if not m.anypats():
402 for f in m.files():
403 for f in m.files():
403 fl = repo.file(f)
404 fl = repo.file(f)
404 for fr in fl:
405 for fr in fl:
405 s.add(fl.linkrev(fr))
406 s.add(fl.linkrev(fr))
406 else:
407 else:
407 for f in repo[None]:
408 for f in repo[None]:
408 if m(f):
409 if m(f):
409 fl = repo.file(f)
410 fl = repo.file(f)
410 for fr in fl:
411 for fr in fl:
411 s.add(fl.linkrev(fr))
412 s.add(fl.linkrev(fr))
412
413
413 return [r for r in subset if r in s]
414 return [r for r in subset if r in s]
414
415
415 def first(repo, subset, x):
416 def first(repo, subset, x):
416 """``first(set, [n])``
417 """``first(set, [n])``
417 An alias for limit().
418 An alias for limit().
418 """
419 """
419 return limit(repo, subset, x)
420 return limit(repo, subset, x)
420
421
421 def follow(repo, subset, x):
422 def follow(repo, subset, x):
422 """``follow([file])``
423 """``follow([file])``
423 An alias for ``::.`` (ancestors of the working copy's first parent).
424 An alias for ``::.`` (ancestors of the working copy's first parent).
424 If a filename is specified, the history of the given file is followed,
425 If a filename is specified, the history of the given file is followed,
425 including copies.
426 including copies.
426 """
427 """
427 # i18n: "follow" is a keyword
428 # i18n: "follow" is a keyword
428 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
429 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
429 p = repo['.'].rev()
430 p = repo['.'].rev()
430 if l:
431 if l:
431 x = getstring(l[0], _("follow expected a filename"))
432 x = getstring(l[0], _("follow expected a filename"))
432 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
433 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
433 else:
434 else:
434 s = set(repo.changelog.ancestors(p))
435 s = set(repo.changelog.ancestors(p))
435
436
436 s |= set([p])
437 s |= set([p])
437 return [r for r in subset if r in s]
438 return [r for r in subset if r in s]
438
439
439 def followfile(repo, subset, x):
440 def followfile(repo, subset, x):
440 """``follow()``
441 """``follow()``
441 An alias for ``::.`` (ancestors of the working copy's first parent).
442 An alias for ``::.`` (ancestors of the working copy's first parent).
442 """
443 """
443 # i18n: "follow" is a keyword
444 # i18n: "follow" is a keyword
444 getargs(x, 0, 0, _("follow takes no arguments"))
445 getargs(x, 0, 0, _("follow takes no arguments"))
445 p = repo['.'].rev()
446 p = repo['.'].rev()
446 s = set(repo.changelog.ancestors(p)) | set([p])
447 s = set(repo.changelog.ancestors(p)) | set([p])
447 return [r for r in subset if r in s]
448 return [r for r in subset if r in s]
448
449
449 def getall(repo, subset, x):
450 def getall(repo, subset, x):
450 """``all()``
451 """``all()``
451 All changesets, the same as ``0:tip``.
452 All changesets, the same as ``0:tip``.
452 """
453 """
453 # i18n: "all" is a keyword
454 # i18n: "all" is a keyword
454 getargs(x, 0, 0, _("all takes no arguments"))
455 getargs(x, 0, 0, _("all takes no arguments"))
455 return subset
456 return subset
456
457
457 def grep(repo, subset, x):
458 def grep(repo, subset, x):
458 """``grep(regex)``
459 """``grep(regex)``
459 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
460 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
460 to ensure special escape characters are handled correctly. Unlike
461 to ensure special escape characters are handled correctly. Unlike
461 ``keyword(string)``, the match is case-sensitive.
462 ``keyword(string)``, the match is case-sensitive.
462 """
463 """
463 try:
464 try:
464 # i18n: "grep" is a keyword
465 # i18n: "grep" is a keyword
465 gr = re.compile(getstring(x, _("grep requires a string")))
466 gr = re.compile(getstring(x, _("grep requires a string")))
466 except re.error, e:
467 except re.error, e:
467 raise error.ParseError(_('invalid match pattern: %s') % e)
468 raise error.ParseError(_('invalid match pattern: %s') % e)
468 l = []
469 l = []
469 for r in subset:
470 for r in subset:
470 c = repo[r]
471 c = repo[r]
471 for e in c.files() + [c.user(), c.description()]:
472 for e in c.files() + [c.user(), c.description()]:
472 if gr.search(e):
473 if gr.search(e):
473 l.append(r)
474 l.append(r)
474 break
475 break
475 return l
476 return l
476
477
477 def hasfile(repo, subset, x):
478 def hasfile(repo, subset, x):
478 """``file(pattern)``
479 """``file(pattern)``
479 Changesets affecting files matched by pattern.
480 Changesets affecting files matched by pattern.
480 """
481 """
481 # i18n: "file" is a keyword
482 # i18n: "file" is a keyword
482 pat = getstring(x, _("file requires a pattern"))
483 pat = getstring(x, _("file requires a pattern"))
483 m = matchmod.match(repo.root, repo.getcwd(), [pat])
484 m = matchmod.match(repo.root, repo.getcwd(), [pat])
484 s = []
485 s = []
485 for r in subset:
486 for r in subset:
486 for f in repo[r].files():
487 for f in repo[r].files():
487 if m(f):
488 if m(f):
488 s.append(r)
489 s.append(r)
489 break
490 break
490 return s
491 return s
491
492
492 def head(repo, subset, x):
493 def head(repo, subset, x):
493 """``head()``
494 """``head()``
494 Changeset is a named branch head.
495 Changeset is a named branch head.
495 """
496 """
496 # i18n: "head" is a keyword
497 # i18n: "head" is a keyword
497 getargs(x, 0, 0, _("head takes no arguments"))
498 getargs(x, 0, 0, _("head takes no arguments"))
498 hs = set()
499 hs = set()
499 for b, ls in repo.branchmap().iteritems():
500 for b, ls in repo.branchmap().iteritems():
500 hs.update(repo[h].rev() for h in ls)
501 hs.update(repo[h].rev() for h in ls)
501 return [r for r in subset if r in hs]
502 return [r for r in subset if r in hs]
502
503
503 def heads(repo, subset, x):
504 def heads(repo, subset, x):
504 """``heads(set)``
505 """``heads(set)``
505 Members of set with no children in set.
506 Members of set with no children in set.
506 """
507 """
507 s = getset(repo, subset, x)
508 s = getset(repo, subset, x)
508 ps = set(parents(repo, subset, x))
509 ps = set(parents(repo, subset, x))
509 return [r for r in s if r not in ps]
510 return [r for r in s if r not in ps]
510
511
511 def keyword(repo, subset, x):
512 def keyword(repo, subset, x):
512 """``keyword(string)``
513 """``keyword(string)``
513 Search commit message, user name, and names of changed files for
514 Search commit message, user name, and names of changed files for
514 string. The match is case-insensitive.
515 string. The match is case-insensitive.
515 """
516 """
516 # i18n: "keyword" is a keyword
517 # i18n: "keyword" is a keyword
517 kw = getstring(x, _("keyword requires a string")).lower()
518 kw = getstring(x, _("keyword requires a string")).lower()
518 l = []
519 l = []
519 for r in subset:
520 for r in subset:
520 c = repo[r]
521 c = repo[r]
521 t = " ".join(c.files() + [c.user(), c.description()])
522 t = " ".join(c.files() + [c.user(), c.description()])
522 if kw in t.lower():
523 if kw in t.lower():
523 l.append(r)
524 l.append(r)
524 return l
525 return l
525
526
526 def limit(repo, subset, x):
527 def limit(repo, subset, x):
527 """``limit(set, [n])``
528 """``limit(set, [n])``
528 First n members of set, defaulting to 1.
529 First n members of set, defaulting to 1.
529 """
530 """
530 # i18n: "limit" is a keyword
531 # i18n: "limit" is a keyword
531 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
532 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
532 try:
533 try:
533 lim = 1
534 lim = 1
534 if len(l) == 2:
535 if len(l) == 2:
535 # i18n: "limit" is a keyword
536 # i18n: "limit" is a keyword
536 lim = int(getstring(l[1], _("limit requires a number")))
537 lim = int(getstring(l[1], _("limit requires a number")))
537 except (TypeError, ValueError):
538 except (TypeError, ValueError):
538 # i18n: "limit" is a keyword
539 # i18n: "limit" is a keyword
539 raise error.ParseError(_("limit expects a number"))
540 raise error.ParseError(_("limit expects a number"))
540 ss = set(subset)
541 ss = set(subset)
541 os = getset(repo, range(len(repo)), l[0])[:lim]
542 os = getset(repo, range(len(repo)), l[0])[:lim]
542 return [r for r in os if r in ss]
543 return [r for r in os if r in ss]
543
544
544 def last(repo, subset, x):
545 def last(repo, subset, x):
545 """``last(set, [n])``
546 """``last(set, [n])``
546 Last n members of set, defaulting to 1.
547 Last n members of set, defaulting to 1.
547 """
548 """
548 # i18n: "last" is a keyword
549 # i18n: "last" is a keyword
549 l = getargs(x, 1, 2, _("last requires one or two arguments"))
550 l = getargs(x, 1, 2, _("last requires one or two arguments"))
550 try:
551 try:
551 lim = 1
552 lim = 1
552 if len(l) == 2:
553 if len(l) == 2:
553 # i18n: "last" is a keyword
554 # i18n: "last" is a keyword
554 lim = int(getstring(l[1], _("last requires a number")))
555 lim = int(getstring(l[1], _("last requires a number")))
555 except (TypeError, ValueError):
556 except (TypeError, ValueError):
556 # i18n: "last" is a keyword
557 # i18n: "last" is a keyword
557 raise error.ParseError(_("last expects a number"))
558 raise error.ParseError(_("last expects a number"))
558 ss = set(subset)
559 ss = set(subset)
559 os = getset(repo, range(len(repo)), l[0])[-lim:]
560 os = getset(repo, range(len(repo)), l[0])[-lim:]
560 return [r for r in os if r in ss]
561 return [r for r in os if r in ss]
561
562
562 def maxrev(repo, subset, x):
563 def maxrev(repo, subset, x):
563 """``max(set)``
564 """``max(set)``
564 Changeset with highest revision number in set.
565 Changeset with highest revision number in set.
565 """
566 """
566 os = getset(repo, range(len(repo)), x)
567 os = getset(repo, range(len(repo)), x)
567 if os:
568 if os:
568 m = max(os)
569 m = max(os)
569 if m in subset:
570 if m in subset:
570 return [m]
571 return [m]
571 return []
572 return []
572
573
573 def merge(repo, subset, x):
574 def merge(repo, subset, x):
574 """``merge()``
575 """``merge()``
575 Changeset is a merge changeset.
576 Changeset is a merge changeset.
576 """
577 """
577 # i18n: "merge" is a keyword
578 # i18n: "merge" is a keyword
578 getargs(x, 0, 0, _("merge takes no arguments"))
579 getargs(x, 0, 0, _("merge takes no arguments"))
579 cl = repo.changelog
580 cl = repo.changelog
580 return [r for r in subset if cl.parentrevs(r)[1] != -1]
581 return [r for r in subset if cl.parentrevs(r)[1] != -1]
581
582
582 def minrev(repo, subset, x):
583 def minrev(repo, subset, x):
583 """``min(set)``
584 """``min(set)``
584 Changeset with lowest revision number in set.
585 Changeset with lowest revision number in set.
585 """
586 """
586 os = getset(repo, range(len(repo)), x)
587 os = getset(repo, range(len(repo)), x)
587 if os:
588 if os:
588 m = min(os)
589 m = min(os)
589 if m in subset:
590 if m in subset:
590 return [m]
591 return [m]
591 return []
592 return []
592
593
593 def modifies(repo, subset, x):
594 def modifies(repo, subset, x):
594 """``modifies(pattern)``
595 """``modifies(pattern)``
595 Changesets modifying files matched by pattern.
596 Changesets modifying files matched by pattern.
596 """
597 """
597 # i18n: "modifies" is a keyword
598 # i18n: "modifies" is a keyword
598 pat = getstring(x, _("modifies requires a pattern"))
599 pat = getstring(x, _("modifies requires a pattern"))
599 return checkstatus(repo, subset, pat, 0)
600 return checkstatus(repo, subset, pat, 0)
600
601
601 def node(repo, subset, x):
602 def node(repo, subset, x):
602 """``id(string)``
603 """``id(string)``
603 Revision non-ambiguously specified by the given hex string prefix.
604 Revision non-ambiguously specified by the given hex string prefix.
604 """
605 """
605 # i18n: "id" is a keyword
606 # i18n: "id" is a keyword
606 l = getargs(x, 1, 1, _("id requires one argument"))
607 l = getargs(x, 1, 1, _("id requires one argument"))
607 # i18n: "id" is a keyword
608 # i18n: "id" is a keyword
608 n = getstring(l[0], _("id requires a string"))
609 n = getstring(l[0], _("id requires a string"))
609 if len(n) == 40:
610 if len(n) == 40:
610 rn = repo[n].rev()
611 rn = repo[n].rev()
611 else:
612 else:
612 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
613 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
613 return [r for r in subset if r == rn]
614 return [r for r in subset if r == rn]
614
615
615 def outgoing(repo, subset, x):
616 def outgoing(repo, subset, x):
616 """``outgoing([path])``
617 """``outgoing([path])``
617 Changesets not found in the specified destination repository, or the
618 Changesets not found in the specified destination repository, or the
618 default push location.
619 default push location.
619 """
620 """
620 import hg # avoid start-up nasties
621 import hg # avoid start-up nasties
621 # i18n: "outgoing" is a keyword
622 # i18n: "outgoing" is a keyword
622 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
623 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
623 # i18n: "outgoing" is a keyword
624 # i18n: "outgoing" is a keyword
624 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
625 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
625 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
626 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
626 dest, branches = hg.parseurl(dest)
627 dest, branches = hg.parseurl(dest)
627 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
628 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
628 if revs:
629 if revs:
629 revs = [repo.lookup(rev) for rev in revs]
630 revs = [repo.lookup(rev) for rev in revs]
630 other = hg.peer(repo, {}, dest)
631 other = hg.peer(repo, {}, dest)
631 repo.ui.pushbuffer()
632 repo.ui.pushbuffer()
632 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
633 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
633 repo.ui.popbuffer()
634 repo.ui.popbuffer()
634 cl = repo.changelog
635 cl = repo.changelog
635 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
636 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
636 return [r for r in subset if r in o]
637 return [r for r in subset if r in o]
637
638
638 def p1(repo, subset, x):
639 def p1(repo, subset, x):
639 """``p1([set])``
640 """``p1([set])``
640 First parent of changesets in set, or the working directory.
641 First parent of changesets in set, or the working directory.
641 """
642 """
642 if x is None:
643 if x is None:
643 p = repo[x].p1().rev()
644 p = repo[x].p1().rev()
644 return [r for r in subset if r == p]
645 return [r for r in subset if r == p]
645
646
646 ps = set()
647 ps = set()
647 cl = repo.changelog
648 cl = repo.changelog
648 for r in getset(repo, range(len(repo)), x):
649 for r in getset(repo, range(len(repo)), x):
649 ps.add(cl.parentrevs(r)[0])
650 ps.add(cl.parentrevs(r)[0])
650 return [r for r in subset if r in ps]
651 return [r for r in subset if r in ps]
651
652
652 def p2(repo, subset, x):
653 def p2(repo, subset, x):
653 """``p2([set])``
654 """``p2([set])``
654 Second parent of changesets in set, or the working directory.
655 Second parent of changesets in set, or the working directory.
655 """
656 """
656 if x is None:
657 if x is None:
657 ps = repo[x].parents()
658 ps = repo[x].parents()
658 try:
659 try:
659 p = ps[1].rev()
660 p = ps[1].rev()
660 return [r for r in subset if r == p]
661 return [r for r in subset if r == p]
661 except IndexError:
662 except IndexError:
662 return []
663 return []
663
664
664 ps = set()
665 ps = set()
665 cl = repo.changelog
666 cl = repo.changelog
666 for r in getset(repo, range(len(repo)), x):
667 for r in getset(repo, range(len(repo)), x):
667 ps.add(cl.parentrevs(r)[1])
668 ps.add(cl.parentrevs(r)[1])
668 return [r for r in subset if r in ps]
669 return [r for r in subset if r in ps]
669
670
670 def parents(repo, subset, x):
671 def parents(repo, subset, x):
671 """``parents([set])``
672 """``parents([set])``
672 The set of all parents for all changesets in set, or the working directory.
673 The set of all parents for all changesets in set, or the working directory.
673 """
674 """
674 if x is None:
675 if x is None:
675 ps = tuple(p.rev() for p in repo[x].parents())
676 ps = tuple(p.rev() for p in repo[x].parents())
676 return [r for r in subset if r in ps]
677 return [r for r in subset if r in ps]
677
678
678 ps = set()
679 ps = set()
679 cl = repo.changelog
680 cl = repo.changelog
680 for r in getset(repo, range(len(repo)), x):
681 for r in getset(repo, range(len(repo)), x):
681 ps.update(cl.parentrevs(r))
682 ps.update(cl.parentrevs(r))
682 return [r for r in subset if r in ps]
683 return [r for r in subset if r in ps]
683
684
684 def parentspec(repo, subset, x, n):
685 def parentspec(repo, subset, x, n):
685 """``set^0``
686 """``set^0``
686 The set.
687 The set.
687 ``set^1`` (or ``set^``), ``set^2``
688 ``set^1`` (or ``set^``), ``set^2``
688 First or second parent, respectively, of all changesets in set.
689 First or second parent, respectively, of all changesets in set.
689 """
690 """
690 try:
691 try:
691 n = int(n[1])
692 n = int(n[1])
692 if n not in (0, 1, 2):
693 if n not in (0, 1, 2):
693 raise ValueError
694 raise ValueError
694 except (TypeError, ValueError):
695 except (TypeError, ValueError):
695 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
696 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
696 ps = set()
697 ps = set()
697 cl = repo.changelog
698 cl = repo.changelog
698 for r in getset(repo, subset, x):
699 for r in getset(repo, subset, x):
699 if n == 0:
700 if n == 0:
700 ps.add(r)
701 ps.add(r)
701 elif n == 1:
702 elif n == 1:
702 ps.add(cl.parentrevs(r)[0])
703 ps.add(cl.parentrevs(r)[0])
703 elif n == 2:
704 elif n == 2:
704 parents = cl.parentrevs(r)
705 parents = cl.parentrevs(r)
705 if len(parents) > 1:
706 if len(parents) > 1:
706 ps.add(parents[1])
707 ps.add(parents[1])
707 return [r for r in subset if r in ps]
708 return [r for r in subset if r in ps]
708
709
709 def present(repo, subset, x):
710 def present(repo, subset, x):
710 """``present(set)``
711 """``present(set)``
711 An empty set, if any revision in set isn't found; otherwise,
712 An empty set, if any revision in set isn't found; otherwise,
712 all revisions in set.
713 all revisions in set.
713 """
714 """
714 try:
715 try:
715 return getset(repo, subset, x)
716 return getset(repo, subset, x)
716 except error.RepoLookupError:
717 except error.RepoLookupError:
717 return []
718 return []
718
719
719 def removes(repo, subset, x):
720 def removes(repo, subset, x):
720 """``removes(pattern)``
721 """``removes(pattern)``
721 Changesets which remove files matching pattern.
722 Changesets which remove files matching pattern.
722 """
723 """
723 # i18n: "removes" is a keyword
724 # i18n: "removes" is a keyword
724 pat = getstring(x, _("removes requires a pattern"))
725 pat = getstring(x, _("removes requires a pattern"))
725 return checkstatus(repo, subset, pat, 2)
726 return checkstatus(repo, subset, pat, 2)
726
727
727 def rev(repo, subset, x):
728 def rev(repo, subset, x):
728 """``rev(number)``
729 """``rev(number)``
729 Revision with the given numeric identifier.
730 Revision with the given numeric identifier.
730 """
731 """
731 # i18n: "rev" is a keyword
732 # i18n: "rev" is a keyword
732 l = getargs(x, 1, 1, _("rev requires one argument"))
733 l = getargs(x, 1, 1, _("rev requires one argument"))
733 try:
734 try:
734 # i18n: "rev" is a keyword
735 # i18n: "rev" is a keyword
735 l = int(getstring(l[0], _("rev requires a number")))
736 l = int(getstring(l[0], _("rev requires a number")))
736 except (TypeError, ValueError):
737 except (TypeError, ValueError):
737 # i18n: "rev" is a keyword
738 # i18n: "rev" is a keyword
738 raise error.ParseError(_("rev expects a number"))
739 raise error.ParseError(_("rev expects a number"))
739 return [r for r in subset if r == l]
740 return [r for r in subset if r == l]
740
741
741 def reverse(repo, subset, x):
742 def reverse(repo, subset, x):
742 """``reverse(set)``
743 """``reverse(set)``
743 Reverse order of set.
744 Reverse order of set.
744 """
745 """
745 l = getset(repo, subset, x)
746 l = getset(repo, subset, x)
746 l.reverse()
747 l.reverse()
747 return l
748 return l
748
749
749 def roots(repo, subset, x):
750 def roots(repo, subset, x):
750 """``roots(set)``
751 """``roots(set)``
751 Changesets with no parent changeset in set.
752 Changesets with no parent changeset in set.
752 """
753 """
753 s = getset(repo, subset, x)
754 s = getset(repo, subset, x)
754 cs = set(children(repo, subset, x))
755 cs = set(children(repo, subset, x))
755 return [r for r in s if r not in cs]
756 return [r for r in s if r not in cs]
756
757
757 def sort(repo, subset, x):
758 def sort(repo, subset, x):
758 """``sort(set[, [-]key...])``
759 """``sort(set[, [-]key...])``
759 Sort set by keys. The default sort order is ascending, specify a key
760 Sort set by keys. The default sort order is ascending, specify a key
760 as ``-key`` to sort in descending order.
761 as ``-key`` to sort in descending order.
761
762
762 The keys can be:
763 The keys can be:
763
764
764 - ``rev`` for the revision number,
765 - ``rev`` for the revision number,
765 - ``branch`` for the branch name,
766 - ``branch`` for the branch name,
766 - ``desc`` for the commit message (description),
767 - ``desc`` for the commit message (description),
767 - ``user`` for user name (``author`` can be used as an alias),
768 - ``user`` for user name (``author`` can be used as an alias),
768 - ``date`` for the commit date
769 - ``date`` for the commit date
769 """
770 """
770 # i18n: "sort" is a keyword
771 # i18n: "sort" is a keyword
771 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
772 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
772 keys = "rev"
773 keys = "rev"
773 if len(l) == 2:
774 if len(l) == 2:
774 keys = getstring(l[1], _("sort spec must be a string"))
775 keys = getstring(l[1], _("sort spec must be a string"))
775
776
776 s = l[0]
777 s = l[0]
777 keys = keys.split()
778 keys = keys.split()
778 l = []
779 l = []
779 def invert(s):
780 def invert(s):
780 return "".join(chr(255 - ord(c)) for c in s)
781 return "".join(chr(255 - ord(c)) for c in s)
781 for r in getset(repo, subset, s):
782 for r in getset(repo, subset, s):
782 c = repo[r]
783 c = repo[r]
783 e = []
784 e = []
784 for k in keys:
785 for k in keys:
785 if k == 'rev':
786 if k == 'rev':
786 e.append(r)
787 e.append(r)
787 elif k == '-rev':
788 elif k == '-rev':
788 e.append(-r)
789 e.append(-r)
789 elif k == 'branch':
790 elif k == 'branch':
790 e.append(c.branch())
791 e.append(c.branch())
791 elif k == '-branch':
792 elif k == '-branch':
792 e.append(invert(c.branch()))
793 e.append(invert(c.branch()))
793 elif k == 'desc':
794 elif k == 'desc':
794 e.append(c.description())
795 e.append(c.description())
795 elif k == '-desc':
796 elif k == '-desc':
796 e.append(invert(c.description()))
797 e.append(invert(c.description()))
797 elif k in 'user author':
798 elif k in 'user author':
798 e.append(c.user())
799 e.append(c.user())
799 elif k in '-user -author':
800 elif k in '-user -author':
800 e.append(invert(c.user()))
801 e.append(invert(c.user()))
801 elif k == 'date':
802 elif k == 'date':
802 e.append(c.date()[0])
803 e.append(c.date()[0])
803 elif k == '-date':
804 elif k == '-date':
804 e.append(-c.date()[0])
805 e.append(-c.date()[0])
805 else:
806 else:
806 raise error.ParseError(_("unknown sort key %r") % k)
807 raise error.ParseError(_("unknown sort key %r") % k)
807 e.append(r)
808 e.append(r)
808 l.append(e)
809 l.append(e)
809 l.sort()
810 l.sort()
810 return [e[-1] for e in l]
811 return [e[-1] for e in l]
811
812
812 def tag(repo, subset, x):
813 def tag(repo, subset, x):
813 """``tag([name])``
814 """``tag([name])``
814 The specified tag by name, or all tagged revisions if no name is given.
815 The specified tag by name, or all tagged revisions if no name is given.
815 """
816 """
816 # i18n: "tag" is a keyword
817 # i18n: "tag" is a keyword
817 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
818 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
818 cl = repo.changelog
819 cl = repo.changelog
819 if args:
820 if args:
820 tn = getstring(args[0],
821 tn = getstring(args[0],
821 # i18n: "tag" is a keyword
822 # i18n: "tag" is a keyword
822 _('the argument to tag must be a string'))
823 _('the argument to tag must be a string'))
823 if not repo.tags().get(tn, None):
824 if not repo.tags().get(tn, None):
824 raise util.Abort(_("tag '%s' does not exist") % tn)
825 raise util.Abort(_("tag '%s' does not exist") % tn)
825 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
826 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
826 else:
827 else:
827 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
828 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
828 return [r for r in subset if r in s]
829 return [r for r in subset if r in s]
829
830
830 def tagged(repo, subset, x):
831 def tagged(repo, subset, x):
831 return tag(repo, subset, x)
832 return tag(repo, subset, x)
832
833
833 def user(repo, subset, x):
834 def user(repo, subset, x):
834 """``user(string)``
835 """``user(string)``
835 User name contains string. The match is case-insensitive.
836 User name contains string. The match is case-insensitive.
836 """
837 """
837 return author(repo, subset, x)
838 return author(repo, subset, x)
838
839
839 symbols = {
840 symbols = {
840 "adds": adds,
841 "adds": adds,
841 "all": getall,
842 "all": getall,
842 "ancestor": ancestor,
843 "ancestor": ancestor,
843 "ancestors": ancestors,
844 "ancestors": ancestors,
844 "author": author,
845 "author": author,
845 "bisect": bisect,
846 "bisect": bisect,
846 "bisected": bisected,
847 "bisected": bisected,
847 "bookmark": bookmark,
848 "bookmark": bookmark,
848 "branch": branch,
849 "branch": branch,
849 "children": children,
850 "children": children,
850 "closed": closed,
851 "closed": closed,
851 "contains": contains,
852 "contains": contains,
852 "date": date,
853 "date": date,
853 "desc": desc,
854 "desc": desc,
854 "descendants": descendants,
855 "descendants": descendants,
855 "file": hasfile,
856 "file": hasfile,
856 "filelog": filelog,
857 "filelog": filelog,
857 "first": first,
858 "first": first,
858 "follow": follow,
859 "follow": follow,
859 "grep": grep,
860 "grep": grep,
860 "head": head,
861 "head": head,
861 "heads": heads,
862 "heads": heads,
862 "id": node,
863 "id": node,
863 "keyword": keyword,
864 "keyword": keyword,
864 "last": last,
865 "last": last,
865 "limit": limit,
866 "limit": limit,
866 "max": maxrev,
867 "max": maxrev,
867 "merge": merge,
868 "merge": merge,
868 "min": minrev,
869 "min": minrev,
869 "modifies": modifies,
870 "modifies": modifies,
870 "outgoing": outgoing,
871 "outgoing": outgoing,
871 "p1": p1,
872 "p1": p1,
872 "p2": p2,
873 "p2": p2,
873 "parents": parents,
874 "parents": parents,
874 "present": present,
875 "present": present,
875 "removes": removes,
876 "removes": removes,
876 "rev": rev,
877 "rev": rev,
877 "reverse": reverse,
878 "reverse": reverse,
878 "roots": roots,
879 "roots": roots,
879 "sort": sort,
880 "sort": sort,
880 "tag": tag,
881 "tag": tag,
881 "tagged": tagged,
882 "tagged": tagged,
882 "user": user,
883 "user": user,
883 }
884 }
884
885
885 methods = {
886 methods = {
886 "range": rangeset,
887 "range": rangeset,
887 "string": stringset,
888 "string": stringset,
888 "symbol": symbolset,
889 "symbol": symbolset,
889 "and": andset,
890 "and": andset,
890 "or": orset,
891 "or": orset,
891 "not": notset,
892 "not": notset,
892 "list": listset,
893 "list": listset,
893 "func": func,
894 "func": func,
894 "ancestor": ancestorspec,
895 "ancestor": ancestorspec,
895 "parent": parentspec,
896 "parent": parentspec,
896 "parentpost": p1,
897 "parentpost": p1,
897 }
898 }
898
899
899 def optimize(x, small):
900 def optimize(x, small):
900 if x is None:
901 if x is None:
901 return 0, x
902 return 0, x
902
903
903 smallbonus = 1
904 smallbonus = 1
904 if small:
905 if small:
905 smallbonus = .5
906 smallbonus = .5
906
907
907 op = x[0]
908 op = x[0]
908 if op == 'minus':
909 if op == 'minus':
909 return optimize(('and', x[1], ('not', x[2])), small)
910 return optimize(('and', x[1], ('not', x[2])), small)
910 elif op == 'dagrange':
911 elif op == 'dagrange':
911 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
912 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
912 ('func', ('symbol', 'ancestors'), x[2])), small)
913 ('func', ('symbol', 'ancestors'), x[2])), small)
913 elif op == 'dagrangepre':
914 elif op == 'dagrangepre':
914 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
915 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
915 elif op == 'dagrangepost':
916 elif op == 'dagrangepost':
916 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
917 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
917 elif op == 'rangepre':
918 elif op == 'rangepre':
918 return optimize(('range', ('string', '0'), x[1]), small)
919 return optimize(('range', ('string', '0'), x[1]), small)
919 elif op == 'rangepost':
920 elif op == 'rangepost':
920 return optimize(('range', x[1], ('string', 'tip')), small)
921 return optimize(('range', x[1], ('string', 'tip')), small)
921 elif op == 'negate':
922 elif op == 'negate':
922 return optimize(('string',
923 return optimize(('string',
923 '-' + getstring(x[1], _("can't negate that"))), small)
924 '-' + getstring(x[1], _("can't negate that"))), small)
924 elif op in 'string symbol negate':
925 elif op in 'string symbol negate':
925 return smallbonus, x # single revisions are small
926 return smallbonus, x # single revisions are small
926 elif op == 'and' or op == 'dagrange':
927 elif op == 'and' or op == 'dagrange':
927 wa, ta = optimize(x[1], True)
928 wa, ta = optimize(x[1], True)
928 wb, tb = optimize(x[2], True)
929 wb, tb = optimize(x[2], True)
929 w = min(wa, wb)
930 w = min(wa, wb)
930 if wa > wb:
931 if wa > wb:
931 return w, (op, tb, ta)
932 return w, (op, tb, ta)
932 return w, (op, ta, tb)
933 return w, (op, ta, tb)
933 elif op == 'or':
934 elif op == 'or':
934 wa, ta = optimize(x[1], False)
935 wa, ta = optimize(x[1], False)
935 wb, tb = optimize(x[2], False)
936 wb, tb = optimize(x[2], False)
936 if wb < wa:
937 if wb < wa:
937 wb, wa = wa, wb
938 wb, wa = wa, wb
938 return max(wa, wb), (op, ta, tb)
939 return max(wa, wb), (op, ta, tb)
939 elif op == 'not':
940 elif op == 'not':
940 o = optimize(x[1], not small)
941 o = optimize(x[1], not small)
941 return o[0], (op, o[1])
942 return o[0], (op, o[1])
942 elif op == 'parentpost':
943 elif op == 'parentpost':
943 o = optimize(x[1], small)
944 o = optimize(x[1], small)
944 return o[0], (op, o[1])
945 return o[0], (op, o[1])
945 elif op == 'group':
946 elif op == 'group':
946 return optimize(x[1], small)
947 return optimize(x[1], small)
947 elif op in 'range list parent ancestorspec':
948 elif op in 'range list parent ancestorspec':
948 if op == 'parent':
949 if op == 'parent':
949 # x^:y means (x^) : y, not x ^ (:y)
950 # x^:y means (x^) : y, not x ^ (:y)
950 post = ('parentpost', x[1])
951 post = ('parentpost', x[1])
951 if x[2][0] == 'dagrangepre':
952 if x[2][0] == 'dagrangepre':
952 return optimize(('dagrange', post, x[2][1]), small)
953 return optimize(('dagrange', post, x[2][1]), small)
953 elif x[2][0] == 'rangepre':
954 elif x[2][0] == 'rangepre':
954 return optimize(('range', post, x[2][1]), small)
955 return optimize(('range', post, x[2][1]), small)
955
956
956 wa, ta = optimize(x[1], small)
957 wa, ta = optimize(x[1], small)
957 wb, tb = optimize(x[2], small)
958 wb, tb = optimize(x[2], small)
958 return wa + wb, (op, ta, tb)
959 return wa + wb, (op, ta, tb)
959 elif op == 'func':
960 elif op == 'func':
960 f = getstring(x[1], _("not a symbol"))
961 f = getstring(x[1], _("not a symbol"))
961 wa, ta = optimize(x[2], small)
962 wa, ta = optimize(x[2], small)
962 if f in ("author branch closed date desc file grep keyword "
963 if f in ("author branch closed date desc file grep keyword "
963 "outgoing user"):
964 "outgoing user"):
964 w = 10 # slow
965 w = 10 # slow
965 elif f in "modifies adds removes":
966 elif f in "modifies adds removes":
966 w = 30 # slower
967 w = 30 # slower
967 elif f == "contains":
968 elif f == "contains":
968 w = 100 # very slow
969 w = 100 # very slow
969 elif f == "ancestor":
970 elif f == "ancestor":
970 w = 1 * smallbonus
971 w = 1 * smallbonus
971 elif f in "reverse limit first":
972 elif f in "reverse limit first":
972 w = 0
973 w = 0
973 elif f in "sort":
974 elif f in "sort":
974 w = 10 # assume most sorts look at changelog
975 w = 10 # assume most sorts look at changelog
975 else:
976 else:
976 w = 1
977 w = 1
977 return w + wa, (op, x[1], ta)
978 return w + wa, (op, x[1], ta)
978 return 1, x
979 return 1, x
979
980
980 class revsetalias(object):
981 class revsetalias(object):
981 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
982 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
982 args = None
983 args = None
983
984
984 def __init__(self, name, value):
985 def __init__(self, name, value):
985 '''Aliases like:
986 '''Aliases like:
986
987
987 h = heads(default)
988 h = heads(default)
988 b($1) = ancestors($1) - ancestors(default)
989 b($1) = ancestors($1) - ancestors(default)
989 '''
990 '''
990 if isinstance(name, tuple): # parameter substitution
991 if isinstance(name, tuple): # parameter substitution
991 self.tree = name
992 self.tree = name
992 self.replacement = value
993 self.replacement = value
993 else: # alias definition
994 else: # alias definition
994 m = self.funcre.search(name)
995 m = self.funcre.search(name)
995 if m:
996 if m:
996 self.tree = ('func', ('symbol', m.group(1)))
997 self.tree = ('func', ('symbol', m.group(1)))
997 self.args = [x.strip() for x in m.group(2).split(',')]
998 self.args = [x.strip() for x in m.group(2).split(',')]
998 for arg in self.args:
999 for arg in self.args:
999 value = value.replace(arg, repr(arg))
1000 value = value.replace(arg, repr(arg))
1000 else:
1001 else:
1001 self.tree = ('symbol', name)
1002 self.tree = ('symbol', name)
1002
1003
1003 self.replacement, pos = parse(value)
1004 self.replacement, pos = parse(value)
1004 if pos != len(value):
1005 if pos != len(value):
1005 raise error.ParseError(_('invalid token'), pos)
1006 raise error.ParseError(_('invalid token'), pos)
1006
1007
1007 def process(self, tree):
1008 def process(self, tree):
1008 if isinstance(tree, tuple):
1009 if isinstance(tree, tuple):
1009 if self.args is None:
1010 if self.args is None:
1010 if tree == self.tree:
1011 if tree == self.tree:
1011 return self.replacement
1012 return self.replacement
1012 elif tree[:2] == self.tree:
1013 elif tree[:2] == self.tree:
1013 l = getlist(tree[2])
1014 l = getlist(tree[2])
1014 if len(l) != len(self.args):
1015 if len(l) != len(self.args):
1015 raise error.ParseError(
1016 raise error.ParseError(
1016 _('invalid number of arguments: %s') % len(l))
1017 _('invalid number of arguments: %s') % len(l))
1017 result = self.replacement
1018 result = self.replacement
1018 for a, v in zip(self.args, l):
1019 for a, v in zip(self.args, l):
1019 valalias = revsetalias(('string', a), v)
1020 valalias = revsetalias(('string', a), v)
1020 result = valalias.process(result)
1021 result = valalias.process(result)
1021 return result
1022 return result
1022 return tuple(map(self.process, tree))
1023 return tuple(map(self.process, tree))
1023 return tree
1024 return tree
1024
1025
1025 def findaliases(ui, tree):
1026 def findaliases(ui, tree):
1026 for k, v in ui.configitems('revsetalias'):
1027 for k, v in ui.configitems('revsetalias'):
1027 alias = revsetalias(k, v)
1028 alias = revsetalias(k, v)
1028 tree = alias.process(tree)
1029 tree = alias.process(tree)
1029 return tree
1030 return tree
1030
1031
1031 parse = parser.parser(tokenize, elements).parse
1032 parse = parser.parser(tokenize, elements).parse
1032
1033
1033 def match(ui, spec):
1034 def match(ui, spec):
1034 if not spec:
1035 if not spec:
1035 raise error.ParseError(_("empty query"))
1036 raise error.ParseError(_("empty query"))
1036 tree, pos = parse(spec)
1037 tree, pos = parse(spec)
1037 if (pos != len(spec)):
1038 if (pos != len(spec)):
1038 raise error.ParseError(_("invalid token"), pos)
1039 raise error.ParseError(_("invalid token"), pos)
1039 if ui:
1040 if ui:
1040 tree = findaliases(ui, tree)
1041 tree = findaliases(ui, tree)
1041 weight, tree = optimize(tree, True)
1042 weight, tree = optimize(tree, True)
1042 def mfunc(repo, subset):
1043 def mfunc(repo, subset):
1043 return getset(repo, subset, tree)
1044 return getset(repo, subset, tree)
1044 return mfunc
1045 return mfunc
1045
1046
1046 def formatspec(expr, *args):
1047 def formatspec(expr, *args):
1047 '''
1048 '''
1048 This is a convenience function for using revsets internally, and
1049 This is a convenience function for using revsets internally, and
1049 escapes arguments appropriately. Aliases are intentionally ignored
1050 escapes arguments appropriately. Aliases are intentionally ignored
1050 so that intended expression behavior isn't accidentally subverted.
1051 so that intended expression behavior isn't accidentally subverted.
1051
1052
1052 Supported arguments:
1053 Supported arguments:
1053
1054
1054 %d = int(arg), no quoting
1055 %d = int(arg), no quoting
1055 %s = string(arg), escaped and single-quoted
1056 %s = string(arg), escaped and single-quoted
1056 %b = arg.branch(), escaped and single-quoted
1057 %b = arg.branch(), escaped and single-quoted
1057 %n = hex(arg), single-quoted
1058 %n = hex(arg), single-quoted
1058 %% = a literal '%'
1059 %% = a literal '%'
1059
1060
1060 >>> formatspec('%d:: and not %d::', 10, 20)
1061 >>> formatspec('%d:: and not %d::', 10, 20)
1061 '10:: and not 20::'
1062 '10:: and not 20::'
1062 >>> formatspec('keyword(%s)', 'foo\\xe9')
1063 >>> formatspec('keyword(%s)', 'foo\\xe9')
1063 "keyword('foo\\\\xe9')"
1064 "keyword('foo\\\\xe9')"
1064 >>> b = lambda: 'default'
1065 >>> b = lambda: 'default'
1065 >>> b.branch = b
1066 >>> b.branch = b
1066 >>> formatspec('branch(%b)', b)
1067 >>> formatspec('branch(%b)', b)
1067 "branch('default')"
1068 "branch('default')"
1068 '''
1069 '''
1069
1070
1070 def quote(s):
1071 def quote(s):
1071 return repr(str(s))
1072 return repr(str(s))
1072
1073
1073 ret = ''
1074 ret = ''
1074 pos = 0
1075 pos = 0
1075 arg = 0
1076 arg = 0
1076 while pos < len(expr):
1077 while pos < len(expr):
1077 c = expr[pos]
1078 c = expr[pos]
1078 if c == '%':
1079 if c == '%':
1079 pos += 1
1080 pos += 1
1080 d = expr[pos]
1081 d = expr[pos]
1081 if d == '%':
1082 if d == '%':
1082 ret += d
1083 ret += d
1083 elif d == 'd':
1084 elif d == 'd':
1084 ret += str(int(args[arg]))
1085 ret += str(int(args[arg]))
1085 arg += 1
1086 arg += 1
1086 elif d == 's':
1087 elif d == 's':
1087 ret += quote(args[arg])
1088 ret += quote(args[arg])
1088 arg += 1
1089 arg += 1
1089 elif d == 'n':
1090 elif d == 'n':
1090 ret += quote(node.hex(args[arg]))
1091 ret += quote(node.hex(args[arg]))
1091 arg += 1
1092 arg += 1
1092 elif d == 'b':
1093 elif d == 'b':
1093 ret += quote(args[arg].branch())
1094 ret += quote(args[arg].branch())
1094 arg += 1
1095 arg += 1
1095 else:
1096 else:
1096 raise util.Abort('unexpected revspec format character %s' % d)
1097 raise util.Abort('unexpected revspec format character %s' % d)
1097 else:
1098 else:
1098 ret += c
1099 ret += c
1099 pos += 1
1100 pos += 1
1100
1101
1101 return ret
1102 return ret
1102
1103
1103 # tell hggettext to extract docstrings from these functions:
1104 # tell hggettext to extract docstrings from these functions:
1104 i18nfunctions = symbols.values()
1105 i18nfunctions = symbols.values()
@@ -1,532 +1,580 b''
1 # The tests in test-bisect are done on a linear history. Here the
1 # The tests in test-bisect are done on a linear history. Here the
2 # following repository history is used for testing:
2 # following repository history is used for testing:
3 #
3 #
4 # 17
4 # 17
5 # |
5 # |
6 # 18 16
6 # 18 16
7 # \ /
7 # \ /
8 # 15
8 # 15
9 # / \
9 # / \
10 # / \
10 # / \
11 # 10 13
11 # 10 13
12 # / \ |
12 # / \ |
13 # / \ | 14
13 # / \ | 14
14 # 7 6 9 12 /
14 # 7 6 9 12 /
15 # \ / \ | |/
15 # \ / \ | |/
16 # 4 \ | 11
16 # 4 \ | 11
17 # \ \ | /
17 # \ \ | /
18 # 3 5 | /
18 # 3 5 | /
19 # \ / |/
19 # \ / |/
20 # 2 8
20 # 2 8
21 # \ /
21 # \ /
22 # 1
22 # 1
23 # |
23 # |
24 # 0
24 # 0
25
25
26 init
26 init
27
27
28 $ hg init
28 $ hg init
29
29
30 committing changes
30 committing changes
31
31
32 $ echo > a
32 $ echo > a
33 $ echo '0' >> a
33 $ echo '0' >> a
34 $ hg add a
34 $ hg add a
35 $ hg ci -m "0" -d "0 0"
35 $ hg ci -m "0" -d "0 0"
36 $ echo '1' >> a
36 $ echo '1' >> a
37 $ hg ci -m "1" -d "1 0"
37 $ hg ci -m "1" -d "1 0"
38 $ echo '2' >> a
38 $ echo '2' >> a
39 $ hg ci -m "2" -d "2 0"
39 $ hg ci -m "2" -d "2 0"
40 $ echo '3' >> a
40 $ echo '3' >> a
41 $ hg ci -m "3" -d "3 0"
41 $ hg ci -m "3" -d "3 0"
42 $ echo '4' >> a
42 $ echo '4' >> a
43 $ hg ci -m "4" -d "4 0"
43 $ hg ci -m "4" -d "4 0"
44
44
45 create branch
45 create branch
46
46
47 $ hg up -r 2
47 $ hg up -r 2
48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 $ echo '5' >> b
49 $ echo '5' >> b
50 $ hg add b
50 $ hg add b
51 $ hg ci -m "5" -d "5 0"
51 $ hg ci -m "5" -d "5 0"
52 created new head
52 created new head
53
53
54 merge
54 merge
55
55
56 $ hg merge
56 $ hg merge
57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 (branch merge, don't forget to commit)
58 (branch merge, don't forget to commit)
59 $ hg ci -m "merge 4,5" -d "6 0"
59 $ hg ci -m "merge 4,5" -d "6 0"
60
60
61 create branch
61 create branch
62
62
63 $ hg up -r 4
63 $ hg up -r 4
64 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
64 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
65 $ echo '7' > c
65 $ echo '7' > c
66 $ hg add c
66 $ hg add c
67 $ hg ci -m "7" -d "7 0"
67 $ hg ci -m "7" -d "7 0"
68 created new head
68 created new head
69
69
70 create branch
70 create branch
71
71
72 $ hg up -r 1
72 $ hg up -r 1
73 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
73 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
74 $ echo '8' > d
74 $ echo '8' > d
75 $ hg add d
75 $ hg add d
76 $ hg ci -m "8" -d "8 0"
76 $ hg ci -m "8" -d "8 0"
77 created new head
77 created new head
78 $ echo '9' >> d
78 $ echo '9' >> d
79 $ hg ci -m "9" -d "9 0"
79 $ hg ci -m "9" -d "9 0"
80
80
81 merge
81 merge
82
82
83 $ hg merge -r 6
83 $ hg merge -r 6
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 (branch merge, don't forget to commit)
85 (branch merge, don't forget to commit)
86 $ hg ci -m "merge 6,9" -d "10 0"
86 $ hg ci -m "merge 6,9" -d "10 0"
87
87
88 create branch
88 create branch
89
89
90 $ hg up -r 8
90 $ hg up -r 8
91 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
91 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
92 $ echo '11' > e
92 $ echo '11' > e
93 $ hg add e
93 $ hg add e
94 $ hg ci -m "11" -d "11 0"
94 $ hg ci -m "11" -d "11 0"
95 created new head
95 created new head
96 $ echo '12' >> e
96 $ echo '12' >> e
97 $ hg ci -m "12" -d "12 0"
97 $ hg ci -m "12" -d "12 0"
98 $ echo '13' >> e
98 $ echo '13' >> e
99 $ hg ci -m "13" -d "13 0"
99 $ hg ci -m "13" -d "13 0"
100
100
101 create branch
101 create branch
102
102
103 $ hg up -r 11
103 $ hg up -r 11
104 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
104 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
105 $ echo '14' > f
105 $ echo '14' > f
106 $ hg add f
106 $ hg add f
107 $ hg ci -m "14" -d "14 0"
107 $ hg ci -m "14" -d "14 0"
108 created new head
108 created new head
109
109
110 merge
110 merge
111
111
112 $ hg up -r 13 -C
112 $ hg up -r 13 -C
113 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
113 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
114 $ hg merge -r 10
114 $ hg merge -r 10
115 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
115 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 (branch merge, don't forget to commit)
116 (branch merge, don't forget to commit)
117 $ hg ci -m "merge 10,13" -d "15 0"
117 $ hg ci -m "merge 10,13" -d "15 0"
118 $ echo '16' >> e
118 $ echo '16' >> e
119 $ hg ci -m "16" -d "16 0"
119 $ hg ci -m "16" -d "16 0"
120 $ echo '17' >> e
120 $ echo '17' >> e
121 $ hg ci -m "17" -d "17 0"
121 $ hg ci -m "17" -d "17 0"
122
122
123 create branch
123 create branch
124
124
125 $ hg up -r 15
125 $ hg up -r 15
126 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
126 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
127 $ echo '18' >> e
127 $ echo '18' >> e
128 $ hg ci -m "18" -d "18 0"
128 $ hg ci -m "18" -d "18 0"
129 created new head
129 created new head
130
130
131 log
131 log
132
132
133 $ hg log
133 $ hg log
134 changeset: 18:d42e18c7bc9b
134 changeset: 18:d42e18c7bc9b
135 tag: tip
135 tag: tip
136 parent: 15:857b178a7cf3
136 parent: 15:857b178a7cf3
137 user: test
137 user: test
138 date: Thu Jan 01 00:00:18 1970 +0000
138 date: Thu Jan 01 00:00:18 1970 +0000
139 summary: 18
139 summary: 18
140
140
141 changeset: 17:228c06deef46
141 changeset: 17:228c06deef46
142 user: test
142 user: test
143 date: Thu Jan 01 00:00:17 1970 +0000
143 date: Thu Jan 01 00:00:17 1970 +0000
144 summary: 17
144 summary: 17
145
145
146 changeset: 16:609d82a7ebae
146 changeset: 16:609d82a7ebae
147 user: test
147 user: test
148 date: Thu Jan 01 00:00:16 1970 +0000
148 date: Thu Jan 01 00:00:16 1970 +0000
149 summary: 16
149 summary: 16
150
150
151 changeset: 15:857b178a7cf3
151 changeset: 15:857b178a7cf3
152 parent: 13:b0a32c86eb31
152 parent: 13:b0a32c86eb31
153 parent: 10:429fcd26f52d
153 parent: 10:429fcd26f52d
154 user: test
154 user: test
155 date: Thu Jan 01 00:00:15 1970 +0000
155 date: Thu Jan 01 00:00:15 1970 +0000
156 summary: merge 10,13
156 summary: merge 10,13
157
157
158 changeset: 14:faa450606157
158 changeset: 14:faa450606157
159 parent: 11:82ca6f06eccd
159 parent: 11:82ca6f06eccd
160 user: test
160 user: test
161 date: Thu Jan 01 00:00:14 1970 +0000
161 date: Thu Jan 01 00:00:14 1970 +0000
162 summary: 14
162 summary: 14
163
163
164 changeset: 13:b0a32c86eb31
164 changeset: 13:b0a32c86eb31
165 user: test
165 user: test
166 date: Thu Jan 01 00:00:13 1970 +0000
166 date: Thu Jan 01 00:00:13 1970 +0000
167 summary: 13
167 summary: 13
168
168
169 changeset: 12:9f259202bbe7
169 changeset: 12:9f259202bbe7
170 user: test
170 user: test
171 date: Thu Jan 01 00:00:12 1970 +0000
171 date: Thu Jan 01 00:00:12 1970 +0000
172 summary: 12
172 summary: 12
173
173
174 changeset: 11:82ca6f06eccd
174 changeset: 11:82ca6f06eccd
175 parent: 8:dab8161ac8fc
175 parent: 8:dab8161ac8fc
176 user: test
176 user: test
177 date: Thu Jan 01 00:00:11 1970 +0000
177 date: Thu Jan 01 00:00:11 1970 +0000
178 summary: 11
178 summary: 11
179
179
180 changeset: 10:429fcd26f52d
180 changeset: 10:429fcd26f52d
181 parent: 9:3c77083deb4a
181 parent: 9:3c77083deb4a
182 parent: 6:a214d5d3811a
182 parent: 6:a214d5d3811a
183 user: test
183 user: test
184 date: Thu Jan 01 00:00:10 1970 +0000
184 date: Thu Jan 01 00:00:10 1970 +0000
185 summary: merge 6,9
185 summary: merge 6,9
186
186
187 changeset: 9:3c77083deb4a
187 changeset: 9:3c77083deb4a
188 user: test
188 user: test
189 date: Thu Jan 01 00:00:09 1970 +0000
189 date: Thu Jan 01 00:00:09 1970 +0000
190 summary: 9
190 summary: 9
191
191
192 changeset: 8:dab8161ac8fc
192 changeset: 8:dab8161ac8fc
193 parent: 1:4ca5088da217
193 parent: 1:4ca5088da217
194 user: test
194 user: test
195 date: Thu Jan 01 00:00:08 1970 +0000
195 date: Thu Jan 01 00:00:08 1970 +0000
196 summary: 8
196 summary: 8
197
197
198 changeset: 7:50c76098bbf2
198 changeset: 7:50c76098bbf2
199 parent: 4:5c668c22234f
199 parent: 4:5c668c22234f
200 user: test
200 user: test
201 date: Thu Jan 01 00:00:07 1970 +0000
201 date: Thu Jan 01 00:00:07 1970 +0000
202 summary: 7
202 summary: 7
203
203
204 changeset: 6:a214d5d3811a
204 changeset: 6:a214d5d3811a
205 parent: 5:385a529b6670
205 parent: 5:385a529b6670
206 parent: 4:5c668c22234f
206 parent: 4:5c668c22234f
207 user: test
207 user: test
208 date: Thu Jan 01 00:00:06 1970 +0000
208 date: Thu Jan 01 00:00:06 1970 +0000
209 summary: merge 4,5
209 summary: merge 4,5
210
210
211 changeset: 5:385a529b6670
211 changeset: 5:385a529b6670
212 parent: 2:051e12f87bf1
212 parent: 2:051e12f87bf1
213 user: test
213 user: test
214 date: Thu Jan 01 00:00:05 1970 +0000
214 date: Thu Jan 01 00:00:05 1970 +0000
215 summary: 5
215 summary: 5
216
216
217 changeset: 4:5c668c22234f
217 changeset: 4:5c668c22234f
218 user: test
218 user: test
219 date: Thu Jan 01 00:00:04 1970 +0000
219 date: Thu Jan 01 00:00:04 1970 +0000
220 summary: 4
220 summary: 4
221
221
222 changeset: 3:0950834f0a9c
222 changeset: 3:0950834f0a9c
223 user: test
223 user: test
224 date: Thu Jan 01 00:00:03 1970 +0000
224 date: Thu Jan 01 00:00:03 1970 +0000
225 summary: 3
225 summary: 3
226
226
227 changeset: 2:051e12f87bf1
227 changeset: 2:051e12f87bf1
228 user: test
228 user: test
229 date: Thu Jan 01 00:00:02 1970 +0000
229 date: Thu Jan 01 00:00:02 1970 +0000
230 summary: 2
230 summary: 2
231
231
232 changeset: 1:4ca5088da217
232 changeset: 1:4ca5088da217
233 user: test
233 user: test
234 date: Thu Jan 01 00:00:01 1970 +0000
234 date: Thu Jan 01 00:00:01 1970 +0000
235 summary: 1
235 summary: 1
236
236
237 changeset: 0:33b1f9bc8bc5
237 changeset: 0:33b1f9bc8bc5
238 user: test
238 user: test
239 date: Thu Jan 01 00:00:00 1970 +0000
239 date: Thu Jan 01 00:00:00 1970 +0000
240 summary: 0
240 summary: 0
241
241
242
242
243 hg up -C
243 hg up -C
244
244
245 $ hg up -C
245 $ hg up -C
246 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
246 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
247
247
248 complex bisect test 1 # first bad rev is 9
248 complex bisect test 1 # first bad rev is 9
249
249
250 $ hg bisect -r
250 $ hg bisect -r
251 $ hg bisect -g 0
251 $ hg bisect -g 0
252 $ hg bisect -b 17 # -> update to rev 6
252 $ hg bisect -b 17 # -> update to rev 6
253 Testing changeset 6:a214d5d3811a (15 changesets remaining, ~3 tests)
253 Testing changeset 6:a214d5d3811a (15 changesets remaining, ~3 tests)
254 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
254 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
255 $ hg bisect -g # -> update to rev 13
255 $ hg bisect -g # -> update to rev 13
256 Testing changeset 13:b0a32c86eb31 (9 changesets remaining, ~3 tests)
256 Testing changeset 13:b0a32c86eb31 (9 changesets remaining, ~3 tests)
257 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
257 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
258 $ hg bisect -s # -> update to rev 10
258 $ hg bisect -s # -> update to rev 10
259 Testing changeset 10:429fcd26f52d (9 changesets remaining, ~3 tests)
259 Testing changeset 10:429fcd26f52d (9 changesets remaining, ~3 tests)
260 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
260 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
261 $ hg bisect -b # -> update to rev 8
261 $ hg bisect -b # -> update to rev 8
262 Testing changeset 8:dab8161ac8fc (3 changesets remaining, ~1 tests)
262 Testing changeset 8:dab8161ac8fc (3 changesets remaining, ~1 tests)
263 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
263 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
264 $ hg bisect -g # -> update to rev 9
264 $ hg bisect -g # -> update to rev 9
265 Testing changeset 9:3c77083deb4a (2 changesets remaining, ~1 tests)
265 Testing changeset 9:3c77083deb4a (2 changesets remaining, ~1 tests)
266 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 $ hg bisect -b
267 $ hg bisect -b
268 The first bad revision is:
268 The first bad revision is:
269 changeset: 9:3c77083deb4a
269 changeset: 9:3c77083deb4a
270 user: test
270 user: test
271 date: Thu Jan 01 00:00:09 1970 +0000
271 date: Thu Jan 01 00:00:09 1970 +0000
272 summary: 9
272 summary: 9
273
273
274 $ hg log -q -r 'bisect(range)'
274 $ hg log -q -r 'bisect(range)'
275 0:33b1f9bc8bc5
275 0:33b1f9bc8bc5
276 1:4ca5088da217
276 1:4ca5088da217
277 2:051e12f87bf1
277 2:051e12f87bf1
278 3:0950834f0a9c
278 3:0950834f0a9c
279 4:5c668c22234f
279 4:5c668c22234f
280 5:385a529b6670
280 5:385a529b6670
281 6:a214d5d3811a
281 6:a214d5d3811a
282 8:dab8161ac8fc
282 8:dab8161ac8fc
283 9:3c77083deb4a
283 9:3c77083deb4a
284 10:429fcd26f52d
284 10:429fcd26f52d
285 11:82ca6f06eccd
285 11:82ca6f06eccd
286 12:9f259202bbe7
286 12:9f259202bbe7
287 13:b0a32c86eb31
287 13:b0a32c86eb31
288 15:857b178a7cf3
288 15:857b178a7cf3
289 16:609d82a7ebae
289 16:609d82a7ebae
290 17:228c06deef46
290 17:228c06deef46
291 $ hg log -q -r 'bisect(pruned)'
292 0:33b1f9bc8bc5
293 6:a214d5d3811a
294 8:dab8161ac8fc
295 9:3c77083deb4a
296 10:429fcd26f52d
297 13:b0a32c86eb31
298 17:228c06deef46
291
299
292 complex bisect test 2 # first good rev is 13
300 complex bisect test 2 # first good rev is 13
293
301
294 $ hg bisect -r
302 $ hg bisect -r
295 $ hg bisect -g 18
303 $ hg bisect -g 18
296 $ hg bisect -b 1 # -> update to rev 6
304 $ hg bisect -b 1 # -> update to rev 6
297 Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests)
305 Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests)
298 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
306 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
299 $ hg bisect -s # -> update to rev 10
307 $ hg bisect -s # -> update to rev 10
300 Testing changeset 10:429fcd26f52d (13 changesets remaining, ~3 tests)
308 Testing changeset 10:429fcd26f52d (13 changesets remaining, ~3 tests)
301 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
309 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
302 $ hg bisect -b # -> update to rev 12
310 $ hg bisect -b # -> update to rev 12
303 Testing changeset 12:9f259202bbe7 (5 changesets remaining, ~2 tests)
311 Testing changeset 12:9f259202bbe7 (5 changesets remaining, ~2 tests)
304 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
312 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
305 $ hg bisect -b # -> update to rev 13
313 $ hg bisect -b # -> update to rev 13
306 Testing changeset 13:b0a32c86eb31 (3 changesets remaining, ~1 tests)
314 Testing changeset 13:b0a32c86eb31 (3 changesets remaining, ~1 tests)
307 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
315 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
308 $ hg bisect -g
316 $ hg bisect -g
309 The first good revision is:
317 The first good revision is:
310 changeset: 13:b0a32c86eb31
318 changeset: 13:b0a32c86eb31
311 user: test
319 user: test
312 date: Thu Jan 01 00:00:13 1970 +0000
320 date: Thu Jan 01 00:00:13 1970 +0000
313 summary: 13
321 summary: 13
314
322
315 $ hg log -q -r 'bisect(range)'
323 $ hg log -q -r 'bisect(range)'
316 1:4ca5088da217
324 1:4ca5088da217
317 2:051e12f87bf1
325 2:051e12f87bf1
318 3:0950834f0a9c
326 3:0950834f0a9c
319 4:5c668c22234f
327 4:5c668c22234f
320 5:385a529b6670
328 5:385a529b6670
321 6:a214d5d3811a
329 6:a214d5d3811a
322 8:dab8161ac8fc
330 8:dab8161ac8fc
323 9:3c77083deb4a
331 9:3c77083deb4a
324 10:429fcd26f52d
332 10:429fcd26f52d
325 11:82ca6f06eccd
333 11:82ca6f06eccd
326 12:9f259202bbe7
334 12:9f259202bbe7
327 13:b0a32c86eb31
335 13:b0a32c86eb31
328 15:857b178a7cf3
336 15:857b178a7cf3
329 18:d42e18c7bc9b
337 18:d42e18c7bc9b
338 $ hg log -q -r 'bisect(pruned)'
339 1:4ca5088da217
340 6:a214d5d3811a
341 10:429fcd26f52d
342 12:9f259202bbe7
343 13:b0a32c86eb31
344 18:d42e18c7bc9b
330
345
331 complex bisect test 3
346 complex bisect test 3
332
347
333 first bad rev is 15
348 first bad rev is 15
334 10,9,13 are skipped an might be the first bad revisions as well
349 10,9,13 are skipped an might be the first bad revisions as well
335
350
336 $ hg bisect -r
351 $ hg bisect -r
337 $ hg bisect -g 1
352 $ hg bisect -g 1
338 $ hg bisect -b 16 # -> update to rev 6
353 $ hg bisect -b 16 # -> update to rev 6
339 Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests)
354 Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests)
340 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
355 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
341 $ hg bisect -g # -> update to rev 13
356 $ hg bisect -g # -> update to rev 13
342 Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests)
357 Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests)
343 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
358 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
344 $ hg bisect -s # -> update to rev 10
359 $ hg bisect -s # -> update to rev 10
345 Testing changeset 10:429fcd26f52d (8 changesets remaining, ~3 tests)
360 Testing changeset 10:429fcd26f52d (8 changesets remaining, ~3 tests)
346 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
361 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
347 $ hg bisect -s # -> update to rev 12
362 $ hg bisect -s # -> update to rev 12
348 Testing changeset 12:9f259202bbe7 (8 changesets remaining, ~3 tests)
363 Testing changeset 12:9f259202bbe7 (8 changesets remaining, ~3 tests)
349 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
364 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
350 $ hg bisect -g # -> update to rev 9
365 $ hg bisect -g # -> update to rev 9
351 Testing changeset 9:3c77083deb4a (5 changesets remaining, ~2 tests)
366 Testing changeset 9:3c77083deb4a (5 changesets remaining, ~2 tests)
352 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
367 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
353 $ hg bisect -s # -> update to rev 15
368 $ hg bisect -s # -> update to rev 15
354 Testing changeset 15:857b178a7cf3 (5 changesets remaining, ~2 tests)
369 Testing changeset 15:857b178a7cf3 (5 changesets remaining, ~2 tests)
355 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
370 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
356 $ hg bisect -b
371 $ hg bisect -b
357 Due to skipped revisions, the first bad revision could be any of:
372 Due to skipped revisions, the first bad revision could be any of:
358 changeset: 9:3c77083deb4a
373 changeset: 9:3c77083deb4a
359 user: test
374 user: test
360 date: Thu Jan 01 00:00:09 1970 +0000
375 date: Thu Jan 01 00:00:09 1970 +0000
361 summary: 9
376 summary: 9
362
377
363 changeset: 10:429fcd26f52d
378 changeset: 10:429fcd26f52d
364 parent: 9:3c77083deb4a
379 parent: 9:3c77083deb4a
365 parent: 6:a214d5d3811a
380 parent: 6:a214d5d3811a
366 user: test
381 user: test
367 date: Thu Jan 01 00:00:10 1970 +0000
382 date: Thu Jan 01 00:00:10 1970 +0000
368 summary: merge 6,9
383 summary: merge 6,9
369
384
370 changeset: 13:b0a32c86eb31
385 changeset: 13:b0a32c86eb31
371 user: test
386 user: test
372 date: Thu Jan 01 00:00:13 1970 +0000
387 date: Thu Jan 01 00:00:13 1970 +0000
373 summary: 13
388 summary: 13
374
389
375 changeset: 15:857b178a7cf3
390 changeset: 15:857b178a7cf3
376 parent: 13:b0a32c86eb31
391 parent: 13:b0a32c86eb31
377 parent: 10:429fcd26f52d
392 parent: 10:429fcd26f52d
378 user: test
393 user: test
379 date: Thu Jan 01 00:00:15 1970 +0000
394 date: Thu Jan 01 00:00:15 1970 +0000
380 summary: merge 10,13
395 summary: merge 10,13
381
396
382 $ hg log -q -r 'bisect(range)'
397 $ hg log -q -r 'bisect(range)'
383 1:4ca5088da217
398 1:4ca5088da217
384 2:051e12f87bf1
399 2:051e12f87bf1
385 3:0950834f0a9c
400 3:0950834f0a9c
386 4:5c668c22234f
401 4:5c668c22234f
387 5:385a529b6670
402 5:385a529b6670
388 6:a214d5d3811a
403 6:a214d5d3811a
389 8:dab8161ac8fc
404 8:dab8161ac8fc
390 9:3c77083deb4a
405 9:3c77083deb4a
391 10:429fcd26f52d
406 10:429fcd26f52d
392 11:82ca6f06eccd
407 11:82ca6f06eccd
393 12:9f259202bbe7
408 12:9f259202bbe7
394 13:b0a32c86eb31
409 13:b0a32c86eb31
395 15:857b178a7cf3
410 15:857b178a7cf3
396 16:609d82a7ebae
411 16:609d82a7ebae
412 $ hg log -q -r 'bisect(pruned)'
413 1:4ca5088da217
414 6:a214d5d3811a
415 9:3c77083deb4a
416 10:429fcd26f52d
417 12:9f259202bbe7
418 13:b0a32c86eb31
419 15:857b178a7cf3
420 16:609d82a7ebae
397
421
398 complex bisect test 4
422 complex bisect test 4
399
423
400 first good revision is 17
424 first good revision is 17
401 15,16 are skipped an might be the first good revisions as well
425 15,16 are skipped an might be the first good revisions as well
402
426
403 $ hg bisect -r
427 $ hg bisect -r
404 $ hg bisect -g 17
428 $ hg bisect -g 17
405 $ hg bisect -b 8 # -> update to rev 10
429 $ hg bisect -b 8 # -> update to rev 10
406 Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests)
430 Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests)
407 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
431 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
408 $ hg bisect -b # -> update to rev 13
432 $ hg bisect -b # -> update to rev 13
409 Testing changeset 10:429fcd26f52d (5 changesets remaining, ~2 tests)
433 Testing changeset 10:429fcd26f52d (5 changesets remaining, ~2 tests)
410 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
434 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
411 $ hg bisect -b # -> update to rev 15
435 $ hg bisect -b # -> update to rev 15
412 Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests)
436 Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests)
413 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 $ hg bisect -s # -> update to rev 16
438 $ hg bisect -s # -> update to rev 16
415 Testing changeset 16:609d82a7ebae (3 changesets remaining, ~1 tests)
439 Testing changeset 16:609d82a7ebae (3 changesets remaining, ~1 tests)
416 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
440 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
417 $ hg bisect -s
441 $ hg bisect -s
418 Due to skipped revisions, the first good revision could be any of:
442 Due to skipped revisions, the first good revision could be any of:
419 changeset: 15:857b178a7cf3
443 changeset: 15:857b178a7cf3
420 parent: 13:b0a32c86eb31
444 parent: 13:b0a32c86eb31
421 parent: 10:429fcd26f52d
445 parent: 10:429fcd26f52d
422 user: test
446 user: test
423 date: Thu Jan 01 00:00:15 1970 +0000
447 date: Thu Jan 01 00:00:15 1970 +0000
424 summary: merge 10,13
448 summary: merge 10,13
425
449
426 changeset: 16:609d82a7ebae
450 changeset: 16:609d82a7ebae
427 user: test
451 user: test
428 date: Thu Jan 01 00:00:16 1970 +0000
452 date: Thu Jan 01 00:00:16 1970 +0000
429 summary: 16
453 summary: 16
430
454
431 changeset: 17:228c06deef46
455 changeset: 17:228c06deef46
432 user: test
456 user: test
433 date: Thu Jan 01 00:00:17 1970 +0000
457 date: Thu Jan 01 00:00:17 1970 +0000
434 summary: 17
458 summary: 17
435
459
436 $ hg log -q -r 'bisect(range)'
460 $ hg log -q -r 'bisect(range)'
437 8:dab8161ac8fc
461 8:dab8161ac8fc
438 9:3c77083deb4a
462 9:3c77083deb4a
439 10:429fcd26f52d
463 10:429fcd26f52d
440 11:82ca6f06eccd
464 11:82ca6f06eccd
441 12:9f259202bbe7
465 12:9f259202bbe7
442 13:b0a32c86eb31
466 13:b0a32c86eb31
443 15:857b178a7cf3
467 15:857b178a7cf3
444 16:609d82a7ebae
468 16:609d82a7ebae
445 17:228c06deef46
469 17:228c06deef46
470 $ hg log -q -r 'bisect(pruned)'
471 8:dab8161ac8fc
472 10:429fcd26f52d
473 13:b0a32c86eb31
474 15:857b178a7cf3
475 16:609d82a7ebae
476 17:228c06deef46
446
477
447 test unrelated revs:
478 test unrelated revs:
448
479
449 $ hg bisect --reset
480 $ hg bisect --reset
450 $ hg bisect -b 7
481 $ hg bisect -b 7
451 $ hg bisect -g 14
482 $ hg bisect -g 14
452 abort: starting revisions are not directly related
483 abort: starting revisions are not directly related
453 [255]
484 [255]
454 $ hg log -q -r 'bisect(range)'
485 $ hg log -q -r 'bisect(range)'
486 $ hg log -q -r 'bisect(pruned)'
455 $ hg bisect --reset
487 $ hg bisect --reset
456
488
457 end at merge: 17 bad, 11 good (but 9 is first bad)
489 end at merge: 17 bad, 11 good (but 9 is first bad)
458
490
459 $ hg bisect -r
491 $ hg bisect -r
460 $ hg bisect -b 17
492 $ hg bisect -b 17
461 $ hg bisect -g 11
493 $ hg bisect -g 11
462 Testing changeset 13:b0a32c86eb31 (5 changesets remaining, ~2 tests)
494 Testing changeset 13:b0a32c86eb31 (5 changesets remaining, ~2 tests)
463 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
495 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
464 $ hg bisect -g
496 $ hg bisect -g
465 Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests)
497 Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests)
466 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
498 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
467 $ hg bisect -b
499 $ hg bisect -b
468 The first bad revision is:
500 The first bad revision is:
469 changeset: 15:857b178a7cf3
501 changeset: 15:857b178a7cf3
470 parent: 13:b0a32c86eb31
502 parent: 13:b0a32c86eb31
471 parent: 10:429fcd26f52d
503 parent: 10:429fcd26f52d
472 user: test
504 user: test
473 date: Thu Jan 01 00:00:15 1970 +0000
505 date: Thu Jan 01 00:00:15 1970 +0000
474 summary: merge 10,13
506 summary: merge 10,13
475
507
476 Not all ancestors of this changeset have been checked.
508 Not all ancestors of this changeset have been checked.
477 Use bisect --extend to continue the bisection from
509 Use bisect --extend to continue the bisection from
478 the common ancestor, dab8161ac8fc.
510 the common ancestor, dab8161ac8fc.
479 $ hg log -q -r 'bisect(range)'
511 $ hg log -q -r 'bisect(range)'
480 11:82ca6f06eccd
512 11:82ca6f06eccd
481 12:9f259202bbe7
513 12:9f259202bbe7
482 13:b0a32c86eb31
514 13:b0a32c86eb31
483 15:857b178a7cf3
515 15:857b178a7cf3
484 16:609d82a7ebae
516 16:609d82a7ebae
485 17:228c06deef46
517 17:228c06deef46
518 $ hg log -q -r 'bisect(pruned)'
519 11:82ca6f06eccd
520 13:b0a32c86eb31
521 15:857b178a7cf3
522 17:228c06deef46
486 $ hg bisect --extend
523 $ hg bisect --extend
487 Extending search to changeset 8:dab8161ac8fc
524 Extending search to changeset 8:dab8161ac8fc
488 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
525 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
489 $ hg bisect -g # dab8161ac8fc
526 $ hg bisect -g # dab8161ac8fc
490 Testing changeset 9:3c77083deb4a (3 changesets remaining, ~1 tests)
527 Testing changeset 9:3c77083deb4a (3 changesets remaining, ~1 tests)
491 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
528 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
492 $ hg bisect -b
529 $ hg bisect -b
493 The first bad revision is:
530 The first bad revision is:
494 changeset: 9:3c77083deb4a
531 changeset: 9:3c77083deb4a
495 user: test
532 user: test
496 date: Thu Jan 01 00:00:09 1970 +0000
533 date: Thu Jan 01 00:00:09 1970 +0000
497 summary: 9
534 summary: 9
498
535
499 $ hg log -q -r 'bisect(range)'
536 $ hg log -q -r 'bisect(range)'
500 8:dab8161ac8fc
537 8:dab8161ac8fc
501 9:3c77083deb4a
538 9:3c77083deb4a
502 10:429fcd26f52d
539 10:429fcd26f52d
503 11:82ca6f06eccd
540 11:82ca6f06eccd
504 12:9f259202bbe7
541 12:9f259202bbe7
505 13:b0a32c86eb31
542 13:b0a32c86eb31
506 15:857b178a7cf3
543 15:857b178a7cf3
507 16:609d82a7ebae
544 16:609d82a7ebae
508 17:228c06deef46
545 17:228c06deef46
546 $ hg log -q -r 'bisect(pruned)'
547 8:dab8161ac8fc
548 9:3c77083deb4a
549 11:82ca6f06eccd
550 13:b0a32c86eb31
551 15:857b178a7cf3
552 17:228c06deef46
509
553
510 user adds irrelevant but consistent information (here: -g 2) to bisect state
554 user adds irrelevant but consistent information (here: -g 2) to bisect state
511
555
512 $ hg bisect -r
556 $ hg bisect -r
513 $ hg bisect -b 13
557 $ hg bisect -b 13
514 $ hg bisect -g 8
558 $ hg bisect -g 8
515 Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests)
559 Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests)
516 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
560 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
517 $ hg bisect -g 2
561 $ hg bisect -g 2
518 Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests)
562 Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests)
519 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
563 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
520 $ hg bisect -b
564 $ hg bisect -b
521 The first bad revision is:
565 The first bad revision is:
522 changeset: 11:82ca6f06eccd
566 changeset: 11:82ca6f06eccd
523 parent: 8:dab8161ac8fc
567 parent: 8:dab8161ac8fc
524 user: test
568 user: test
525 date: Thu Jan 01 00:00:11 1970 +0000
569 date: Thu Jan 01 00:00:11 1970 +0000
526 summary: 11
570 summary: 11
527
571
528 $ hg log -q -r 'bisect(range)'
572 $ hg log -q -r 'bisect(range)'
529 8:dab8161ac8fc
573 8:dab8161ac8fc
530 11:82ca6f06eccd
574 11:82ca6f06eccd
531 12:9f259202bbe7
575 12:9f259202bbe7
532 13:b0a32c86eb31
576 13:b0a32c86eb31
577 $ hg log -q -r 'bisect(pruned)'
578 8:dab8161ac8fc
579 11:82ca6f06eccd
580 13:b0a32c86eb31
General Comments 0
You need to be logged in to leave comments. Login now