mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-04-28 01:00:52 -07:00
155 lines
4.7 KiB
Python
155 lines
4.7 KiB
Python
#!/usr/bin/env python
|
|
#
|
|
#
|
|
# Ravenbrook
|
|
# <http://www.ravenbrook.com/>
|
|
#
|
|
# P4-BISECT -- FIND CHANGE THAT INTRODUCED A BUG
|
|
#
|
|
# Gareth Rees, Ravenbrook Limited, 2014-04-14
|
|
#
|
|
#
|
|
# 1. INTRODUCTION
|
|
#
|
|
# This script automates (or partly automates) the process of finding,
|
|
# by binary search, the change that introduced a bug.
|
|
#
|
|
# The interface is modelled closely on git-bisect(1).
|
|
|
|
import argparse
|
|
from functools import partial
|
|
import json
|
|
from os import unlink
|
|
import p4
|
|
import subprocess
|
|
import sys
|
|
|
|
BISECT_FILE = '.p4-bisect'
|
|
|
|
class State(object):
|
|
def __init__(self, **d):
|
|
self.filespec = d['filespec']
|
|
self.changes = d['changes']
|
|
if 'current' in d:
|
|
self.current = d['current']
|
|
|
|
@classmethod
|
|
def load(cls):
|
|
with open(BISECT_FILE, 'r') as f:
|
|
return cls(**json.load(f))
|
|
|
|
def save(self):
|
|
with open(BISECT_FILE, 'w') as f:
|
|
json.dump(vars(self), f)
|
|
|
|
def update(self):
|
|
n = len(self.changes)
|
|
if n == 0:
|
|
print("no changes remaining.".format(**vars(self)))
|
|
elif n == 1:
|
|
print("{} change remaining: {}.".format(n, self.changes[0]))
|
|
elif n == 2:
|
|
print("{} changes remaining: [{}, {}]."
|
|
.format(n, self.changes[0], self.changes[-1]))
|
|
else:
|
|
print("{} changes remaining: [{}, ..., {}]."
|
|
.format(n, self.changes[0], self.changes[-1]))
|
|
if n > 0:
|
|
self.current = self.changes[n // 2]
|
|
print("Syncing to changelevel {current}.".format(**vars(self)))
|
|
p4.do('update', '{filespec}@{current}'.format(**vars(self)))
|
|
self.save()
|
|
|
|
def help(parser, args):
|
|
parser.print_help()
|
|
|
|
def start(args):
|
|
changes = sorted(int(c['change']) for c in p4.run('changes', args.filespec))
|
|
if not changes:
|
|
parser.error("No changes for {filespec}".format(**vars(args)))
|
|
if args.first is None:
|
|
args.first = changes[0]
|
|
if args.last is None:
|
|
args.last = changes[-1]
|
|
state = State(filespec=args.filespec,
|
|
changes=[c for c in changes if args.first <= c <= args.last])
|
|
state.update()
|
|
|
|
def good(args):
|
|
state = State.load()
|
|
print("Change {current} good.".format(**vars(state)))
|
|
state.changes = [c for c in state.changes if c > state.current]
|
|
state.update()
|
|
|
|
def bad(args):
|
|
state = State.load()
|
|
print("Change {current} bad.".format(**vars(state)))
|
|
state.changes = [c for c in state.changes if c < state.current]
|
|
state.update()
|
|
|
|
def skip(args):
|
|
state = State.load()
|
|
print("Skipping change {current}.".format(**vars(state)))
|
|
state.changes.remove(state.current)
|
|
state.update()
|
|
|
|
def reset(args):
|
|
state = State.load()
|
|
p4.do('update', state.filespec)
|
|
unlink(BISECT_FILE)
|
|
|
|
def run(args):
|
|
while True:
|
|
state = State.load()
|
|
if not state.changes:
|
|
break
|
|
result = subprocess.call([args.cmd] + args.args)
|
|
if result == 0:
|
|
good(None)
|
|
elif result == 125:
|
|
skip(None)
|
|
elif 0 < result < 128:
|
|
bad(None)
|
|
else:
|
|
exit(result)
|
|
|
|
def main(argv):
|
|
parser = argparse.ArgumentParser(prog='p4-bisect')
|
|
subparsers = parser.add_subparsers()
|
|
a = subparsers.add_parser
|
|
|
|
help_parser = a('help', help='show this help message')
|
|
help_parser.set_defaults(func=partial(help, help_parser))
|
|
|
|
start_parser = a('start', help='start a p4-bisect session')
|
|
aa = start_parser.add_argument
|
|
start_parser.add_argument('-f', '--filespec', default='...',
|
|
help='filespec to search')
|
|
start_parser.add_argument('first', nargs='?', type=int,
|
|
help='earliest changelevel to examine')
|
|
start_parser.add_argument('last', nargs='?', type=int,
|
|
help='latest changelevel to examine')
|
|
start_parser.set_defaults(func=start)
|
|
|
|
good_parser = a('good', help='declare current revision good')
|
|
good_parser.set_defaults(func=good)
|
|
|
|
bad_parser = a('bad', help='declare current revision bad')
|
|
bad_parser.set_defaults(func=bad)
|
|
|
|
reset_parser = a('reset', help='finish p4-bisect session')
|
|
reset_parser.set_defaults(func=reset)
|
|
|
|
run_parser = a('run', help='run p4-bisect session automatically')
|
|
run_parser.add_argument('cmd',
|
|
help='command that determines if current '
|
|
'changelevel is good or bad')
|
|
run_parser.add_argument('args', nargs=argparse.REMAINDER,
|
|
help='arguments to pass to cmd')
|
|
run_parser.set_defaults(func=run)
|
|
|
|
args = parser.parse_args(argv[1:])
|
|
args.func(args)
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv)
|