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
17
"""bzr upgrade logic."""
19
# change upgrade from .bzr to create a '.bzr-new', then do a bait and switch.
22
from bzrlib.bzrdir import ConvertBzrDir4To5, ConvertBzrDir5To6, BzrDir, BzrDirFormat4, BzrDirFormat5
23
import bzrlib.errors as errors
24
from bzrlib.transport import get_transport
25
import bzrlib.ui as ui
19
"""Experiment in converting existing bzr branches to weaves."""
21
# To make this properly useful
23
# 1. assign text version ids, and put those text versions into
24
# the inventory as they're converted.
26
# 2. keep track of the previous version of each file, rather than
27
# just using the last one imported
29
# 3. assign entry versions when files are added, renamed or moved.
31
# 4. when merged-in versions are observed, walk down through them
32
# to discover everything, then commit bottom-up
34
# 5. track ancestry as things are merged in, and commit that in each
37
# Perhaps it's best to first walk the whole graph and make a plan for
38
# what should be imported in what order? Need a kind of topological
39
# sort of all revisions. (Or do we, can we just before doing a revision
40
# see that all its parents have either been converted or abandoned?)
51
import hotshot, hotshot.stats
56
from bzrlib.branch import Branch, find_branch
57
from bzrlib.revfile import Revfile
58
from bzrlib.weave import Weave
59
from bzrlib.weavefile import read_weave, write_weave
60
from bzrlib.progress import ProgressBar
61
from bzrlib.atomicfile import AtomicFile
62
from bzrlib.xml4 import serializer_v4
63
from bzrlib.xml5 import serializer_v5
64
from bzrlib.trace import mutter, note, warning, enable_default_logging
28
68
class Convert(object):
30
def __init__(self, url, format):
32
self.bzrdir = BzrDir.open_unsupported(url)
33
if self.bzrdir.root_transport.is_readonly():
34
raise errors.UpgradeReadonly
35
self.transport = self.bzrdir.root_transport
36
self.pb = ui.ui_factory.nested_progress_bar()
71
self.converted_revs = set()
72
self.absent_revisions = set()
44
branch = self.bzrdir.open_branch()
45
if branch.bzrdir.root_transport.base != \
46
self.bzrdir.root_transport.base:
47
self.pb.note("This is a checkout. The branch (%s) needs to be "
48
"upgraded separately.",
49
branch.bzrdir.root_transport.base)
50
except errors.NotBranchError:
52
if not self.bzrdir.needs_format_conversion(self.format):
53
raise errors.UpToDateFormat(self.bzrdir._format)
54
if not self.bzrdir.can_convert_format():
55
raise errors.BzrError("cannot upgrade from branch format %s" %
57
self.pb.note('starting upgrade of %s', self.transport.base)
58
self._backup_control_dir()
59
while self.bzrdir.needs_format_conversion(self.format):
60
converter = self.bzrdir._format.get_converter(self.format)
61
self.bzrdir = converter.convert(self.bzrdir, self.pb)
62
self.pb.note("finished")
64
def _backup_control_dir(self):
65
self.pb.note('making backup of tree history')
66
self.transport.copy_tree('.bzr', '.bzr.backup')
67
self.pb.note('%s.bzr has been backed up to %s.bzr.backup',
70
self.pb.note('if conversion fails, you can move this directory back to .bzr')
71
self.pb.note('if it succeeds, you can remove this directory if you wish')
73
def upgrade(url, format=None):
74
"""Upgrade to format, or the default bzrdir format if not supplied."""
81
enable_default_logging()
82
self.pb = ProgressBar()
83
self.inv_weave = Weave('__inventory')
84
self.anc_weave = Weave('__ancestry')
88
# holds in-memory weaves for all files
91
b = self.branch = Branch('.', relax_version_check=True)
94
rev_history = b.revision_history()
98
# to_read is a stack holding the revisions we still need to process;
99
# appending to it adds new highest-priority revisions
101
self.to_read = [rev_history[-1]]
102
self.total_revs = len(rev_history)
104
rev_id = self.to_read.pop()
105
if (rev_id not in self.revisions
106
and rev_id not in self.absent_revisions):
107
self._load_one_rev(rev_id)
109
to_import = self._make_order()
110
for rev_id in to_import:
111
self._import_one_rev(rev_id)
113
# self._convert_one_rev(self.to_read.pop())
115
print 'upgraded to weaves:'
116
print ' %6d revisions and inventories' % len(self.revisions)
117
print ' %6d absent revisions removed' % len(self.absent_revisions)
118
print ' %6d texts' % self.text_count
120
self._write_all_weaves()
123
def _write_all_weaves(self):
125
return ############################################
126
# TODO: commit them all atomically at the end, not one by one
127
write_atomic_weave(self.inv_weave, 'weaves/inventory.weave')
128
write_atomic_weave(self.anc_weave, 'weaves/ancestry.weave')
129
for file_id, file_weave in text_weaves.items():
130
self.pb.update('writing weave', i, len(text_weaves))
131
write_atomic_weave(file_weave, 'weaves/%s.weave' % file_id)
137
def _load_one_rev(self, rev_id):
138
"""Load a revision object into memory.
140
Any parents not either loaded or abandoned get queued to be
142
self.pb.update('loading revision',
143
len(self.converted_revs),
145
if rev_id not in self.branch.revision_store:
147
note('revision {%s} not present in branch; '
148
'will not be converted',
150
self.absent_revisions.add(rev_id)
152
rev_xml = self.branch.revision_store[rev_id].read()
153
rev = serializer_v4.read_revision_from_string(rev_xml)
154
for parent_id in [x.revision_id for x in rev.parents]:
156
self.to_read.append(parent_id)
157
self.revisions[rev_id] = rev
160
def _import_one_rev(self, rev_id):
161
"""Convert rev_id and all referenced file texts to new format."""
165
def _make_order(self):
166
"""Return a suitable order for importing revisions.
168
The order must be such that an revision is imported after all
169
its (present) parents.
171
todo = set(self.revisions.keys())
172
done = self.absent_revisions.copy()
175
# scan through looking for a revision whose parents
177
for rev_id in sorted(list(todo)):
178
rev = self.revisions[rev_id]
179
parent_ids = set([x.revision_id for x in rev.parents])
180
if parent_ids.issubset(done):
181
# can take this one now
188
# Cannot import a revision until all its parents have been
189
# imported. in other words, we can only import revisions whose
190
# parents have all been imported. the first step must be to
191
# import a revision with no parents, of which there must be at
192
# least one. (So perhaps it's useful to store forward pointers
193
# from a list of parents to their children?)
195
# Another (equivalent?) approach is to build up the ordered
196
# ancestry list for the last revision, and walk through that. We
197
# are going to need that.
199
# We don't want to have to recurse all the way back down the list.
201
# Suppose we keep a queue of the revisions able to be processed at
202
# any point. This starts out with all the revisions having no
205
# This seems like a generally useful algorithm...
207
def _convert_one_rev(self, rev_id):
208
self._bump_progress()
211
if rev_id not in b.revision_store:
213
note('revision {%s} not present in branch; '
214
'will not be converted',
216
self.absent_revisions.add(rev_id)
219
rev_xml = b.revision_store[rev_id].read()
220
inv_xml = b.inventory_store[rev_id].read()
222
rev = serializer_v4.read_revision_from_string(rev_xml)
223
inv = serializer_v4.read_inventory_from_string(inv_xml)
225
self.converted_revs.add(rev_id)
227
return ##########################################
229
new_idx = self.inv_weave.add(rev_id, inv_parents, inv_xml)
230
inv_parents = [new_idx]
232
tree = b.revision_tree(rev_id)
235
# for each file in the inventory, put it into its own revfile
238
if ie.kind != 'file':
240
if last_text_sha.get(file_id) == ie.text_sha1:
243
last_text_sha[file_id] = ie.text_sha1
245
# new text (though possibly already stored); need to store it
246
text_lines = tree.get_file(file_id).readlines()
248
# if the file's created for the first time in this
249
# revision then make a new weave; else find the old one
250
if file_id not in text_weaves:
251
text_weaves[file_id] = Weave()
253
w = text_weaves[file_id]
255
# base the new text version off whatever was last
256
# (actually it'd be better to track this, to allow for
257
# files that are deleted and then reappear)
264
w.add(rev_id, parents, text_lines)
271
def write_atomic_weave(weave, filename):
272
inv_wf = AtomicFile(filename)
274
write_weave(weave, inv_wf)
282
def profile_convert():
283
prof_f = tempfile.NamedTemporaryFile()
285
prof = hotshot.Profile(prof_f.name)
287
prof.runcall(Convert)
290
stats = hotshot.stats.load(prof_f.name)
292
stats.sort_stats('time')
293
# XXX: Might like to write to stderr or the trace file instead but
294
# print_stats seems hardcoded to stdout
295
stats.print_stats(20)
298
if '-p' in sys.argv[1:]: