13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""bisect command implementations."""
19
from __future__ import absolute_import
20
from .controldir import ControlDir
21
from . import revision as _mod_revision
22
from .commands import Command
23
from .errors import CommandError
24
from .option import Option
25
from .trace import note
23
from ...controldir import ControlDir
24
from ... import revision as _mod_revision
25
from ...commands import Command
26
from ...errors import BzrCommandError
27
from ...option import Option
28
from ...trace import note
27
BISECT_INFO_PATH = "bisect"
28
BISECT_REV_PATH = "bisect_revid"
30
bisect_info_path = ".bzr/bisect"
31
bisect_rev_path = ".bzr/bisect_revid"
31
34
class BisectCurrent(object):
32
35
"""Bisect class for managing the current revision."""
34
def __init__(self, controldir, filename=BISECT_REV_PATH):
37
def __init__(self, filename = bisect_rev_path):
35
38
self._filename = filename
36
self._controldir = controldir
37
self._branch = self._controldir.open_branch()
38
if self._controldir.control_transport.has(filename):
39
self._revid = self._controldir.control_transport.get_bytes(
39
self._bzrdir = ControlDir.open_containing(".")[0]
40
self._bzrbranch = self._bzrdir.open_branch()
41
if os.path.exists(filename):
42
revid_file = open(filename)
43
self._revid = revid_file.read().strip()
42
self._revid = self._branch.last_revision()
46
self._revid = self._bzrbranch.last_revision()
45
49
"""Save the current revision."""
46
self._controldir.control_transport.put_bytes(
47
self._filename, self._revid + b"\n")
51
revid_file = open(self._filename, "w")
52
revid_file.write(self._revid + "\n")
49
55
def get_current_revid(self):
50
56
"""Return the current revision id."""
53
59
def get_current_revno(self):
54
60
"""Return the current revision number as a tuple."""
55
return self._branch.revision_id_to_dotted_revno(self._revid)
61
revdict = self._bzrbranch.get_revision_id_to_revno_map()
62
return revdict[self.get_current_revid()]
57
64
def get_parent_revids(self):
58
65
"""Return the IDs of the current revision's predecessors."""
59
repo = self._branch.repository
60
with repo.lock_read():
61
retval = repo.get_parent_map([self._revid]).get(self._revid, None)
66
repo = self._bzrbranch.repository
68
retval = repo.get_parent_map([self._revid]).get(self._revid, None)
64
72
def is_merge_point(self):
65
73
"""Is the current revision a merge point?"""
66
74
return len(self.get_parent_revids()) > 1
68
def show_rev_log(self, outf):
76
def show_rev_log(self, out = sys.stdout):
69
77
"""Write the current revision's log entry to a file."""
70
rev = self._branch.repository.get_revision(self._revid)
78
rev = self._bzrbranch.repository.get_revision(self._revid)
71
79
revno = ".".join([str(x) for x in self.get_current_revno()])
72
outf.write("On revision %s (%s):\n%s\n" % (revno, rev.revision_id,
80
out.write("On revision %s (%s):\n%s\n" % (revno, rev.revision_id,
75
83
def switch(self, revid):
76
84
"""Switch the current revision to the given revid."""
77
working = self._controldir.open_workingtree()
85
working = self._bzrdir.open_workingtree()
78
86
if isinstance(revid, int):
79
revid = self._branch.get_rev_id(revid)
87
revid = self._bzrbranch.get_rev_id(revid)
80
88
elif isinstance(revid, list):
81
89
revid = revid[0].in_history(working.branch).rev_id
82
90
working.revert(None, working.branch.repository.revision_tree(revid),
88
96
"""Revert bisection, setting the working tree to normal."""
89
working = self._controldir.open_workingtree()
97
working = self._bzrdir.open_workingtree()
90
98
last_rev = working.branch.last_revision()
91
99
rev_tree = working.branch.repository.revision_tree(last_rev)
92
100
working.revert(None, rev_tree, False)
93
if self._controldir.control_transport.has(BISECT_REV_PATH):
94
self._controldir.control_transport.delete(BISECT_REV_PATH)
101
if os.path.exists(bisect_rev_path):
102
os.unlink(bisect_rev_path)
97
105
class BisectLog(object):
98
106
"""Bisect log file handler."""
100
def __init__(self, controldir, filename=BISECT_INFO_PATH):
108
def __init__(self, filename = bisect_info_path):
102
self._current = BisectCurrent(controldir)
103
self._controldir = controldir
110
self._current = BisectCurrent()
105
112
self._high_revid = None
106
113
self._low_revid = None
107
114
self._middle_revid = None
111
118
def _open_for_read(self):
112
119
"""Open log file for reading."""
113
120
if self._filename:
114
return self._controldir.control_transport.get(self._filename)
121
return open(self._filename)
118
def _load_tree(self):
125
def _open_for_write(self):
126
"""Open log file for writing."""
128
return open(self._filename, "w")
132
def _load_bzr_tree(self):
119
133
"""Load bzr information."""
121
self._branch = self._controldir.open_branch()
135
self._bzrdir = ControlDir.open_containing('.')[0]
136
self._bzrbranch = self._bzrdir.open_branch()
123
def _find_range_and_middle(self, branch_last_rev=None):
138
def _find_range_and_middle(self, branch_last_rev = None):
124
139
"""Find the current revision range, and the midpoint."""
140
self._load_bzr_tree()
126
141
self._middle_revid = None
128
143
if not branch_last_rev:
129
last_revid = self._branch.last_revision()
144
last_revid = self._bzrbranch.last_revision()
131
146
last_revid = branch_last_rev
133
repo = self._branch.repository
134
with repo.lock_read():
148
repo = self._bzrbranch.repository
135
151
graph = repo.get_graph()
136
rev_sequence = graph.iter_lefthand_ancestry(
137
last_revid, (_mod_revision.NULL_REVISION,))
152
rev_sequence = graph.iter_lefthand_ancestry(last_revid,
153
(_mod_revision.NULL_REVISION,))
138
154
high_revid = None
140
156
between_revs = []
198
216
"""Load the bisection log."""
200
if self._controldir.control_transport.has(self._filename):
218
if os.path.exists(self._filename):
201
219
revlog = self._open_for_read()
202
220
for line in revlog:
203
221
(revid, status) = line.split()
204
self._items.append((revid, status.decode('ascii')))
222
self._items.append((revid, status))
207
225
"""Save the bisection log."""
209
(b"%s %s\n" % (revid, status.encode('ascii')))
210
for (revid, status) in self._items)
212
self._controldir.control_transport.put_bytes(
213
self._filename, contents)
215
sys.stdout.write(contents)
226
revlog = self._open_for_write()
227
for (revid, status) in self._items:
228
revlog.write("%s %s\n" % (revid, status))
217
230
def is_done(self):
218
231
"""Report whether we've found the right revision."""
272
288
of which changes the state of the bisection. The
276
292
Start a bisect, possibly clearing out a previous bisect.
278
brz bisect yes [-r rev]
294
bzr bisect yes [-r rev]
279
295
The specified revision (or the current revision, if not given)
280
296
has the characteristic we're looking for,
282
brz bisect no [-r rev]
298
bzr bisect no [-r rev]
283
299
The specified revision (or the current revision, if not given)
284
300
does not have the characteristic we're looking for,
286
brz bisect move -r rev
302
bzr bisect move -r rev
287
303
Switch to a different revision manually. Use if the bisect
288
304
algorithm chooses a revision that is not suitable. Try to
289
305
move as little as possible.
292
308
Clear out a bisection in progress.
294
brz bisect log [-o file]
310
bzr bisect log [-o file]
295
311
Output a log of the current bisection to standard output, or
296
312
to the specified file.
298
brz bisect replay <logfile>
314
bzr bisect replay <logfile>
299
315
Replay a previously-saved bisect log, forgetting any bisection
300
316
that might be in progress.
302
brz bisect run <script>
318
bzr bisect run <script>
303
319
Bisect automatically using <script> to determine 'yes' or 'no'.
304
320
<script> should exit with:
310
326
takes_args = ['subcommand', 'args*']
311
327
takes_options = [Option('output', short_name='o',
312
help='Write log to this file.', type=str),
313
'revision', 'directory']
328
help='Write log to this file.', type=unicode),
315
def _check(self, controldir):
316
332
"""Check preconditions for most operations to work."""
317
if not controldir.control_transport.has(BISECT_INFO_PATH):
318
raise CommandError("No bisection in progress.")
333
if not os.path.exists(bisect_info_path):
334
raise BzrCommandError("No bisection in progress.")
320
def _set_state(self, controldir, revspec, state):
336
def _set_state(self, revspec, state):
321
337
"""Set the state of the given revspec and bisecting.
323
339
Returns boolean indicating if bisection is done."""
324
bisect_log = BisectLog(controldir)
340
bisect_log = BisectLog()
325
341
if bisect_log.is_done():
326
342
note("No further bisection is possible.\n")
327
bisect_log._current.show_rev_log(outf=self.outf)
343
bisect_log._current.show_rev_log(self.outf)
345
360
elif subcommand in ('replay', ) and args_list and len(args_list) == 1:
346
361
log_fn = args_list[0]
347
362
elif subcommand in ('move', ) and not revision:
363
raise BzrCommandError(
349
364
"The 'bisect move' command requires a revision.")
350
365
elif subcommand in ('run', ):
351
366
run_script = args_list[0]
352
367
elif args_list or revision:
368
raise BzrCommandError(
354
369
"Improper arguments to bisect " + subcommand)
356
controldir, _ = ControlDir.open_containing(directory)
359
373
if subcommand == "start":
360
self.start(controldir)
361
375
elif subcommand == "yes":
362
self.yes(controldir, revision)
363
377
elif subcommand == "no":
364
self.no(controldir, revision)
365
379
elif subcommand == "move":
366
self.move(controldir, revision)
367
381
elif subcommand == "reset":
368
self.reset(controldir)
369
383
elif subcommand == "log":
370
self.log(controldir, output)
371
385
elif subcommand == "replay":
372
self.replay(controldir, log_fn)
373
387
elif subcommand == "run":
374
self.run_bisect(controldir, run_script)
388
self.run_bisect(run_script)
390
raise BzrCommandError(
377
391
"Unknown bisect command: " + subcommand)
379
def reset(self, controldir):
380
394
"""Reset the bisect state to no state."""
381
self._check(controldir)
382
BisectCurrent(controldir).reset()
383
controldir.control_transport.delete(BISECT_INFO_PATH)
396
BisectCurrent().reset()
397
os.unlink(bisect_info_path)
385
def start(self, controldir):
386
400
"""Reset the bisect state, then prepare for a new bisection."""
387
if controldir.control_transport.has(BISECT_INFO_PATH):
388
BisectCurrent(controldir).reset()
389
controldir.control_transport.delete(BISECT_INFO_PATH)
401
if os.path.exists(bisect_info_path):
402
BisectCurrent().reset()
403
os.unlink(bisect_info_path)
391
bisect_log = BisectLog(controldir)
405
bisect_log = BisectLog()
392
406
bisect_log.set_current("start")
393
407
bisect_log.save()
395
def yes(self, controldir, revspec):
409
def yes(self, revspec):
396
410
"""Mark that a given revision has the state we're looking for."""
397
self._set_state(controldir, revspec, "yes")
399
def no(self, controldir, revspec):
400
"""Mark a given revision as wrong."""
401
self._set_state(controldir, revspec, "no")
403
def move(self, controldir, revspec):
411
self._set_state(revspec, "yes")
413
def no(self, revspec):
414
"""Mark that a given revision does not have the state we're looking for."""
415
self._set_state(revspec, "no")
417
def move(self, revspec):
404
418
"""Move to a different revision manually."""
405
current = BisectCurrent(controldir)
419
current = BisectCurrent()
406
420
current.switch(revspec)
407
current.show_rev_log(outf=self.outf)
421
current.show_rev_log(out=self.outf)
409
def log(self, controldir, filename):
423
def log(self, filename):
410
424
"""Write the current bisect log to a file."""
411
self._check(controldir)
412
bisect_log = BisectLog(controldir)
426
bisect_log = BisectLog()
413
427
bisect_log.change_file_name(filename)
414
428
bisect_log.save()
416
def replay(self, controldir, filename):
430
def replay(self, filename):
417
431
"""Apply the given log file to a clean state, so the state is
418
432
exactly as it was when the log was saved."""
419
if controldir.control_transport.has(BISECT_INFO_PATH):
420
BisectCurrent(controldir).reset()
421
controldir.control_transport.delete(BISECT_INFO_PATH)
422
bisect_log = BisectLog(controldir, filename)
423
bisect_log.change_file_name(BISECT_INFO_PATH)
433
if os.path.exists(bisect_info_path):
434
BisectCurrent().reset()
435
os.unlink(bisect_info_path)
436
bisect_log = BisectLog(filename)
437
bisect_log.change_file_name(bisect_info_path)
424
438
bisect_log.save()
426
440
bisect_log.bisect(self.outf)
428
def run_bisect(self, controldir, script):
442
def run_bisect(self, script):
429
443
import subprocess
430
444
note("Starting bisect.")
431
self.start(controldir)
434
448
process = subprocess.Popen(script, shell=True)
436
450
retcode = process.returncode
438
done = self._set_state(controldir, None, 'yes')
452
done = self._set_state(None, 'yes')
439
453
elif retcode == 125:
442
done = self._set_state(controldir, None, 'no')
456
done = self._set_state(None, 'no')
445
459
except RuntimeError: