1
# Copyright (C) 2005, 2006, 2009 Canonical Ltd
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
"""Serializer factory for reading and writing bundles.
20
from __future__ import absolute_import
22
from bzrlib import errors
26
23
from bzrlib.bundle.serializer import (BundleSerializer,
29
26
from bzrlib.bundle.serializer import binary_diff
30
from bzrlib.bundle.bundle_data import (RevisionInfo, BundleInfo)
27
from bzrlib.bundle.bundle_data import (RevisionInfo, BundleInfo, BundleTree)
31
28
from bzrlib.diff import internal_diff
29
from bzrlib.osutils import pathjoin
30
from bzrlib.progress import DummyProgress
32
31
from bzrlib.revision import NULL_REVISION
33
33
from bzrlib.testament import StrictTestament
34
34
from bzrlib.timestamp import (
35
35
format_highres_date,
37
38
from bzrlib.textfile import text_file
38
39
from bzrlib.trace import mutter
118
119
source.lock_read()
120
121
self._write_main_header()
121
pb = ui.ui_factory.nested_progress_bar()
123
124
self._write_revisions(pb)
129
def write_bundle(self, repository, target, base, fileobj):
130
return self._write_bundle(repository, target, base, fileobj)
132
131
def _write_main_header(self):
133
132
"""Write the header for the changes"""
135
f.write(_get_bundle_header('0.8'))
134
f.write(BUNDLE_HEADER)
138
138
def _write(self, key, value, indent=1, trailing_space_when_empty=False):
144
144
If this parameter is True, and value is the empty string, we will
145
145
write an extra space.
148
raise ValueError('indentation must be greater than 0')
147
assert indent > 0, 'indentation must be greater than 0'
150
149
f.write('#' + (' ' * indent))
151
150
f.write(key.encode('utf-8'))
182
181
i_max = len(self.revision_ids)
183
182
for i, rev_id in enumerate(self.revision_ids):
184
pb.update("Generating revision data", i, i_max)
183
pb.update("Generating revsion data", i, i_max)
185
184
rev = self.source.get_revision(rev_id)
186
185
if rev_id == last_rev_id:
187
186
rev_tree = last_rev_tree
205
204
base_tree = self.source.revision_tree(base_id)
206
205
force_binary = (i != 0)
207
self._write_revision(rev, rev_tree, base_id, base_tree,
206
self._write_revision(rev, rev_tree, base_id, base_tree,
208
207
explicit_base, force_binary)
210
209
last_rev_id = base_id
211
210
last_rev_tree = base_tree
213
212
def _testament_sha1(self, revision_id):
214
return StrictTestament.from_revision(self.source,
213
return StrictTestament.from_revision(self.source,
215
214
revision_id).as_sha1()
217
def _write_revision(self, rev, rev_tree, base_rev, base_tree,
216
def _write_revision(self, rev, rev_tree, base_rev, base_tree,
218
217
explicit_base, force_binary):
219
218
"""Write out the information for a revision."""
220
219
def w(key, value):
239
238
for name, value in sorted(rev.properties.items()):
240
239
self._write(name, value, indent=3,
241
240
trailing_space_when_empty=True)
243
242
# Add an extra blank space at the end
244
243
self.to_file.write('\n')
252
251
self.to_file.write(' // '.join(p_texts).encode('utf-8'))
253
252
self.to_file.write('\n')
255
def _write_delta(self, new_tree, old_tree, default_revision_id,
254
def _write_delta(self, new_tree, old_tree, default_revision_id,
257
256
"""Write out the changes between the trees."""
258
257
DEVNULL = '/dev/null'
262
261
def do_diff(file_id, old_path, new_path, action, force_binary):
263
262
def tree_lines(tree, require_text=False):
264
if tree.has_id(file_id):
265
264
tree_file = tree.get_file(file_id)
266
265
if require_text is True:
267
266
tree_file = text_file(tree_file)
275
274
old_lines = tree_lines(old_tree, require_text=True)
276
275
new_lines = tree_lines(new_tree, require_text=True)
277
276
action.write(self.to_file)
278
internal_diff(old_path, old_lines, new_path, new_lines,
277
internal_diff(old_path, old_lines, new_path, new_lines,
280
279
except errors.BinaryFile:
281
280
old_lines = tree_lines(old_tree, require_text=False)
282
281
new_lines = tree_lines(new_tree, require_text=False)
283
282
action.add_property('encoding', 'base64')
284
283
action.write(self.to_file)
285
binary_diff(old_path, old_lines, new_path, new_lines,
284
binary_diff(old_path, old_lines, new_path, new_lines,
288
287
def finish_action(action, file_id, kind, meta_modified, text_modified,
289
288
old_path, new_path):
290
entry = new_tree.root_inventory[file_id]
289
entry = new_tree.inventory[file_id]
291
290
if entry.revision != default_revision_id:
292
291
action.add_utf8_property('last-changed', entry.revision)
293
292
if meta_modified:
307
306
for path, file_id, kind in delta.added:
308
307
action = Action('added', [kind, path], [('file-id', file_id)])
309
meta_modified = (kind=='file' and
308
meta_modified = (kind=='file' and
310
309
new_tree.is_executable(file_id))
311
310
finish_action(action, file_id, kind, meta_modified, True,
326
325
for path, file_id, kind in delta.unchanged:
327
new_rev = new_tree.get_file_revision(file_id)
326
ie = new_tree.inventory[file_id]
327
new_rev = getattr(ie, 'revision', None)
328
328
if new_rev is None:
330
old_rev = old_tree.get_file_revision(file_id)
330
old_rev = getattr(old_tree.inventory[ie.file_id], 'revision', None)
331
331
if new_rev != old_rev:
332
action = Action('modified', [new_tree.kind(file_id),
333
new_tree.id2path(file_id)])
334
action.add_utf8_property('last-changed', new_rev)
332
action = Action('modified', [ie.kind,
333
new_tree.id2path(ie.file_id)])
334
action.add_utf8_property('last-changed', ie.revision)
335
335
action.write(self.to_file)
347
347
object.__init__(self)
348
348
self.from_file = iter(from_file)
349
349
self._next_line = None
351
351
self.info = self._get_info()
352
352
# We put the actual inventory ids in the footer, so that the patch
353
353
# is easier to read for humans.
459
459
# What do we do with a key we don't recognize
460
460
raise errors.MalformedHeader('Unknown Key: "%s"' % key)
462
462
def _read_many(self, indent):
463
463
"""If a line ends with no entry, that means that it should be
464
464
followed with multiple lines of values.
501
501
elif line.startswith('... '):
502
502
action += line[len('... '):-1].decode('utf-8')
504
if (self._next_line is not None and
504
if (self._next_line is not None and
505
505
self._next_line.startswith('===')):
506
506
return action, lines, True
507
507
elif self._next_line is None or self._next_line.startswith('#'):
521
521
action, lines, do_continue = self._read_one_patch()
522
522
if action is not None:
523
523
revision_actions.append((action, lines))
524
if self.info.revisions[-1].tree_actions is not None:
525
raise AssertionError()
524
assert self.info.revisions[-1].tree_actions is None
526
525
self.info.revisions[-1].tree_actions = revision_actions
528
527
def _read_footer(self):
550
549
testament = StrictTestament.from_revision(repository, revision_id)
551
550
return testament.as_sha1()
553
def _testament_sha1(self, revision, tree):
554
return StrictTestament(revision, tree).as_sha1()
552
def _testament_sha1(self, revision, inventory):
553
return StrictTestament(revision, inventory).as_sha1()