/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.54.34 by Jeff Licquia
Add copyright/license info.
1
# bisect plugin for Bazaar 2.x (bzr)
2
# Copyright 2006 Jeff Licquia.
3
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
0.54.38 by Jeff Licquia
Add plugin description docstring.
18
"Support for git-style bisection."
19
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
20
import sys
0.54.4 by Jeff Licquia
Add test setup and dummy test.
21
import os
0.54.12 by Jeff Licquia
Add some bzr functionality.
22
import bzrlib.bzrdir
0.54.1 by Jeff Licquia
Initial revision. The plugin skeleton is made, along with a spec for
23
from bzrlib.commands import Command, register_command
0.54.3 by Jeff Licquia
Add test suite and get plugin to load.
24
from bzrlib.errors import BzrCommandError
0.54.37 by Jeff Licquia
Import the proper things for Option to work.
25
from bzrlib.option import Option
0.54.1 by Jeff Licquia
Initial revision. The plugin skeleton is made, along with a spec for
26
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
27
bisect_info_path = ".bzr/bisect"
0.54.25 by Jeff Licquia
Change name of revid file.
28
bisect_rev_path = ".bzr/bisect_revid"
0.54.14 by Jeff Licquia
Create a parent for the test classes, and add a BisectCurrent class
29
0.54.52 by Jeff Licquia
PEP 8 fixes.
30
0.54.14 by Jeff Licquia
Create a parent for the test classes, and add a BisectCurrent class
31
class BisectCurrent(object):
32
    "Bisect class for managing the current revision."
33
34
    def __init__(self, filename = bisect_rev_path):
35
        self._filename = filename
36
        self._bzrdir = bzrlib.bzrdir.BzrDir.open_containing(".")[0]
0.54.16 by Jeff Licquia
Fix revision ID handling in BisectCurrent.
37
        self._bzrbranch = self._bzrdir.open_branch()
0.54.14 by Jeff Licquia
Create a parent for the test classes, and add a BisectCurrent class
38
        if os.path.exists(filename):
39
            f = open(filename)
40
            self._revid = f.read().strip()
41
            f.close()
42
        else:
0.54.27 by Jeff Licquia
Use the -r parameter when setting the bisect status, instead of setting
43
            self._revid = self._bzrbranch.last_revision()
0.54.14 by Jeff Licquia
Create a parent for the test classes, and add a BisectCurrent class
44
45
    def _save(self):
46
        f = open(self._filename, "w")
47
        f.write(self._revid + "\n")
48
        f.close()
49
0.54.27 by Jeff Licquia
Use the -r parameter when setting the bisect status, instead of setting
50
    def get_current_revid(self):
51
        return self._revid
52
0.54.51 by Jeff Licquia
Get subtree traversal working properly at last.
53
    def get_parent_revids(self):
0.54.55 by Jeff Licquia
Fix more missing locks, found in testing bzr 1.0 compatibility.
54
        repo = self._bzrbranch.repository
55
        repo.lock_read()
56
        retval = repo.get_parents([self._revid])[0]
57
        repo.unlock()
58
        return retval
0.54.44 by Jeff Licquia
Implement is_merge_point().
59
0.54.43 by Jeff Licquia
Add non-functioning is_merge_point() method to BisectCurrent.
60
    def is_merge_point(self):
61
        "Is the current revision a merge point?"
0.54.51 by Jeff Licquia
Get subtree traversal working properly at last.
62
        return len(self.get_parent_revids()) > 1
0.54.43 by Jeff Licquia
Add non-functioning is_merge_point() method to BisectCurrent.
63
0.54.32 by Jeff Licquia
When changing revisions, print information about the revision.
64
    def show_rev_log(self, out = sys.stdout):
0.54.51 by Jeff Licquia
Get subtree traversal working properly at last.
65
        rev = self._bzrbranch.repository.get_revision(self._revid)
66
        sys.stdout.write("On revision ?? (%s):\n%s\n" % (rev.revision_id,
67
                                                         rev.message))
0.54.32 by Jeff Licquia
When changing revisions, print information about the revision.
68
0.54.22 by Jeff Licquia
Get BisectCurrent working.
69
    def switch(self, revid):
0.54.17 by Jeff Licquia
Fix switch to use the proper object (working tree).
70
        wt = self._bzrdir.open_workingtree()
0.54.22 by Jeff Licquia
Get BisectCurrent working.
71
        if isinstance(revid, int):
72
            revid = self._bzrbranch.get_rev_id(revid)
0.54.31 by Jeff Licquia
Do revision specs properly with "bisect move".
73
        elif isinstance(revid, list):
74
            revid = revid[0].in_history(wt.branch).rev_id
0.54.22 by Jeff Licquia
Get BisectCurrent working.
75
        wt.revert([], wt.branch.repository.revision_tree(revid), False)
76
        self._revid = revid
0.54.14 by Jeff Licquia
Create a parent for the test classes, and add a BisectCurrent class
77
        self._save()
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
78
0.54.22 by Jeff Licquia
Get BisectCurrent working.
79
    def reset(self):
80
        wt = self._bzrdir.open_workingtree()
81
        last_rev = wt.branch.last_revision()
82
        rev_tree = wt.branch.repository.revision_tree(last_rev)
83
        wt.revert([], rev_tree, False)
0.54.24 by Jeff Licquia
Only remove the bzr revno file if it exists.
84
        if os.path.exists(bisect_rev_path):
85
            os.unlink(bisect_rev_path)
0.54.22 by Jeff Licquia
Get BisectCurrent working.
86
0.54.52 by Jeff Licquia
PEP 8 fixes.
87
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
88
class BisectLog(object):
89
    "Bisect log file handler."
90
91
    def __init__(self, filename = bisect_info_path):
92
        self._items = []
0.54.16 by Jeff Licquia
Fix revision ID handling in BisectCurrent.
93
        self._current = BisectCurrent()
0.54.12 by Jeff Licquia
Add some bzr functionality.
94
        self._bzrdir = None
0.54.51 by Jeff Licquia
Get subtree traversal working properly at last.
95
        self._high_revid = None
96
        self._low_revid = None
0.54.45 by Jeff Licquia
Refactor finding range and middle; store the middle revid, not revno.
97
        self._middle_revid = None
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
98
        self.change_file_name(filename)
99
        self.load()
100
101
    def _open_for_read(self):
102
        if self._filename:
103
            return open(self._filename)
104
        else:
105
            return sys.stdin
106
107
    def _open_for_write(self):
108
        if self._filename:
109
            return open(self._filename, "w")
110
        else:
111
            return sys.stdout
112
0.54.12 by Jeff Licquia
Add some bzr functionality.
113
    def _load_bzr_tree(self):
114
        if not self._bzrdir:
115
            self._bzrdir = bzrlib.bzrdir.BzrDir.open_containing('.')[0]
116
            self._bzrbranch = self._bzrdir.open_branch()
117
0.54.51 by Jeff Licquia
Get subtree traversal working properly at last.
118
    def _find_range_and_middle(self, branch_last_rev = None):
0.54.12 by Jeff Licquia
Add some bzr functionality.
119
        self._load_bzr_tree()
0.54.47 by Jeff Licquia
Refactor: when finding the middle, iterate over revids instead of
120
        self._middle_revid = None
0.54.21 by Jeff Licquia
The bisect call should detect a missing range, and just silently return.
121
0.54.51 by Jeff Licquia
Get subtree traversal working properly at last.
122
        if not branch_last_rev:
123
            last_revid = self._bzrbranch.last_revision()
124
        else:
125
            last_revid = branch_last_rev
126
0.54.52 by Jeff Licquia
PEP 8 fixes.
127
        repo = self._bzrbranch.repository
0.54.55 by Jeff Licquia
Fix more missing locks, found in testing bzr 1.0 compatibility.
128
        repo.lock_read()
0.54.52 by Jeff Licquia
PEP 8 fixes.
129
        rev_sequence = repo.iter_reverse_revision_history(last_revid)
0.54.47 by Jeff Licquia
Refactor: when finding the middle, iterate over revids instead of
130
        high_revid = None
131
        low_revid = None
132
        between_revs = []
0.54.48 by Jeff Licquia
More refactoring: go backwards from a high revision when finding the
133
        for revision in rev_sequence:
134
            between_revs.insert(0, revision)
0.54.52 by Jeff Licquia
PEP 8 fixes.
135
            matches = [x[1] for x in self._items
0.54.20 by Jeff Licquia
When looking for range, only use revision entries that have been
136
                       if x[0] == revision and x[1] in ('yes', 'no')]
0.54.12 by Jeff Licquia
Add some bzr functionality.
137
            if not matches:
138
                continue
139
            if len(matches) > 1:
0.54.47 by Jeff Licquia
Refactor: when finding the middle, iterate over revids instead of
140
                raise RuntimeError("revision %s duplicated" % revision)
0.54.12 by Jeff Licquia
Add some bzr functionality.
141
            if matches[0] == "yes":
0.54.47 by Jeff Licquia
Refactor: when finding the middle, iterate over revids instead of
142
                high_revid = revision
0.54.48 by Jeff Licquia
More refactoring: go backwards from a high revision when finding the
143
                between_revs = []
0.54.12 by Jeff Licquia
Add some bzr functionality.
144
            elif matches[0] == "no":
0.54.47 by Jeff Licquia
Refactor: when finding the middle, iterate over revids instead of
145
                low_revid = revision
0.54.48 by Jeff Licquia
More refactoring: go backwards from a high revision when finding the
146
                del between_revs[0]
147
                break
0.54.47 by Jeff Licquia
Refactor: when finding the middle, iterate over revids instead of
148
149
        if not high_revid:
150
            high_revid = last_revid
0.54.51 by Jeff Licquia
Get subtree traversal working properly at last.
151
        if not low_revid:
152
            low_revid = self._bzrbranch.get_rev_id(1)
0.54.47 by Jeff Licquia
Refactor: when finding the middle, iterate over revids instead of
153
0.54.55 by Jeff Licquia
Fix more missing locks, found in testing bzr 1.0 compatibility.
154
        repo.unlock()
155
0.54.47 by Jeff Licquia
Refactor: when finding the middle, iterate over revids instead of
156
        # The spread must include the high revision, to bias
157
        # odd numbers of intervening revisions towards the high
158
        # side.
159
160
        spread = len(between_revs) + 1
161
        if spread < 2:
162
            middle_index = 0
163
        else:
164
            middle_index = (spread / 2) - 1
165
166
        if len(between_revs) > 0:
167
            self._middle_revid = between_revs[middle_index]
168
        else:
169
            self._middle_revid = high_revid
0.54.12 by Jeff Licquia
Add some bzr functionality.
170
0.54.51 by Jeff Licquia
Get subtree traversal working properly at last.
171
        self._high_revid = high_revid
172
        self._low_revid = low_revid
173
0.54.12 by Jeff Licquia
Add some bzr functionality.
174
    def _switch_wc_to_revno(self, revno):
0.54.18 by Jeff Licquia
Incorporate use of new BisectCurrent objects where appropriate.
175
        self._current.switch(revno)
0.54.32 by Jeff Licquia
When changing revisions, print information about the revision.
176
        self._current.show_rev_log()
0.54.12 by Jeff Licquia
Add some bzr functionality.
177
0.54.30 by Jeff Licquia
Distinguish between revision specs and revision ids, and do the right
178
    def _set_status(self, revid, status):
0.54.46 by Jeff Licquia
Report a proper error when trying to mark the same revid twice.
179
        if revid in [x[0] for x in self._items if x[1] in ['yes', 'no']]:
0.54.52 by Jeff Licquia
PEP 8 fixes.
180
            raise RuntimeError("attempting to add revid %s twice" % revid)
0.54.30 by Jeff Licquia
Distinguish between revision specs and revision ids, and do the right
181
        self._items.append((revid, status))
182
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
183
    def change_file_name(self, filename):
184
        self._filename = filename
185
186
    def load(self):
187
        self._items = []
188
        if os.path.exists(self._filename):
189
            f = self._open_for_read()
190
            for line in f:
191
                (revid, status) = line.split()
192
                self._items.append((revid, status))
193
194
    def save(self):
195
        f = self._open_for_write()
196
        for (revid, status) in self._items:
197
            f.write("%s %s\n" % (revid, status))
198
0.54.30 by Jeff Licquia
Distinguish between revision specs and revision ids, and do the right
199
    def set_status_from_revspec(self, revspec, status):
200
        self._load_bzr_tree()
201
        revid = revspec[0].in_history(self._bzrbranch).rev_id
202
        self._set_status(revid, status)
0.54.27 by Jeff Licquia
Use the -r parameter when setting the bisect status, instead of setting
203
0.54.12 by Jeff Licquia
Add some bzr functionality.
204
    def set_current(self, status):
0.54.30 by Jeff Licquia
Distinguish between revision specs and revision ids, and do the right
205
        self._set_status(self._current.get_current_revid(), status)
0.54.12 by Jeff Licquia
Add some bzr functionality.
206
207
    def bisect(self):
0.54.51 by Jeff Licquia
Get subtree traversal working properly at last.
208
        self._find_range_and_middle()
209
        self._switch_wc_to_revno(self._middle_revid)
210
211
        # If we've found the "final" revision, check for a
212
        # merge point.
213
214
        if self._middle_revid == self._high_revid and \
215
           self._current.is_merge_point():
216
            for parent in self._current.get_parent_revids():
217
                if parent == self._low_revid:
218
                    continue
219
                else:
220
                    self._find_range_and_middle(parent)
221
                    self._switch_wc_to_revno(self._middle_revid)
222
                    break
0.54.12 by Jeff Licquia
Add some bzr functionality.
223
0.54.52 by Jeff Licquia
PEP 8 fixes.
224
0.54.1 by Jeff Licquia
Initial revision. The plugin skeleton is made, along with a spec for
225
class cmd_bisect(Command):
226
    """Find an interesting commit using a binary search.
227
228
    Bisecting, in a nutshell, is a way to find the commit at which
229
    some testable change was made, such as the introduction of a bug
230
    or feature.  By identifying a version which did not have the
231
    interesting change and a later version which did, a developer
232
    can test for the presence of the change at various points in
233
    the history, eventually ending up at the precise commit when
234
    the change was first introduced.
235
236
    This command uses subcommands to implement the search, each
237
    of which changes the state of the bisection.  The
238
    subcommands are:
239
240
    bzr bisect start
241
        Start a bisect, possibly clearing out a previous bisect.
242
243
    bzr bisect yes [-r rev]
244
        The specified revision (or the current revision, if not given)
245
        has the characteristic we're looking for,
246
247
    bzr bisect no [-r rev]
248
        The specified revision (or the current revision, if not given)
0.55.1 by Doug Lee
Minor spello fix.
249
        does not have the characteristic we're looking for,
0.54.1 by Jeff Licquia
Initial revision. The plugin skeleton is made, along with a spec for
250
0.54.13 by Jeff Licquia
Since bzr does not store a "current revision" other than the last
251
    bzr bisect move -r rev
252
        Switch to a different revision manually.  Use if the bisect
253
        algorithm chooses a revision that is not suitable.  Try to
254
        move as little as possible.
255
0.54.1 by Jeff Licquia
Initial revision. The plugin skeleton is made, along with a spec for
256
    bzr bisect reset
257
        Clear out a bisection in progress.
258
0.54.36 by Jeff Licquia
Add -o for log, and use it in the tests to capture the log.
259
    bzr bisect log [-o file]
260
        Output a log of the current bisection to standard output, or
261
        to the specified file.
0.54.1 by Jeff Licquia
Initial revision. The plugin skeleton is made, along with a spec for
262
263
    bzr bisect replay <logfile>
264
        Replay a previously-saved bisect log, forgetting any bisection
265
        that might be in progress.
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
266
    """
267
268
    takes_args = ['subcommand', 'args*']
0.54.36 by Jeff Licquia
Add -o for log, and use it in the tests to capture the log.
269
    takes_options = [Option('output', short_name='o',
270
                            help='Write log to this file.', type=unicode),
271
                     'revision']
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
272
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
273
    def _check(self):
274
        # Conditions that must be true for most operations to
275
        # work.
276
277
        if not os.path.exists(bisect_info_path):
278
            raise BzrCommandError("No bisect info found")
279
0.54.29 by Jeff Licquia
Oops! Mark start of method properly.
280
    def _set_state(self, revspec, state):
0.54.28 by Jeff Licquia
Refactor shared code to prevent cut-n-paste errors.
281
        bl = BisectLog()
282
        if revspec:
0.54.30 by Jeff Licquia
Distinguish between revision specs and revision ids, and do the right
283
            bl.set_status_from_revspec(revspec, state)
0.54.28 by Jeff Licquia
Refactor shared code to prevent cut-n-paste errors.
284
        else:
285
            bl.set_current(state)
286
        bl.bisect()
287
        bl.save()
288
0.54.36 by Jeff Licquia
Add -o for log, and use it in the tests to capture the log.
289
    def run(self, subcommand, args_list, revision=None, output=None):
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
290
        # Handle subcommand parameters.
291
292
        log_fn = None
0.54.13 by Jeff Licquia
Since bzr does not store a "current revision" other than the last
293
        if subcommand in ('yes', 'no', 'move') and revision:
0.54.8 by Jeff Licquia
Fix dispatcher.
294
            pass
0.54.6 by Jeff Licquia
Fix obvious typos and other stupidity.
295
        elif subcommand in ('replay',) and args_list and len(args_list) == 1:
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
296
            log_fn = args_list[0]
0.54.13 by Jeff Licquia
Since bzr does not store a "current revision" other than the last
297
        elif subcommand in ('move',) and not revision:
0.54.52 by Jeff Licquia
PEP 8 fixes.
298
            raise BzrCommandError(
299
                "The 'bisect move' command requires a revision.")
0.54.8 by Jeff Licquia
Fix dispatcher.
300
        elif args_list or revision:
0.54.52 by Jeff Licquia
PEP 8 fixes.
301
            raise BzrCommandError(
302
                "Improper arguments to bisect " + subcommand)
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
303
304
        # Dispatch.
305
306
        if subcommand == "start":
307
            self.start()
308
        elif subcommand == "yes":
309
            self.yes(revision)
310
        elif subcommand == "no":
311
            self.no(revision)
0.54.13 by Jeff Licquia
Since bzr does not store a "current revision" other than the last
312
        elif subcommand == "move":
313
            self.move(revision)
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
314
        elif subcommand == "reset":
315
            self.reset()
316
        elif subcommand == "log":
0.54.36 by Jeff Licquia
Add -o for log, and use it in the tests to capture the log.
317
            self.log(output)
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
318
        elif subcommand == "replay":
319
            self.replay(log_fn)
320
321
    def reset(self):
322
        "Reset the bisect state to no state."
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
323
0.54.22 by Jeff Licquia
Get BisectCurrent working.
324
        BisectCurrent().reset()
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
325
        if os.path.exists(bisect_info_path):
326
            os.unlink(bisect_info_path)
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
327
328
    def start(self):
329
        "Reset the bisect state, then prepare for a new bisection."
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
330
331
        self.reset()
0.54.12 by Jeff Licquia
Add some bzr functionality.
332
        bl = BisectLog()
333
        bl.set_current("start")
334
        bl.save()
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
335
0.54.28 by Jeff Licquia
Refactor shared code to prevent cut-n-paste errors.
336
    def yes(self, revspec):
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
337
        "Mark that a given revision has the state we're looking for."
0.54.12 by Jeff Licquia
Add some bzr functionality.
338
0.54.28 by Jeff Licquia
Refactor shared code to prevent cut-n-paste errors.
339
        self._set_state(revspec, "yes")
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
340
0.54.28 by Jeff Licquia
Refactor shared code to prevent cut-n-paste errors.
341
    def no(self, revspec):
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
342
        "Mark that a given revision does not have the state we're looking for."
0.54.12 by Jeff Licquia
Add some bzr functionality.
343
0.54.28 by Jeff Licquia
Refactor shared code to prevent cut-n-paste errors.
344
        self._set_state(revspec, "no")
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
345
0.54.31 by Jeff Licquia
Do revision specs properly with "bisect move".
346
    def move(self, revspec):
0.54.13 by Jeff Licquia
Since bzr does not store a "current revision" other than the last
347
        "Move to a different revision manually."
348
0.54.18 by Jeff Licquia
Incorporate use of new BisectCurrent objects where appropriate.
349
        bc = BisectCurrent()
0.54.31 by Jeff Licquia
Do revision specs properly with "bisect move".
350
        bc.switch(revspec)
0.54.32 by Jeff Licquia
When changing revisions, print information about the revision.
351
        bc.show_rev_log()
0.54.13 by Jeff Licquia
Since bzr does not store a "current revision" other than the last
352
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
353
    def log(self, filename):
354
        "Write the current bisect log to a file."
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
355
356
        self._check()
357
358
        bl = BisectLog()
359
        bl.change_file_name(filename)
360
        bl.save()
0.54.2 by Jeff Licquia
Set up the subcommand dispatcher.
361
362
    def replay(self, filename):
363
        """Apply the given log file to a clean state, so the state is
364
        exactly as it was when the log was saved."""
0.54.11 by Jeff Licquia
Add log file handler, give it basic I/O, and write first tests for it.
365
366
        self.reset()
367
368
        bl = BisectLog(filename)
369
        bl.change_file_name(bisect_info_path)
370
        bl.save()
0.54.1 by Jeff Licquia
Initial revision. The plugin skeleton is made, along with a spec for
371
0.54.12 by Jeff Licquia
Add some bzr functionality.
372
        bl.bisect()
373
0.54.1 by Jeff Licquia
Initial revision. The plugin skeleton is made, along with a spec for
374
register_command(cmd_bisect)
0.54.3 by Jeff Licquia
Add test suite and get plugin to load.
375
0.54.52 by Jeff Licquia
PEP 8 fixes.
376
0.54.3 by Jeff Licquia
Add test suite and get plugin to load.
377
def test_suite():
0.54.40 by Jeff Licquia
Move the tests to their own module.
378
    from bzrlib.plugins.bisect import tests
0.54.53 by Jeff Licquia
Move test suite detection function to the test module.
379
    return tests.test_suite()