##// END OF EJS Templates
rust-status: add missing variants to `Dispatch` enum...
rust-status: add missing variants to `Dispatch` enum Differential Revision: https://phab.mercurial-scm.org/D8088

File last commit:

r44937:9d2b2df2 default
r45013:61709b84 default
Show More
hbisect.py
329 lines | 10.9 KiB | text/x-python | PythonLexer
Matt Mackall
bisect: make bisect a built-in command
r5775 # changelog bisection for mercurial
#
# Copyright 2007 Matt Mackall
# Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
Martin Geisler
add blank line after copyright notices and after header
r8228 #
Matt Mackall
bisect: make bisect a built-in command
r5775 # Inspired by git bisect, extension skeleton taken from mq.py.
#
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Matt Mackall
bisect: make bisect a built-in command
r5775
Gregory Szorc
hbisect: use absolute_import
r25952 from __future__ import absolute_import
Martin von Zweigbergk
util: drop alias for collections.deque...
r25113 import collections
Denis Laxalde
bisect: replace try:/finally: by a "restore_state" context manager...
r44064 import contextlib
Gregory Szorc
hbisect: use absolute_import
r25952
from .i18n import _
from .node import (
hex,
short,
)
Augie Fackler
formatting: blacken the codebase...
r43346 from . import error
Matt Mackall
bisect: make bisect a built-in command
r5775
David Soria Parra
hbisect: pass repo into hbisect.bisect...
r35126 def bisect(repo, state):
Bernhard Leiner
Add support for multiple possible bisect results (issue1228, issue1182)...
r6858 """find the next node (if any) for testing during a bisect search.
returns a (nodes, number, good) tuple.
'nodes' is the final result of the bisect if 'number' is 0.
Otherwise 'number' indicates the remaining possible candidates for
the search and 'nodes' contains the next bisect target.
'good' is True if bisect is searching for a first good changeset, False
if searching for a first bad one.
"""
timeless
bisect: do not crash with rewritten commits
r42501 repo = repo.unfiltered()
David Soria Parra
hbisect: pass repo into hbisect.bisect...
r35126 changelog = repo.changelog
Matt Mackall
bisect: make bisect a built-in command
r5775 clparents = changelog.parentrevs
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 skip = {changelog.rev(n) for n in state[b'skip']}
Matt Mackall
bisect: make bisect a built-in command
r5775
Matt Mackall
bisect: handle search for bad to good transitions...
r5776 def buildancestors(bad, good):
badrev = min([changelog.rev(n) for n in bad])
David Soria Parra
hbisect: use a defaultdict to avoid large allocations for a large changelogs...
r35128 ancestors = collections.defaultdict(lambda: None)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for rev in repo.revs(b"descendants(%ln) - ancestors(%ln)", good, good):
Alexander Krauss
hbisect: do not assume that min(good) is an ancestor of min(bad)...
r14895 ancestors[rev] = []
Matt Mackall
bisect: handle search for bad to good transitions...
r5776 if ancestors[badrev] is None:
Matt Mackall
bisect: improve tests...
r5777 return badrev, None
Matt Mackall
bisect: handle search for bad to good transitions...
r5776 return badrev, ancestors
Martin Geisler
hbisect: use real Booleans instead of 0/1
r14215 good = False
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 badrev, ancestors = buildancestors(state[b'bad'], state[b'good'])
Augie Fackler
formatting: blacken the codebase...
r43346 if not ancestors: # looking for bad to good transition?
Martin Geisler
hbisect: use real Booleans instead of 0/1
r14215 good = True
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 badrev, ancestors = buildancestors(state[b'good'], state[b'bad'])
Matt Mackall
bisect: improve tests...
r5777 bad = changelog.node(badrev)
Augie Fackler
formatting: blacken the codebase...
r43346 if not ancestors: # now we're confused
if (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 len(state[b'bad']) == 1
and len(state[b'good']) == 1
and state[b'bad'] != state[b'good']
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b"starting revisions are not directly related"))
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b"inconsistent state, %d:%s is good and bad")
Augie Fackler
formatting: blacken the codebase...
r43346 % (badrev, short(bad))
)
Matt Mackall
bisect: make bisect a built-in command
r5775
# build children dict
children = {}
Martin von Zweigbergk
util: drop alias for collections.deque...
r25113 visit = collections.deque([badrev])
Matt Mackall
bisect: make bisect a built-in command
r5775 candidates = []
while visit:
Bryan O'Sullivan
cleanup: use the deque type where appropriate...
r16803 rev = visit.popleft()
Matt Mackall
bisect: make bisect a built-in command
r5775 if ancestors[rev] == []:
candidates.append(rev)
for prev in clparents(rev):
if prev != -1:
if prev in children:
children[prev].append(rev)
else:
children[prev] = [rev]
visit.append(prev)
candidates.sort()
# have we narrowed it down to one entry?
Bernhard Leiner
Add support for multiple possible bisect results (issue1228, issue1182)...
r6858 # or have all other possible candidates besides 'bad' have been skipped?
Matt Mackall
bisect: make bisect a built-in command
r5775 tot = len(candidates)
Bernhard Leiner
Add support for multiple possible bisect results (issue1228, issue1182)...
r6858 unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
if tot == 1 or not unskipped:
Augie Fackler
hbisect: avoid shadowing a variable in a list comprehension
r30389 return ([changelog.node(c) for c in candidates], 0, good)
Benoit Boissinot
bisect: use integer division
r7902 perfect = tot // 2
Matt Mackall
bisect: make bisect a built-in command
r5775
# find the best node to test
best_rev = None
best_len = -1
Benoit Boissinot
bisect: use set instead of dict
r8463 poison = set()
Matt Mackall
bisect: make bisect a built-in command
r5775 for rev in candidates:
if rev in poison:
Martin Geisler
hbisect: use set.update for bulk updates
r8482 # poison children
poison.update(children.get(rev, []))
Matt Mackall
bisect: make bisect a built-in command
r5775 continue
a = ancestors[rev] or [rev]
ancestors[rev] = None
Augie Fackler
formatting: blacken the codebase...
r43346 x = len(a) # number of ancestors
y = tot - x # number of non-ancestors
value = min(x, y) # how good is this test?
Matt Mackall
bisect: make bisect a built-in command
r5775 if value > best_len and rev not in skip:
best_len = value
best_rev = rev
Augie Fackler
formatting: blacken the codebase...
r43346 if value == perfect: # found a perfect candidate? quit early
Matt Mackall
bisect: make bisect a built-in command
r5775 break
Augie Fackler
formatting: blacken the codebase...
r43346 if y < perfect and rev not in skip: # all downhill from here?
Martin Geisler
hbisect: use set.update for bulk updates
r8482 # poison children
poison.update(children.get(rev, []))
Matt Mackall
bisect: make bisect a built-in command
r5775 continue
for c in children.get(rev, []):
if ancestors[c]:
Martin Geisler
replace set-like dictionaries with real sets...
r8152 ancestors[c] = list(set(ancestors[c] + a))
Matt Mackall
bisect: make bisect a built-in command
r5775 else:
ancestors[c] = a + [c]
assert best_rev is not None
best_node = changelog.node(best_rev)
Bernhard Leiner
Add support for multiple possible bisect results (issue1228, issue1182)...
r6858 return ([best_node], tot, good)
Alexander Solovyov
bisect: ability to check revision with command
r7227
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
bisect: move the 'extendrange' to the 'hbisect' module...
r30066 def extendrange(repo, state, nodes, good):
# bisect is incomplete when it ends on a merge node and
# one of the parent was not checked.
parents = repo[nodes[0]].parents()
if len(parents) > 1:
if good:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 side = state[b'bad']
Pierre-Yves David
bisect: move the 'extendrange' to the 'hbisect' module...
r30066 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 side = state[b'good']
Augie Fackler
cleanup: run pyupgrade on our source tree to clean up varying things...
r44937 num = len({i.node() for i in parents} & set(side))
Pierre-Yves David
bisect: move the 'extendrange' to the 'hbisect' module...
r30066 if num == 1:
return parents[0].ancestor(parents[1])
return None
Alexander Solovyov
bisect: ability to check revision with command
r7227
Augie Fackler
formatting: blacken the codebase...
r43346
Alexander Solovyov
bisect: ability to check revision with command
r7227 def load_state(repo):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 state = {b'current': [], b'good': [], b'bad': [], b'skip': []}
for l in repo.vfs.tryreadlines(b"bisect.state"):
Bryan O'Sullivan
hbisect: use tryreadlines to load state...
r27525 kind, node = l[:-1].split()
timeless
bisect: do not crash with rewritten commits
r42501 node = repo.unfiltered().lookup(node)
Bryan O'Sullivan
hbisect: use tryreadlines to load state...
r27525 if kind not in state:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b"unknown bisect kind %s") % kind)
Bryan O'Sullivan
hbisect: use tryreadlines to load state...
r27525 state[kind].append(node)
Alexander Solovyov
bisect: ability to check revision with command
r7227 return state
def save_state(repo, state):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 f = repo.vfs(b"bisect.state", b"w", atomictemp=True)
Bryan O'Sullivan
with: use context manager in bisect save_state
r27853 with repo.wlock():
Mads Kiilerich
bisect: store state sorted
r18358 for kind in sorted(state):
Alexander Solovyov
bisect: ability to check revision with command
r7227 for node in state[kind]:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 f.write(b"%s %s\n" % (kind, hex(node)))
Greg Ward
atomictempfile: make close() consistent with other file-like objects....
r15057 f.close()
Alexander Solovyov
bisect: ability to check revision with command
r7227
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
bisect: extract the 'reset' logic into its own function...
r30065 def resetstate(repo):
"""remove any bisect state from the repository"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if repo.vfs.exists(b"bisect.state"):
repo.vfs.unlink(b"bisect.state")
Pierre-Yves David
bisect: extract the 'reset' logic into its own function...
r30065
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
bisect: move check_state into the bisect module...
r30126 def checkstate(state):
"""check we have both 'good' and 'bad' to define a range
Raise Abort exception otherwise."""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if state[b'good'] and state[b'bad']:
Pierre-Yves David
bisect: move check_state into the bisect module...
r30126 return True
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not state[b'good']:
raise error.Abort(_(b'cannot bisect (no known good revisions)'))
Pierre-Yves David
bisect: move check_state into the bisect module...
r30126 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'cannot bisect (no known bad revisions)'))
Pierre-Yves David
bisect: move check_state into the bisect module...
r30126
Augie Fackler
formatting: blacken the codebase...
r43346
Denis Laxalde
bisect: replace try:/finally: by a "restore_state" context manager...
r44064 @contextlib.contextmanager
def restore_state(repo, state, node):
try:
yield
finally:
state[b'current'] = [node]
save_state(repo, state)
"Yann E. MORIN"
revset.bisect: move bisect() code to hbisect.py...
r15135 def get(repo, status):
"""
Return a list of revision(s) that match the given status:
"Yann E. MORIN"
hbisect: add two new revset descriptions: 'goods' and 'bads'...
r15153 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
Mads Kiilerich
fix trivial spelling errors
r17424 - ``goods``, ``bads`` : csets topologically good/bad
"Yann E. MORIN"
hbisect: add two new revset descriptions: 'goods' and 'bads'...
r15153 - ``range`` : csets taking part in the bisection
- ``pruned`` : csets that are goods, bads or skipped
"Yann E. MORIN"
revset.bisect: add new 'untested' set to the bisect keyword...
r15138 - ``untested`` : csets whose fate is yet unknown
"Yann E. MORIN"
revset.bisect: add 'ignored' set to the bisect keyword...
r15147 - ``ignored`` : csets ignored due to DAG topology
Bryan O'Sullivan
bisect: track the current changeset (issue3382)...
r16647 - ``current`` : the cset currently being bisected
"Yann E. MORIN"
revset.bisect: move bisect() code to hbisect.py...
r15135 """
state = load_state(repo)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if status in (b'good', b'bad', b'skip', b'current'):
timeless
bisect: do not crash with rewritten commits
r42501 return map(repo.unfiltered().changelog.rev, state[status])
"Yann E. MORIN"
revset.bisect: move bisect() code to hbisect.py...
r15135 else:
timeless@mozdev.org
spelling: following
r17493 # In the following sets, we do *not* call 'bisect()' with more
timeless@mozdev.org
spelling: recursion
r17509 # than one level of recursion, because that can be very, very
"Yann E. MORIN"
hbisect.get: use simpler code with repo.set(), fix 'pruned' set...
r15146 # time consuming. Instead, we always develop the expression as
# much as possible.
# 'range' is all csets that make the bisection:
# - have a good ancestor and a bad descendant, or conversely
# that's because the bisection can go either way
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 range = b'( bisect(bad)::bisect(good) | bisect(good)::bisect(bad) )'
"Yann E. MORIN"
revset.bisect: add new 'range' set to the bisect keyword...
r15136
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _t = repo.revs(b'bisect(good)::bisect(bad)')
"Yann E. MORIN"
hbisect: add two new revset descriptions: 'goods' and 'bads'...
r15153 # The sets of topologically good or bad csets
if len(_t) == 0:
# Goods are topologically after bads
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 goods = b'bisect(good)::' # Pruned good csets
bads = b'::bisect(bad)' # Pruned bad csets
"Yann E. MORIN"
hbisect: add two new revset descriptions: 'goods' and 'bads'...
r15153 else:
# Goods are topologically before bads
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 goods = b'::bisect(good)' # Pruned good csets
bads = b'bisect(bad)::' # Pruned bad csets
"Yann E. MORIN"
hbisect: add two new revset descriptions: 'goods' and 'bads'...
r15153
# 'pruned' is all csets whose fate is already known: good, bad, skip
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 skips = b'bisect(skip)' # Pruned skipped csets
pruned = b'( (%s) | (%s) | (%s) )' % (goods, bads, skips)
"Yann E. MORIN"
revset.bisect: add new 'range' set to the bisect keyword...
r15136
"Yann E. MORIN"
hbisect.get: use simpler code with repo.set(), fix 'pruned' set...
r15146 # 'untested' is all cset that are- in 'range', but not in 'pruned'
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 untested = b'( (%s) - (%s) )' % (range, pruned)
"Yann E. MORIN"
revset.bisect: add new 'range' set to the bisect keyword...
r15136
"Yann E. MORIN"
revset.bisect: add 'ignored' set to the bisect keyword...
r15147 # 'ignored' is all csets that were not used during the bisection
# due to DAG topology, but may however have had an impact.
Mads Kiilerich
fix trivial spelling errors
r17424 # E.g., a branch merged between bads and goods, but whose branch-
"Yann E. MORIN"
revset.bisect: add 'ignored' set to the bisect keyword...
r15147 # point is out-side of the range.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 iba = b'::bisect(bad) - ::bisect(good)' # Ignored bads' ancestors
iga = b'::bisect(good) - ::bisect(bad)' # Ignored goods' ancestors
ignored = b'( ( (%s) | (%s) ) - (%s) )' % (iba, iga, range)
"Yann E. MORIN"
revset.bisect: add 'ignored' set to the bisect keyword...
r15147
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if status == b'range':
Matt Mackall
localrepo: convert various repo.set() users to repo.revs()
r15404 return repo.revs(range)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif status == b'pruned':
Matt Mackall
localrepo: convert various repo.set() users to repo.revs()
r15404 return repo.revs(pruned)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif status == b'untested':
Matt Mackall
localrepo: convert various repo.set() users to repo.revs()
r15404 return repo.revs(untested)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif status == b'ignored':
Matt Mackall
localrepo: convert various repo.set() users to repo.revs()
r15404 return repo.revs(ignored)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif status == b"goods":
Matt Mackall
localrepo: convert various repo.set() users to repo.revs()
r15404 return repo.revs(goods)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif status == b"bads":
Matt Mackall
localrepo: convert various repo.set() users to repo.revs()
r15404 return repo.revs(bads)
"Yann E. MORIN"
revset.bisect: add new 'range' set to the bisect keyword...
r15136 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'invalid bisect state'))
"Yann E. MORIN"
hbisect: add functions to return a label for a cset bisection status...
r15154
Augie Fackler
formatting: blacken the codebase...
r43346
"Yann E. MORIN"
bisect: remove superfluous parameter in label()...
r15406 def label(repo, node):
"Yann E. MORIN"
hbisect: add functions to return a label for a cset bisection status...
r15154 rev = repo.changelog.rev(node)
# Try explicit sets
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if rev in get(repo, b'good'):
Wagner Bruna
bisect: add i18n contexts
r15308 # i18n: bisect changeset status
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return _(b'good')
if rev in get(repo, b'bad'):
Wagner Bruna
bisect: add i18n contexts
r15308 # i18n: bisect changeset status
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return _(b'bad')
if rev in get(repo, b'skip'):
Wagner Bruna
bisect: add i18n contexts
r15308 # i18n: bisect changeset status
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return _(b'skipped')
if rev in get(repo, b'untested') or rev in get(repo, b'current'):
Wagner Bruna
bisect: add i18n contexts
r15308 # i18n: bisect changeset status
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return _(b'untested')
if rev in get(repo, b'ignored'):
Wagner Bruna
bisect: add i18n contexts
r15308 # i18n: bisect changeset status
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return _(b'ignored')
"Yann E. MORIN"
hbisect: add functions to return a label for a cset bisection status...
r15154
# Try implicit sets
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if rev in get(repo, b'goods'):
Wagner Bruna
bisect: add i18n contexts
r15308 # i18n: bisect changeset status
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return _(b'good (implicit)')
if rev in get(repo, b'bads'):
Wagner Bruna
bisect: add i18n contexts
r15308 # i18n: bisect changeset status
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return _(b'bad (implicit)')
"Yann E. MORIN"
hbisect: add functions to return a label for a cset bisection status...
r15154
return None
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
bisect: move 'printresult' in the 'hbisect' module...
r30067 def printresult(ui, repo, state, displayer, nodes, good):
timeless
bisect: do not crash with rewritten commits
r42501 repo = repo.unfiltered()
Pierre-Yves David
bisect: move 'printresult' in the 'hbisect' module...
r30067 if len(nodes) == 1:
# narrowed it down to a single revision
if good:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write(_(b"The first good revision is:\n"))
Pierre-Yves David
bisect: move 'printresult' in the 'hbisect' module...
r30067 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write(_(b"The first bad revision is:\n"))
Pierre-Yves David
bisect: move 'printresult' in the 'hbisect' module...
r30067 displayer.show(repo[nodes[0]])
extendnode = extendrange(repo, state, nodes, good)
if extendnode is not None:
Augie Fackler
formatting: blacken the codebase...
r43346 ui.write(
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'Not all ancestors of this changeset have been'
b' checked.\nUse bisect --extend to continue the '
b'bisection from\nthe common ancestor, %s.\n'
Augie Fackler
formatting: blacken the codebase...
r43346 )
% extendnode
)
Pierre-Yves David
bisect: move 'printresult' in the 'hbisect' module...
r30067 else:
# multiple possible revisions
if good:
Augie Fackler
formatting: blacken the codebase...
r43346 ui.write(
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"Due to skipped revisions, the first "
b"good revision could be any of:\n"
Augie Fackler
formatting: blacken the codebase...
r43346 )
)
Pierre-Yves David
bisect: move 'printresult' in the 'hbisect' module...
r30067 else:
Augie Fackler
formatting: blacken the codebase...
r43346 ui.write(
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"Due to skipped revisions, the first "
b"bad revision could be any of:\n"
Augie Fackler
formatting: blacken the codebase...
r43346 )
)
Pierre-Yves David
bisect: move 'printresult' in the 'hbisect' module...
r30067 for n in nodes:
displayer.show(repo[n])
displayer.close()