~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to tools/history2weaves.py

  • Committer: Martin Pool
  • Date: 2005-09-16 09:56:24 UTC
  • Revision ID: mbp@sourcefrog.net-20050916095623-ca0dff452934f21f
- make progress bar more tolerant of out-of-range values

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/python
 
2
#
1
3
# Copyright (C) 2005 Canonical Ltd
2
4
#
3
5
# This program is free software; you can redistribute it and/or modify
14
16
# along with this program; if not, write to the Free Software
15
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
18
 
17
 
"""bzr upgrade logic."""
18
 
 
19
 
# change upgrade from .bzr to create a '.bzr-new', then do a bait and switch.
20
 
 
 
19
"""Experiment in converting existing bzr branches to weaves."""
21
20
 
22
21
# To make this properly useful
23
22
#
40
39
# sort of all revisions.  (Or do we, can we just before doing a revision
41
40
# see that all its parents have either been converted or abandoned?)
42
41
 
43
 
 
44
 
# Cannot import a revision until all its parents have been
45
 
# imported.  in other words, we can only import revisions whose
46
 
# parents have all been imported.  the first step must be to
47
 
# import a revision with no parents, of which there must be at
48
 
# least one.  (So perhaps it's useful to store forward pointers
49
 
# from a list of parents to their children?)
50
 
#
51
 
# Another (equivalent?) approach is to build up the ordered
52
 
# ancestry list for the last revision, and walk through that.  We
53
 
# are going to need that.
54
 
#
55
 
# We don't want to have to recurse all the way back down the list.
56
 
#
57
 
# Suppose we keep a queue of the revisions able to be processed at
58
 
# any point.  This starts out with all the revisions having no
59
 
# parents.
60
 
#
61
 
# This seems like a generally useful algorithm...
62
 
#
63
 
# The current algorithm is dumb (O(n**2)?) but will do the job, and
64
 
# takes less than a second on the bzr.dev branch.
65
 
 
66
 
# This currently does a kind of lazy conversion of file texts, where a
67
 
# new text is written in every version.  That's unnecessary but for
68
 
# the moment saves us having to worry about when files need new
69
 
# versions.
70
 
 
71
 
from cStringIO import StringIO
72
 
import os
 
42
if False:
 
43
    try:
 
44
        import psyco
 
45
        psyco.full()
 
46
    except ImportError:
 
47
        pass
 
48
 
 
49
 
73
50
import tempfile
 
51
import hotshot, hotshot.stats
74
52
import sys
75
 
from stat import *
 
53
import logging
 
54
import time
76
55
 
77
 
import bzrlib
78
 
from bzrlib.branch import Branch
79
 
import bzrlib.bzrdir as bzrdir
80
 
from bzrlib.bzrdir import BzrDirFormat, BzrDirFormat4, BzrDirFormat5, BzrDirFormat6
81
 
import bzrlib.errors as errors
82
 
from bzrlib.errors import NoSuchFile, UpgradeReadonly
83
 
import bzrlib.hashcache as hashcache
84
 
from bzrlib.lockable_files import LockableFiles
85
 
from bzrlib.osutils import sha_strings, sha_string, pathjoin, abspath
86
 
from bzrlib.ui import ui_factory
87
 
from bzrlib.store.text import TextStore
88
 
from bzrlib.store.weave import WeaveStore
89
 
from bzrlib.trace import mutter, note, warning
90
 
from bzrlib.transactions import PassThroughTransaction
91
 
from bzrlib.transport import get_transport
92
 
from bzrlib.transport.local import LocalTransport
 
56
from bzrlib.branch import Branch, find_branch
 
57
from bzrlib.revfile import Revfile
93
58
from bzrlib.weave import Weave
94
59
from bzrlib.weavefile import read_weave, write_weave
 
60
from bzrlib.progress import ProgressBar
 
61
from bzrlib.atomicfile import AtomicFile
95
62
from bzrlib.xml4 import serializer_v4
96
63
from bzrlib.xml5 import serializer_v5
 
64
from bzrlib.trace import mutter, note, warning, enable_default_logging
 
65
 
97
66
 
98
67
 
99
68
class Convert(object):
100
 
 
101
 
    def __init__(self, transport):
102
 
        self.base = transport.base
 
69
    def __init__(self):
 
70
        self.total_revs = 0
103
71
        self.converted_revs = set()
104
72
        self.absent_revisions = set()
105
73
        self.text_count = 0
106
 
        self.revisions = {}
107
 
        self.transport = transport
108
 
        if self.transport.is_readonly():
109
 
            raise UpgradeReadonly
110
 
        self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'branch-lock')
111
 
        # Lock the branch (soon to be meta dir) to prevent anyone racing with us
112
 
        # This is currently windows incompatible, it will deadlock. When the upgrade
113
 
        # logic becomes format specific, then we can have the format know how to pass this
114
 
        # on. Also note that we probably have an 'upgrade meta' which upgrades the constituent
115
 
        # parts.
116
 
        print "FIXME: control files reuse" 
117
 
        self.control_files.lock_write()
118
 
        try:
119
 
            self.convert()
120
 
        finally:
121
 
            self.control_files.unlock()
 
74
        self.convert()
 
75
 
 
76
 
122
77
 
123
78
    def convert(self):
124
 
        if not self._open_branch():
125
 
            return
126
 
        note('starting upgrade of %s', self.base)
127
 
        self._backup_control_dir()
128
 
        self.pb = ui_factory.progress_bar()
129
 
        if isinstance(self.old_format, BzrDirFormat4):
130
 
            note('starting upgrade from format 4 to 5')
131
 
            if isinstance(self.transport, LocalTransport):
132
 
                self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
133
 
            self._convert_to_weaves()
134
 
        if isinstance(self.old_format, BzrDirFormat5):
135
 
            note('starting upgrade from format 5 to 6')
136
 
            self._convert_to_prefixed()
137
 
        note("finished")
138
 
 
139
 
    def _convert_to_prefixed(self):
140
 
        from bzrlib.store import hash_prefix
141
 
        bzr_transport = self.transport.clone('.bzr')
142
 
        bzr_transport.delete('branch-format')
143
 
        for store_name in ["weaves", "revision-store"]:
144
 
            note("adding prefixes to %s" % store_name) 
145
 
            store_transport = bzr_transport.clone(store_name)
146
 
            for filename in store_transport.list_dir('.'):
147
 
                if (filename.endswith(".weave") or
148
 
                    filename.endswith(".gz") or
149
 
                    filename.endswith(".sig")):
150
 
                    file_id = os.path.splitext(filename)[0]
151
 
                else:
152
 
                    file_id = filename
153
 
                prefix_dir = hash_prefix(file_id)
154
 
                # FIXME keep track of the dirs made RBC 20060121
155
 
                try:
156
 
                    store_transport.move(filename, prefix_dir + '/' + filename)
157
 
                except NoSuchFile: # catches missing dirs strangely enough
158
 
                    store_transport.mkdir(prefix_dir)
159
 
                    store_transport.move(filename, prefix_dir + '/' + filename)
160
 
        self.old_format = BzrDirFormat6()
161
 
        self._set_new_format(self.old_format.get_format_string())
162
 
        self.bzrdir = self.old_format.open(self.transport)
163
 
        self.branch = self.bzrdir.open_branch()
164
 
 
165
 
    def _convert_to_weaves(self):
166
 
        note('note: upgrade may be faster if all store files are ungzipped first')
167
 
        bzr_transport = self.transport.clone('.bzr')
168
 
        try:
169
 
            # TODO permissions
170
 
            stat = bzr_transport.stat('weaves')
171
 
            if not S_ISDIR(stat.st_mode):
172
 
                bzr_transport.delete('weaves')
173
 
                bzr_transport.mkdir('weaves')
174
 
        except NoSuchFile:
175
 
            bzr_transport.mkdir('weaves')
176
 
        self.inv_weave = Weave('inventory')
 
79
        enable_default_logging()
 
80
        self.pb = ProgressBar()
 
81
        self.inv_weave = Weave('__inventory')
 
82
        self.anc_weave = Weave('__ancestry')
 
83
 
 
84
        last_text_sha = {}
 
85
 
177
86
        # holds in-memory weaves for all files
178
 
        self.text_weaves = {}
179
 
        bzr_transport.delete('branch-format')
180
 
        self._convert_working_inv()
181
 
        rev_history = self.branch.revision_history()
182
 
        # to_read is a stack holding the revisions we still need to process;
 
87
        text_weaves = {}
 
88
 
 
89
        b = self.branch = Branch('.', relax_version_check=True)
 
90
 
 
91
        revno = 1
 
92
        rev_history = b.revision_history()
 
93
        last_idx = None
 
94
        inv_parents = []
 
95
 
 
96
        # todo is a stack holding the revisions we still need to process;
183
97
        # appending to it adds new highest-priority revisions
184
 
        self.known_revisions = set(rev_history)
185
 
        self.to_read = rev_history[-1:]
186
 
        while self.to_read:
187
 
            rev_id = self.to_read.pop()
188
 
            if (rev_id not in self.revisions
189
 
                and rev_id not in self.absent_revisions):
190
 
                self._load_one_rev(rev_id)
191
 
        self.pb.clear()
192
 
        to_import = self._make_order()
193
 
        for i, rev_id in enumerate(to_import):
194
 
            self.pb.update('converting revision', i, len(to_import))
195
 
            self._convert_one_rev(rev_id)
196
 
        self.pb.clear()
 
98
        self.todo = rev_history[:]
 
99
        self.todo.reverse()
 
100
        self.total_revs = len(self.todo)
 
101
 
 
102
        while self.todo:
 
103
            self._convert_one_rev(self.todo.pop())
 
104
 
 
105
        self.pb.clear()
 
106
        print 'upgraded to weaves:'
 
107
        print '  %6d revisions and inventories' % len(self.converted_revs)
 
108
        print '  %6d absent revisions removed' % len(self.absent_revisions)
 
109
        print '  %6d texts' % self.text_count
 
110
 
197
111
        self._write_all_weaves()
198
 
        self._write_all_revs()
199
 
        note('upgraded to weaves:')
200
 
        note('  %6d revisions and inventories' % len(self.revisions))
201
 
        note('  %6d revisions not present' % len(self.absent_revisions))
202
 
        note('  %6d texts' % self.text_count)
203
 
        self._cleanup_spare_files_after_format4()
204
 
        self.old_format = BzrDirFormat5()
205
 
        self._set_new_format(self.old_format.get_format_string())
206
 
        self.bzrdir = self.old_format.open(self.transport)
207
 
        self.branch = self.bzrdir.open_branch()
208
 
 
209
 
    def _open_branch(self):
210
 
        self.old_format = BzrDirFormat.find_format(self.transport)
211
 
        self.bzrdir = self.old_format.open(self.transport)
212
 
        self.branch = self.bzrdir.open_branch()
213
 
        if isinstance(self.old_format, BzrDirFormat6):
214
 
            note('this branch is in the most current format (%s)', self.old_format)
215
 
            return False
216
 
        if (not isinstance(self.old_format, BzrDirFormat4) and
217
 
            not isinstance(self.old_format, BzrDirFormat5) and
218
 
            not isinstance(self.old_format, bzrdir.BzrDirMetaFormat1)):
219
 
            raise errors.BzrError("cannot upgrade from branch format %s" %
220
 
                           self.branch._branch_format)
221
 
        return True
222
 
 
223
 
    def _set_new_format(self, format):
224
 
        self.branch.control_files.put_utf8('branch-format', format)
225
 
 
226
 
    def _cleanup_spare_files_after_format4(self):
227
 
        transport = self.transport.clone('.bzr')
228
 
        print "FIXME working tree upgrade foo."
229
 
        for n in 'merged-patches', 'pending-merged-patches':
230
 
            try:
231
 
                ## assert os.path.getsize(p) == 0
232
 
                transport.delete(n)
233
 
            except NoSuchFile:
234
 
                pass
235
 
        transport.delete_tree('inventory-store')
236
 
        transport.delete_tree('text-store')
237
 
 
238
 
    def _backup_control_dir(self):
239
 
        note('making backup of tree history')
240
 
        self.transport.copy_tree('.bzr', '.bzr.backup')
241
 
        note('%s.bzr has been backed up to %s.bzr.backup',
242
 
             self.transport.base,
243
 
             self.transport.base)
244
 
        note('if conversion fails, you can move this directory back to .bzr')
245
 
        note('if it succeeds, you can remove this directory if you wish')
246
 
 
247
 
    def _convert_working_inv(self):
248
 
        branch = self.branch
249
 
        inv = serializer_v4.read_inventory(branch.control_files.get('inventory'))
250
 
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
251
 
        print "fixme inventory is a working tree change."
252
 
        branch.control_files.put('inventory', new_inv_xml)
 
112
 
253
113
 
254
114
    def _write_all_weaves(self):
255
 
        bzr_transport = self.transport.clone('.bzr')
256
 
        controlweaves = WeaveStore(bzr_transport, prefixed=False)
257
 
        weave_transport = bzr_transport.clone('weaves')
258
 
        weaves = WeaveStore(weave_transport, prefixed=False)
259
 
        transaction = PassThroughTransaction()
260
 
 
261
 
        controlweaves.put_weave('inventory', self.inv_weave, transaction)
262
115
        i = 0
263
 
        try:
264
 
            for file_id, file_weave in self.text_weaves.items():
265
 
                self.pb.update('writing weave', i, len(self.text_weaves))
266
 
                weaves.put_weave(file_id, file_weave, transaction)
267
 
                i += 1
268
 
        finally:
269
 
            self.pb.clear()
270
 
 
271
 
    def _write_all_revs(self):
272
 
        """Write all revisions out in new form."""
273
 
        transport = self.transport.clone('.bzr')
274
 
        transport.delete_tree('revision-store')
275
 
        transport.mkdir('revision-store')
276
 
        revision_transport = transport.clone('revision-store')
277
 
        # TODO permissions
278
 
        revision_store = TextStore(revision_transport,
279
 
                                   prefixed=False,
280
 
                                   compressed=True)
281
 
        try:
282
 
            for i, rev_id in enumerate(self.converted_revs):
283
 
                self.pb.update('write revision', i, len(self.converted_revs))
284
 
                rev_tmp = StringIO()
285
 
                serializer_v5.write_revision(self.revisions[rev_id], rev_tmp)
286
 
                rev_tmp.seek(0)
287
 
                revision_store.add(rev_tmp, rev_id)
288
 
        finally:
289
 
            self.pb.clear()
290
 
 
291
 
            
292
 
    def _load_one_rev(self, rev_id):
293
 
        """Load a revision object into memory.
294
 
 
295
 
        Any parents not either loaded or abandoned get queued to be
296
 
        loaded."""
297
 
        self.pb.update('loading revision',
298
 
                       len(self.revisions),
299
 
                       len(self.known_revisions))
300
 
        if not self.branch.repository.revision_store.has_id(rev_id):
 
116
        return ############################################
 
117
        # TODO: commit them all atomically at the end, not one by one
 
118
        write_atomic_weave(self.inv_weave, 'weaves/inventory.weave')
 
119
        write_atomic_weave(self.anc_weave, 'weaves/ancestry.weave')
 
120
        for file_id, file_weave in text_weaves.items():
 
121
            self.pb.update('writing weave', i, len(text_weaves))
 
122
            write_atomic_weave(file_weave, 'weaves/%s.weave' % file_id)
 
123
            i += 1
 
124
 
 
125
        self.pb.clear()
 
126
 
 
127
        
 
128
    def _convert_one_rev(self, rev_id):
 
129
        self._bump_progress()
 
130
        b = self.branch
 
131
 
 
132
        if rev_id not in b.revision_store:
301
133
            self.pb.clear()
302
134
            note('revision {%s} not present in branch; '
303
 
                 'will be converted as a ghost',
 
135
                 'will not be converted',
304
136
                 rev_id)
305
137
            self.absent_revisions.add(rev_id)
306
 
        else:
307
 
            rev_xml = self.branch.repository.revision_store.get(rev_id).read()
308
 
            rev = serializer_v4.read_revision_from_string(rev_xml)
309
 
            for parent_id in rev.parent_ids:
310
 
                self.known_revisions.add(parent_id)
311
 
                self.to_read.append(parent_id)
312
 
            self.revisions[rev_id] = rev
313
 
 
314
 
 
315
 
    def _load_old_inventory(self, rev_id):
316
 
        assert rev_id not in self.converted_revs
317
 
        old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
318
 
        inv = serializer_v4.read_inventory_from_string(old_inv_xml)
319
 
        rev = self.revisions[rev_id]
320
 
        if rev.inventory_sha1:
321
 
            assert rev.inventory_sha1 == sha_string(old_inv_xml), \
322
 
                'inventory sha mismatch for {%s}' % rev_id
323
 
        return inv
 
138
            return
324
139
        
325
 
 
326
 
    def _load_updated_inventory(self, rev_id):
327
 
        assert rev_id in self.converted_revs
328
 
        inv_xml = self.inv_weave.get_text(rev_id)
329
 
        inv = serializer_v5.read_inventory_from_string(inv_xml)
330
 
        return inv
331
 
 
332
 
 
333
 
    def _convert_one_rev(self, rev_id):
334
 
        """Convert revision and all referenced objects to new format."""
335
 
        rev = self.revisions[rev_id]
336
 
        inv = self._load_old_inventory(rev_id)
337
 
        present_parents = [p for p in rev.parent_ids
338
 
                           if p not in self.absent_revisions]
339
 
        self._convert_revision_contents(rev, inv, present_parents)
340
 
        self._store_new_weave(rev, inv, present_parents)
 
140
        rev_xml = b.revision_store[rev_id].read()
 
141
        inv_xml = b.inventory_store[rev_id].read()
 
142
 
 
143
        rev = serializer_v4.read_revision_from_string(rev_xml)
 
144
        inv = serializer_v4.read_inventory_from_string(inv_xml)
 
145
 
 
146
        # see if parents need to be done first
 
147
        for parent_id in [x.revision_id for x in rev.parents]:
 
148
            if parent_id not in self.converted_revs:
 
149
                self.todo.append(parent_id)
 
150
 
341
151
        self.converted_revs.add(rev_id)
342
 
 
343
 
 
344
 
    def _store_new_weave(self, rev, inv, present_parents):
345
 
        # the XML is now updated with text versions
346
 
        if __debug__:
347
 
            for file_id in inv:
348
 
                ie = inv[file_id]
349
 
                if ie.kind == 'root_directory':
350
 
                    continue
351
 
                assert hasattr(ie, 'revision'), \
352
 
                    'no revision on {%s} in {%s}' % \
353
 
                    (file_id, rev.revision_id)
354
 
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
355
 
        new_inv_sha1 = sha_string(new_inv_xml)
356
 
        self.inv_weave.add(rev.revision_id, 
357
 
                           present_parents,
358
 
                           new_inv_xml.splitlines(True),
359
 
                           new_inv_sha1)
360
 
        rev.inventory_sha1 = new_inv_sha1
361
 
 
362
 
    def _convert_revision_contents(self, rev, inv, present_parents):
363
 
        """Convert all the files within a revision.
364
 
 
365
 
        Also upgrade the inventory to refer to the text revision ids."""
366
 
        rev_id = rev.revision_id
367
 
        mutter('converting texts of revision {%s}',
368
 
               rev_id)
369
 
        parent_invs = map(self._load_updated_inventory, present_parents)
 
152
        
 
153
        return ##########################################
 
154
 
 
155
        new_idx = self.inv_weave.add(rev_id, inv_parents, inv_xml)
 
156
        inv_parents = [new_idx]
 
157
 
 
158
        tree = b.revision_tree(rev_id)
 
159
        inv = tree.inventory
 
160
 
 
161
        # for each file in the inventory, put it into its own revfile
370
162
        for file_id in inv:
371
163
            ie = inv[file_id]
372
 
            self._convert_file_version(rev, ie, parent_invs)
373
 
 
374
 
    def _convert_file_version(self, rev, ie, parent_invs):
375
 
        """Convert one version of one file.
376
 
 
377
 
        The file needs to be added into the weave if it is a merge
378
 
        of >=2 parents or if it's changed from its parent.
379
 
        """
380
 
        if ie.kind == 'root_directory':
381
 
            return
382
 
        file_id = ie.file_id
383
 
        rev_id = rev.revision_id
384
 
        w = self.text_weaves.get(file_id)
385
 
        if w is None:
386
 
            w = Weave(file_id)
387
 
            self.text_weaves[file_id] = w
388
 
        text_changed = False
389
 
        previous_entries = ie.find_previous_heads(parent_invs, w)
390
 
        for old_revision in previous_entries:
391
 
                # if this fails, its a ghost ?
392
 
                assert old_revision in self.converted_revs 
393
 
        self.snapshot_ie(previous_entries, ie, w, rev_id)
394
 
        del ie.text_id
395
 
        assert getattr(ie, 'revision', None) is not None
396
 
 
397
 
    def snapshot_ie(self, previous_revisions, ie, w, rev_id):
398
 
        # TODO: convert this logic, which is ~= snapshot to
399
 
        # a call to:. This needs the path figured out. rather than a work_tree
400
 
        # a v4 revision_tree can be given, or something that looks enough like
401
 
        # one to give the file content to the entry if it needs it.
402
 
        # and we need something that looks like a weave store for snapshot to 
403
 
        # save against.
404
 
        #ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
405
 
        if len(previous_revisions) == 1:
406
 
            previous_ie = previous_revisions.values()[0]
407
 
            if ie._unchanged(previous_ie):
408
 
                ie.revision = previous_ie.revision
409
 
                return
410
 
        parent_indexes = map(w.lookup, previous_revisions)
411
 
        if ie.has_text():
412
 
            text = self.branch.repository.text_store.get(ie.text_id)
413
 
            file_lines = text.readlines()
414
 
            assert sha_strings(file_lines) == ie.text_sha1
415
 
            assert sum(map(len, file_lines)) == ie.text_size
416
 
            w.add(rev_id, parent_indexes, file_lines, ie.text_sha1)
417
 
            self.text_count += 1
418
 
        else:
419
 
            w.add(rev_id, parent_indexes, [], None)
420
 
        ie.revision = rev_id
421
 
        ##mutter('import text {%s} of {%s}',
422
 
        ##       ie.text_id, file_id)
423
 
 
424
 
    def _make_order(self):
425
 
        """Return a suitable order for importing revisions.
426
 
 
427
 
        The order must be such that an revision is imported after all
428
 
        its (present) parents.
429
 
        """
430
 
        todo = set(self.revisions.keys())
431
 
        done = self.absent_revisions.copy()
432
 
        o = []
433
 
        while todo:
434
 
            # scan through looking for a revision whose parents
435
 
            # are all done
436
 
            for rev_id in sorted(list(todo)):
437
 
                rev = self.revisions[rev_id]
438
 
                parent_ids = set(rev.parent_ids)
439
 
                if parent_ids.issubset(done):
440
 
                    # can take this one now
441
 
                    o.append(rev_id)
442
 
                    todo.remove(rev_id)
443
 
                    done.add(rev_id)
444
 
        return o
445
 
 
446
 
 
447
 
def upgrade(url):
448
 
    t = get_transport(url)
449
 
    Convert(t)
 
164
            if ie.kind != 'file':
 
165
                continue
 
166
            if last_text_sha.get(file_id) == ie.text_sha1:
 
167
                # same as last time
 
168
                continue
 
169
            last_text_sha[file_id] = ie.text_sha1
 
170
 
 
171
            # new text (though possibly already stored); need to store it
 
172
            text_lines = tree.get_file(file_id).readlines()
 
173
 
 
174
            # if the file's created for the first time in this
 
175
            # revision then make a new weave; else find the old one
 
176
            if file_id not in text_weaves:
 
177
                text_weaves[file_id] = Weave()
 
178
 
 
179
            w = text_weaves[file_id]
 
180
 
 
181
            # base the new text version off whatever was last
 
182
            # (actually it'd be better to track this, to allow for
 
183
            # files that are deleted and then reappear)
 
184
            last = len(w)
 
185
            if last == 0:
 
186
                parents = []
 
187
            else:
 
188
                parents = [last-1]
 
189
 
 
190
            w.add(rev_id, parents, text_lines)
 
191
            text_count += 1
 
192
 
 
193
        revno += 1
 
194
        
 
195
    def _bump_progress(self):
 
196
        self.pb.update('converting revisions',
 
197
                       len(self.converted_revs),
 
198
                       self.total_revs)
 
199
 
 
200
 
 
201
def write_atomic_weave(weave, filename):
 
202
    inv_wf = AtomicFile(filename)
 
203
    try:
 
204
        write_weave(weave, inv_wf)
 
205
        inv_wf.commit()
 
206
    finally:
 
207
        inv_wf.close()
 
208
 
 
209
    
 
210
 
 
211
 
 
212
def profile_convert(): 
 
213
    prof_f = tempfile.NamedTemporaryFile()
 
214
 
 
215
    prof = hotshot.Profile(prof_f.name)
 
216
 
 
217
    prof.runcall(Convert) 
 
218
    prof.close()
 
219
 
 
220
    stats = hotshot.stats.load(prof_f.name)
 
221
    ##stats.strip_dirs()
 
222
    stats.sort_stats('time')
 
223
    # XXX: Might like to write to stderr or the trace file instead but
 
224
    # print_stats seems hardcoded to stdout
 
225
    stats.print_stats(20)
 
226
            
 
227
 
 
228
if '-p' in sys.argv[1:]:
 
229
    profile_convert()
 
230
else:
 
231
    Convert()
 
232