1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 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
"""Read in a bundle stream, and process it into a BundleReader object."""
24
24
from bzrlib import (
28
27
import bzrlib.errors
29
28
from bzrlib.bundle import apply_bundle
30
from bzrlib.errors import (TestamentMismatch, BzrError,
29
from bzrlib.errors import (TestamentMismatch, BzrError,
31
30
MalformedHeader, MalformedPatches, NotABundle)
32
31
from bzrlib.inventory import (Inventory, InventoryEntry,
33
32
InventoryDirectory, InventoryFile,
78
77
for property in self.properties:
79
78
key_end = property.find(': ')
81
if not property.endswith(':'):
82
raise ValueError(property)
80
assert property.endswith(':')
83
81
key = str(property[:-1])
93
def from_revision(revision):
94
revision_info = RevisionInfo(revision.revision_id)
95
date = timestamp.format_highres_date(revision.timestamp,
97
revision_info.date = date
98
revision_info.timezone = revision.timezone
99
revision_info.timestamp = revision.timestamp
100
revision_info.message = revision.message.split('\n')
101
revision_info.properties = [': '.join(p) for p in
102
revision.properties.iteritems()]
106
91
class BundleInfo(object):
107
92
"""This contains the meta information. Stuff that allows you to
108
93
recreate the revision or inventory XML.
110
def __init__(self, bundle_format=None):
111
self.bundle_format = None
112
96
self.committer = None
114
98
self.message = None
159
143
def get_base(self, revision):
160
144
revision_info = self.get_revision_info(revision.revision_id)
161
145
if revision_info.base_id is not None:
162
return revision_info.base_id
146
if revision_info.base_id == NULL_REVISION:
149
return revision_info.base_id
163
150
if len(revision.parent_ids) == 0:
164
151
# There is no base listed, and
165
152
# the lowest revision doesn't have a parent
166
153
# so this is probably against the empty tree
167
# and thus base truly is NULL_REVISION
154
# and thus base truly is None
170
157
return revision.parent_ids[-1]
192
179
raise KeyError(revision_id)
194
181
def revision_tree(self, repository, revision_id, base=None):
182
revision_id = osutils.safe_revision_id(revision_id)
195
183
revision = self.get_revision(revision_id)
196
184
base = self.get_base(revision)
197
if base == revision_id:
198
raise AssertionError()
185
assert base != revision_id
199
186
if not self._validated_revisions_against_repo:
200
187
self._validate_references_from_repository(repository)
201
188
revision_info = self.get_revision_info(revision_id)
202
189
inventory_revision_id = revision_id
203
bundle_tree = BundleTree(repository.revision_tree(base),
190
bundle_tree = BundleTree(repository.revision_tree(base),
204
191
inventory_revision_id)
205
192
self._update_tree(bundle_tree, revision_id)
239
226
for rev_info in self.revisions:
240
227
checked[rev_info.revision_id] = True
241
228
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
243
230
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
244
231
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
248
235
for revision_id, sha1 in rev_to_sha.iteritems():
249
236
if repository.has_revision(revision_id):
250
testament = StrictTestament.from_revision(repository,
237
testament = StrictTestament.from_revision(repository,
252
239
local_sha1 = self._testament_sha1_from_revision(repository,
254
241
if sha1 != local_sha1:
255
raise BzrError('sha1 mismatch. For revision id {%s}'
242
raise BzrError('sha1 mismatch. For revision id {%s}'
256
243
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
259
246
elif revision_id not in checked:
260
247
missing[revision_id] = sha1
249
for inv_id, sha1 in inv_to_sha.iteritems():
250
if repository.has_revision(inv_id):
251
# Note: branch.get_inventory_sha1() just returns the value that
252
# is stored in the revision text, and that value may be out
253
# of date. This is bogus, because that means we aren't
254
# validating the actual text, just that we wrote and read the
255
# string. But for now, what the hell.
256
local_sha1 = repository.get_inventory_sha1(inv_id)
257
if sha1 != local_sha1:
258
raise BzrError('sha1 mismatch. For inventory id {%s}'
259
'local: %s, bundle: %s' %
260
(inv_id, local_sha1, sha1))
262
264
if len(missing) > 0:
263
265
# I don't know if this is an error yet
264
266
warning('Not all revision hashes could be validated.'
270
272
"""At this point we should have generated the BundleTree,
271
273
so build up an inventory, and make sure the hashes match.
276
assert inv is not None
273
278
# Now we should have a complete inventory entry.
274
279
s = serializer_v5.write_inventory_to_string(inv)
275
280
sha1 = sha_string(s)
276
281
# Target revision is the last entry in the real_revisions list
277
282
rev = self.get_revision(revision_id)
278
if rev.revision_id != revision_id:
279
raise AssertionError()
283
assert rev.revision_id == revision_id
280
284
if sha1 != rev.inventory_sha1:
281
f = open(',,bogus-inv', 'wb')
285
open(',,bogus-inv', 'wb').write(s)
286
286
warning('Inventory sha hash mismatch for revision %s. %s'
287
287
' != %s' % (revision_id, sha1, rev.inventory_sha1))
292
292
# This is a mapping from each revision id to it's sha hash
295
295
rev = self.get_revision(revision_id)
296
296
rev_info = self.get_revision_info(revision_id)
297
if not (rev.revision_id == rev_info.revision_id):
298
raise AssertionError()
299
if not (rev.revision_id == revision_id):
300
raise AssertionError()
297
assert rev.revision_id == rev_info.revision_id
298
assert rev.revision_id == revision_id
301
299
sha1 = self._testament_sha1(rev, inventory)
302
300
if sha1 != rev_info.sha1:
303
301
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
332
330
name, value = info_item.split(':', 1)
333
331
except ValueError:
334
raise ValueError('Value %r has no colon' % info_item)
332
raise 'Value %r has no colon' % info_item
335
333
if name == 'last-changed':
336
334
last_changed = value
337
335
elif name == 'executable':
336
assert value in ('yes', 'no'), value
338
337
val = (value == 'yes')
339
338
bundle_tree.note_executable(new_path, val)
340
339
elif name == 'target':
344
343
return last_changed, encoding
346
345
def do_patch(path, lines, encoding):
347
if encoding == 'base64':
346
if encoding is not None:
347
assert encoding == 'base64'
348
348
patch = base64.decodestring(''.join(lines))
349
elif encoding is None:
350
350
patch = ''.join(lines)
352
raise ValueError(encoding)
353
351
bundle_tree.note_patch(path, patch)
355
353
def renamed(kind, extra, lines):
444
442
' (unrecognized action): %r' % action_line)
445
443
valid_actions[action](kind, extra, lines)
447
def install_revisions(self, target_repo, stream_input=True):
448
"""Install revisions and return the target revision
450
:param target_repo: The repository to install into
451
:param stream_input: Ignored by this implementation.
445
def install_revisions(self, target_repo):
446
"""Install revisions and return the target revision"""
453
447
apply_bundle.install_bundle(target_repo, self)
454
448
return self.target
456
def get_merge_request(self, target_repo):
457
"""Provide data for performing a merge
459
Returns suggested base, suggested target, and patch verification status
461
return None, self.target, 'inapplicable'
464
451
class BundleTree(Tree):
465
452
def __init__(self, base_tree, revision_id):
484
471
def note_rename(self, old_path, new_path):
485
472
"""A file/directory has been renamed from old_path => new_path"""
486
if new_path in self._renamed:
487
raise AssertionError(new_path)
488
if old_path in self._renamed_r:
489
raise AssertionError(old_path)
473
assert new_path not in self._renamed
474
assert old_path not in self._renamed_r
490
475
self._renamed[new_path] = old_path
491
476
self._renamed_r[old_path] = new_path
523
508
def old_path(self, new_path):
524
509
"""Get the old_path (path in the base_tree) for the file at new_path"""
525
if new_path[:1] in ('\\', '/'):
526
raise ValueError(new_path)
510
assert new_path[:1] not in ('\\', '/')
527
511
old_path = self._renamed.get(new_path)
528
512
if old_path is not None:
544
528
if old_path in self._renamed_r:
548
532
def new_path(self, old_path):
549
533
"""Get the new_path (path in the target_tree) for the file at old_path
550
534
in the base tree.
552
if old_path[:1] in ('\\', '/'):
553
raise ValueError(old_path)
536
assert old_path[:1] not in ('\\', '/')
554
537
new_path = self._renamed_r.get(old_path)
555
538
if new_path is not None:
610
593
new_path = self.id2path(file_id)
611
594
return self.base_tree.path2id(new_path)
613
596
def get_file(self, file_id):
614
597
"""Return a file-like object containing the new contents of the
615
598
file given by file_id.
626
609
patch_original = None
627
610
file_patch = self.patches.get(self.id2path(file_id))
628
611
if file_patch is None:
629
if (patch_original is None and
612
if (patch_original is None and
630
613
self.get_kind(file_id) == 'directory'):
631
614
return StringIO()
632
if patch_original is None:
633
raise AssertionError("None: %s" % file_id)
615
assert patch_original is not None, "None: %s" % file_id
634
616
return patch_original
636
if file_patch.startswith('\\'):
638
'Malformed patch for %s, %r' % (file_id, file_patch))
618
assert not file_patch.startswith('\\'), \
619
'Malformed patch for %s, %r' % (file_id, file_patch)
639
620
return patched_file(file_patch, patch_original)
641
622
def get_symlink_target(self, file_id):
688
669
This need to be called before ever accessing self.inventory
690
671
from os.path import dirname, basename
673
assert self.base_tree is not None
691
674
base_inv = self.base_tree.inventory
692
675
inv = Inventory(None, self.revision_id)
715
698
ie.symlink_target = self.get_symlink_target(file_id)
716
699
ie.revision = revision_id
701
if kind in ('directory', 'symlink'):
702
ie.text_size, ie.text_sha1 = None, None
719
704
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
720
if ie.text_size is None:
722
'Got a text_size of None for file_id %r' % file_id)
705
if (ie.text_size is None) and (kind == 'file'):
706
raise BzrError('Got a text_size of None for file_id %r' % file_id)
725
709
sorted_entries = self.sorted_path_id()