/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/plugins/bisect/cmds.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-08 23:30:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170608233031-3qavls2o7a1pqllj
Update imports.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
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
16
16
 
17
17
"""bisect command implementations."""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
import sys
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
 
22
import os
 
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
26
29
 
27
 
BISECT_INFO_PATH = "bisect"
28
 
BISECT_REV_PATH = "bisect_revid"
 
30
bisect_info_path = ".bzr/bisect"
 
31
bisect_rev_path = ".bzr/bisect_revid"
29
32
 
30
33
 
31
34
class BisectCurrent(object):
32
35
    """Bisect class for managing the current revision."""
33
36
 
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(
40
 
                filename).strip()
 
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()
 
44
            revid_file.close()
41
45
        else:
42
 
            self._revid = self._branch.last_revision()
 
46
            self._revid = self._bzrbranch.last_revision()
43
47
 
44
48
    def _save(self):
45
49
        """Save the current revision."""
46
 
        self._controldir.control_transport.put_bytes(
47
 
            self._filename, self._revid + b"\n")
 
50
 
 
51
        revid_file = open(self._filename, "w")
 
52
        revid_file.write(self._revid + "\n")
 
53
        revid_file.close()
48
54
 
49
55
    def get_current_revid(self):
50
56
        """Return the current revision id."""
52
58
 
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()]
56
63
 
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
 
67
        repo.lock_read()
 
68
        retval = repo.get_parent_map([self._revid]).get(self._revid, None)
 
69
        repo.unlock()
62
70
        return retval
63
71
 
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
67
75
 
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,
73
 
                                                   rev.message))
 
80
        out.write("On revision %s (%s):\n%s\n" % (revno, rev.revision_id,
 
81
                                                  rev.message))
74
82
 
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),
86
94
 
87
95
    def reset(self):
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)
95
103
 
96
104
 
97
105
class BisectLog(object):
98
106
    """Bisect log file handler."""
99
107
 
100
 
    def __init__(self, controldir, filename=BISECT_INFO_PATH):
 
108
    def __init__(self, filename = bisect_info_path):
101
109
        self._items = []
102
 
        self._current = BisectCurrent(controldir)
103
 
        self._controldir = controldir
104
 
        self._branch = None
 
110
        self._current = BisectCurrent()
 
111
        self._bzrdir = None
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)
115
122
        else:
116
123
            return sys.stdin
117
124
 
118
 
    def _load_tree(self):
 
125
    def _open_for_write(self):
 
126
        """Open log file for writing."""
 
127
        if self._filename:
 
128
            return open(self._filename, "w")
 
129
        else:
 
130
            return sys.stdout
 
131
 
 
132
    def _load_bzr_tree(self):
119
133
        """Load bzr information."""
120
 
        if not self._branch:
121
 
            self._branch = self._controldir.open_branch()
 
134
        if not self._bzrdir:
 
135
            self._bzrdir = ControlDir.open_containing('.')[0]
 
136
            self._bzrbranch = self._bzrdir.open_branch()
122
137
 
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."""
125
 
        self._load_tree()
 
140
        self._load_bzr_tree()
126
141
        self._middle_revid = None
127
142
 
128
143
        if not branch_last_rev:
129
 
            last_revid = self._branch.last_revision()
 
144
            last_revid = self._bzrbranch.last_revision()
130
145
        else:
131
146
            last_revid = branch_last_rev
132
147
 
133
 
        repo = self._branch.repository
134
 
        with repo.lock_read():
 
148
        repo = self._bzrbranch.repository
 
149
        repo.lock_read()
 
150
        try:
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
139
155
            low_revid = None
140
156
            between_revs = []
157
173
            if not high_revid:
158
174
                high_revid = last_revid
159
175
            if not low_revid:
160
 
                low_revid = self._branch.get_rev_id(1)
 
176
                low_revid = self._bzrbranch.get_rev_id(1)
 
177
        finally:
 
178
            repo.unlock()
161
179
 
162
180
        # The spread must include the high revision, to bias
163
181
        # odd numbers of intervening revisions towards the high
167
185
        if spread < 2:
168
186
            middle_index = 0
169
187
        else:
170
 
            middle_index = (spread // 2) - 1
 
188
            middle_index = (spread / 2) - 1
171
189
 
172
190
        if len(between_revs) > 0:
173
191
            self._middle_revid = between_revs[middle_index]
180
198
    def _switch_wc_to_revno(self, revno, outf):
181
199
        """Move the working tree to the given revno."""
182
200
        self._current.switch(revno)
183
 
        self._current.show_rev_log(outf=outf)
 
201
        self._current.show_rev_log(out=outf)
184
202
 
185
203
    def _set_status(self, revid, status):
186
204
        """Set the bisect status for the given revid."""
197
215
    def load(self):
198
216
        """Load the bisection log."""
199
217
        self._items = []
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))
205
223
 
206
224
    def save(self):
207
225
        """Save the bisection log."""
208
 
        contents = b''.join(
209
 
            (b"%s %s\n" % (revid, status.encode('ascii')))
210
 
            for (revid, status) in self._items)
211
 
        if self._filename:
212
 
            self._controldir.control_transport.put_bytes(
213
 
                self._filename, contents)
214
 
        else:
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))
216
229
 
217
230
    def is_done(self):
218
231
        """Report whether we've found the right revision."""
220
233
 
221
234
    def set_status_from_revspec(self, revspec, status):
222
235
        """Set the bisection status for the revision in revspec."""
223
 
        self._load_tree()
224
 
        revid = revspec[0].in_history(self._branch).rev_id
 
236
        self._load_bzr_tree()
 
237
        revid = revspec[0].in_history(self._bzrbranch).rev_id
225
238
        self._set_status(revid, status)
226
239
 
227
240
    def set_current(self, status):
232
245
        return len(self.get_parent_revids(revid)) > 1
233
246
 
234
247
    def get_parent_revids(self, revid):
235
 
        repo = self._branch.repository
236
 
        with repo.lock_read():
 
248
        repo = self._bzrbranch.repository
 
249
        repo.lock_read()
 
250
        try:
237
251
            retval = repo.get_parent_map([revid]).get(revid, None)
 
252
        finally:
 
253
            repo.unlock()
238
254
        return retval
239
255
 
240
256
    def bisect(self, outf):
242
258
        self._find_range_and_middle()
243
259
        # If we've found the "final" revision, check for a
244
260
        # merge point.
245
 
        while ((self._middle_revid == self._high_revid or
246
 
                self._middle_revid == self._low_revid) and
247
 
                self.is_merge_point(self._middle_revid)):
 
261
        while ((self._middle_revid == self._high_revid
 
262
                or self._middle_revid == self._low_revid)
 
263
                and self.is_merge_point(self._middle_revid)):
248
264
            for parent in self.get_parent_revids(self._middle_revid):
249
265
                if parent == self._low_revid:
250
266
                    continue
272
288
    of which changes the state of the bisection.  The
273
289
    subcommands are:
274
290
 
275
 
    brz bisect start
 
291
    bzr bisect start
276
292
        Start a bisect, possibly clearing out a previous bisect.
277
293
 
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,
281
297
 
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,
285
301
 
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.
290
306
 
291
 
    brz bisect reset
 
307
    bzr bisect reset
292
308
        Clear out a bisection in progress.
293
309
 
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.
297
313
 
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.
301
317
 
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:
305
321
           0 for yes
309
325
 
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),
 
329
                     'revision']
314
330
 
315
 
    def _check(self, controldir):
 
331
    def _check(self):
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.")
319
335
 
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.
322
338
 
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)
328
344
            return True
329
345
 
330
346
        if revspec:
335
351
        bisect_log.save()
336
352
        return False
337
353
 
338
 
    def run(self, subcommand, args_list, directory='.', revision=None,
339
 
            output=None):
 
354
    def run(self, subcommand, args_list, revision=None, output=None):
340
355
        """Handle the bisect command."""
341
356
 
342
357
        log_fn = None
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:
348
 
            raise CommandError(
 
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:
353
 
            raise CommandError(
 
368
            raise BzrCommandError(
354
369
                "Improper arguments to bisect " + subcommand)
355
370
 
356
 
        controldir, _ = ControlDir.open_containing(directory)
357
 
 
358
371
        # Dispatch.
 
372
 
359
373
        if subcommand == "start":
360
 
            self.start(controldir)
 
374
            self.start()
361
375
        elif subcommand == "yes":
362
 
            self.yes(controldir, revision)
 
376
            self.yes(revision)
363
377
        elif subcommand == "no":
364
 
            self.no(controldir, revision)
 
378
            self.no(revision)
365
379
        elif subcommand == "move":
366
 
            self.move(controldir, revision)
 
380
            self.move(revision)
367
381
        elif subcommand == "reset":
368
 
            self.reset(controldir)
 
382
            self.reset()
369
383
        elif subcommand == "log":
370
 
            self.log(controldir, output)
 
384
            self.log(output)
371
385
        elif subcommand == "replay":
372
 
            self.replay(controldir, log_fn)
 
386
            self.replay(log_fn)
373
387
        elif subcommand == "run":
374
 
            self.run_bisect(controldir, run_script)
 
388
            self.run_bisect(run_script)
375
389
        else:
376
 
            raise CommandError(
 
390
            raise BzrCommandError(
377
391
                "Unknown bisect command: " + subcommand)
378
392
 
379
 
    def reset(self, controldir):
 
393
    def reset(self):
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)
 
395
        self._check()
 
396
        BisectCurrent().reset()
 
397
        os.unlink(bisect_info_path)
384
398
 
385
 
    def start(self, controldir):
 
399
    def start(self):
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)
390
404
 
391
 
        bisect_log = BisectLog(controldir)
 
405
        bisect_log = BisectLog()
392
406
        bisect_log.set_current("start")
393
407
        bisect_log.save()
394
408
 
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")
398
 
 
399
 
    def no(self, controldir, revspec):
400
 
        """Mark a given revision as wrong."""
401
 
        self._set_state(controldir, revspec, "no")
402
 
 
403
 
    def move(self, controldir, revspec):
 
411
        self._set_state(revspec, "yes")
 
412
 
 
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")
 
416
 
 
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)
408
422
 
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)
 
425
        self._check()
 
426
        bisect_log = BisectLog()
413
427
        bisect_log.change_file_name(filename)
414
428
        bisect_log.save()
415
429
 
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()
425
439
 
426
440
        bisect_log.bisect(self.outf)
427
441
 
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)
 
445
        self.start()
432
446
        while True:
433
447
            try:
434
448
                process = subprocess.Popen(script, shell=True)
435
449
                process.wait()
436
450
                retcode = process.returncode
437
451
                if retcode == 0:
438
 
                    done = self._set_state(controldir, None, 'yes')
 
452
                    done = self._set_state(None, 'yes')
439
453
                elif retcode == 125:
440
454
                    break
441
455
                else:
442
 
                    done = self._set_state(controldir, None, 'no')
 
456
                    done = self._set_state(None, 'no')
443
457
                if done:
444
458
                    break
445
459
            except RuntimeError: