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