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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
19
from __future__ import absolute_import
20
22
from cStringIO import StringIO
24
26
from bzrlib import (
28
from bzrlib.errors import (TestamentMismatch, BzrError,
29
MalformedHeader, MalformedPatches, NotABundle)
30
from bzrlib.inventory import (Inventory, InventoryEntry,
31
InventoryDirectory, InventoryFile,
33
from bzrlib.osutils import sha_file, sha_string, pathjoin
30
from bzrlib.bundle import apply_bundle
31
from bzrlib.errors import (
35
from bzrlib.inventory import (
41
from bzrlib.osutils import sha_string, pathjoin
34
42
from bzrlib.revision import Revision, NULL_REVISION
35
43
from bzrlib.testament import StrictTestament
36
44
from bzrlib.trace import mutter, warning
37
import bzrlib.transport
38
45
from bzrlib.tree import Tree
39
import bzrlib.urlutils
40
46
from bzrlib.xml5 import serializer_v5
75
81
if self.properties:
76
82
for property in self.properties:
77
83
key_end = property.find(': ')
78
assert key_end is not None
79
key = property[:key_end].encode('utf-8')
80
value = property[key_end+2:].encode('utf-8')
85
if not property.endswith(':'):
86
raise ValueError(property)
87
key = str(property[:-1])
90
key = str(property[:key_end])
91
value = property[key_end+2:]
81
92
rev.properties[key] = value
97
def from_revision(revision):
98
revision_info = RevisionInfo(revision.revision_id)
99
date = timestamp.format_highres_date(revision.timestamp,
101
revision_info.date = date
102
revision_info.timezone = revision.timezone
103
revision_info.timestamp = revision.timestamp
104
revision_info.message = revision.message.split('\n')
105
revision_info.properties = [': '.join(p) for p in
106
revision.properties.iteritems()]
86
110
class BundleInfo(object):
87
111
"""This contains the meta information. Stuff that allows you to
88
112
recreate the revision or inventory XML.
114
def __init__(self, bundle_format=None):
115
self.bundle_format = None
91
116
self.committer = None
93
118
self.message = None
135
163
def get_base(self, revision):
136
164
revision_info = self.get_revision_info(revision.revision_id)
137
165
if revision_info.base_id is not None:
138
if revision_info.base_id == NULL_REVISION:
141
return revision_info.base_id
166
return revision_info.base_id
142
167
if len(revision.parent_ids) == 0:
143
168
# There is no base listed, and
144
169
# the lowest revision doesn't have a parent
145
170
# so this is probably against the empty tree
146
# and thus base truly is None
171
# and thus base truly is NULL_REVISION
149
174
return revision.parent_ids[-1]
171
196
raise KeyError(revision_id)
173
198
def revision_tree(self, repository, revision_id, base=None):
174
revision_id = osutils.safe_revision_id(revision_id)
175
199
revision = self.get_revision(revision_id)
176
200
base = self.get_base(revision)
177
assert base != revision_id
178
self._validate_references_from_repository(repository)
201
if base == revision_id:
202
raise AssertionError()
203
if not self._validated_revisions_against_repo:
204
self._validate_references_from_repository(repository)
179
205
revision_info = self.get_revision_info(revision_id)
180
206
inventory_revision_id = revision_id
181
bundle_tree = BundleTree(repository.revision_tree(base),
207
bundle_tree = BundleTree(repository.revision_tree(base),
182
208
inventory_revision_id)
183
209
self._update_tree(bundle_tree, revision_id)
185
211
inv = bundle_tree.inventory
186
212
self._validate_inventory(inv, revision_id)
187
self._validate_revision(inv, revision_id)
213
self._validate_revision(bundle_tree, revision_id)
189
215
return bundle_tree
226
252
for revision_id, sha1 in rev_to_sha.iteritems():
227
253
if repository.has_revision(revision_id):
228
testament = StrictTestament.from_revision(repository,
254
testament = StrictTestament.from_revision(repository,
230
256
local_sha1 = self._testament_sha1_from_revision(repository,
232
258
if sha1 != local_sha1:
233
raise BzrError('sha1 mismatch. For revision id {%s}'
259
raise BzrError('sha1 mismatch. For revision id {%s}'
234
260
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
237
263
elif revision_id not in checked:
238
264
missing[revision_id] = sha1
240
for inv_id, sha1 in inv_to_sha.iteritems():
241
if repository.has_revision(inv_id):
242
# Note: branch.get_inventory_sha1() just returns the value that
243
# is stored in the revision text, and that value may be out
244
# of date. This is bogus, because that means we aren't
245
# validating the actual text, just that we wrote and read the
246
# string. But for now, what the hell.
247
local_sha1 = repository.get_inventory_sha1(inv_id)
248
if sha1 != local_sha1:
249
raise BzrError('sha1 mismatch. For inventory id {%s}'
250
'local: %s, bundle: %s' %
251
(inv_id, local_sha1, sha1))
255
266
if len(missing) > 0:
256
267
# I don't know if this is an error yet
257
268
warning('Not all revision hashes could be validated.'
258
269
' Unable validate %d hashes' % len(missing))
259
270
mutter('Verified %d sha hashes for the bundle.' % count)
271
self._validated_revisions_against_repo = True
261
273
def _validate_inventory(self, inv, revision_id):
262
274
"""At this point we should have generated the BundleTree,
263
275
so build up an inventory, and make sure the hashes match.
266
assert inv is not None
268
277
# Now we should have a complete inventory entry.
269
278
s = serializer_v5.write_inventory_to_string(inv)
270
279
sha1 = sha_string(s)
271
280
# Target revision is the last entry in the real_revisions list
272
281
rev = self.get_revision(revision_id)
273
assert rev.revision_id == revision_id
282
if rev.revision_id != revision_id:
283
raise AssertionError()
274
284
if sha1 != rev.inventory_sha1:
275
open(',,bogus-inv', 'wb').write(s)
285
f = open(',,bogus-inv', 'wb')
276
290
warning('Inventory sha hash mismatch for revision %s. %s'
277
291
' != %s' % (revision_id, sha1, rev.inventory_sha1))
279
def _validate_revision(self, inventory, revision_id):
293
def _validate_revision(self, tree, revision_id):
280
294
"""Make sure all revision entries match their checksum."""
282
# This is a mapping from each revision id to it's sha hash
296
# This is a mapping from each revision id to its sha hash
285
299
rev = self.get_revision(revision_id)
286
300
rev_info = self.get_revision_info(revision_id)
287
assert rev.revision_id == rev_info.revision_id
288
assert rev.revision_id == revision_id
289
sha1 = self._testament_sha1(rev, inventory)
301
if not (rev.revision_id == rev_info.revision_id):
302
raise AssertionError()
303
if not (rev.revision_id == revision_id):
304
raise AssertionError()
305
sha1 = self._testament_sha1(rev, tree)
290
306
if sha1 != rev_info.sha1:
291
307
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
292
308
if rev.revision_id in rev_to_sha1:
432
448
' (unrecognized action): %r' % action_line)
433
449
valid_actions[action](kind, extra, lines)
451
def install_revisions(self, target_repo, stream_input=True):
452
"""Install revisions and return the target revision
454
:param target_repo: The repository to install into
455
:param stream_input: Ignored by this implementation.
457
apply_bundle.install_bundle(target_repo, self)
460
def get_merge_request(self, target_repo):
461
"""Provide data for performing a merge
463
Returns suggested base, suggested target, and patch verification status
465
return None, self.target, 'inapplicable'
436
468
class BundleTree(Tree):
437
470
def __init__(self, base_tree, revision_id):
438
471
self.base_tree = base_tree
439
472
self._renamed = {} # Mapping from old_path => new_path
589
626
base_id = self.old_contents_id(file_id)
590
627
if (base_id is not None and
591
base_id != self.base_tree.inventory.root.file_id):
628
base_id != self.base_tree.get_root_id()):
592
629
patch_original = self.base_tree.get_file(base_id)
594
631
patch_original = None
595
632
file_patch = self.patches.get(self.id2path(file_id))
596
633
if file_patch is None:
597
if (patch_original is None and
598
self.get_kind(file_id) == 'directory'):
634
if (patch_original is None and
635
self.kind(file_id) == 'directory'):
599
636
return StringIO()
600
assert patch_original is not None, "None: %s" % file_id
637
if patch_original is None:
638
raise AssertionError("None: %s" % file_id)
601
639
return patch_original
603
assert not file_patch.startswith('\\'), \
604
'Malformed patch for %s, %r' % (file_id, file_patch)
641
if file_patch.startswith('\\'):
643
'Malformed patch for %s, %r' % (file_id, file_patch))
605
644
return patched_file(file_patch, patch_original)
607
def get_symlink_target(self, file_id):
608
new_path = self.id2path(file_id)
646
def get_symlink_target(self, file_id, path=None):
648
path = self.id2path(file_id)
610
return self._targets[new_path]
650
return self._targets[path]
612
652
return self.base_tree.get_symlink_target(file_id)
614
def get_kind(self, file_id):
654
def kind(self, file_id):
615
655
if file_id in self._kinds:
616
656
return self._kinds[file_id]
617
return self.base_tree.inventory[file_id].kind
657
return self.base_tree.kind(file_id)
659
def get_file_revision(self, file_id):
660
path = self.id2path(file_id)
661
if path in self._last_changed:
662
return self._last_changed[path]
664
return self.base_tree.get_file_revision(file_id)
619
666
def is_executable(self, file_id):
620
667
path = self.id2path(file_id)
621
668
if path in self._executable:
622
669
return self._executable[path]
624
return self.base_tree.inventory[file_id].executable
671
return self.base_tree.is_executable(file_id)
626
673
def get_last_changed(self, file_id):
627
674
path = self.id2path(file_id)
628
675
if path in self._last_changed:
629
676
return self._last_changed[path]
630
return self.base_tree.inventory[file_id].revision
677
return self.base_tree.get_file_revision(file_id)
632
679
def get_size_and_sha1(self, file_id):
633
680
"""Return the size and sha1 hash of the given file id.
640
687
if new_path not in self.patches:
641
688
# If the entry does not have a patch, then the
642
689
# contents must be the same as in the base_tree
643
ie = self.base_tree.inventory[file_id]
644
if ie.text_size is None:
645
return ie.text_size, ie.text_sha1
646
return int(ie.text_size), ie.text_sha1
690
text_size = self.base_tree.get_file_size(file_id)
691
text_sha1 = self.base_tree.get_file_sha1(file_id)
692
return text_size, text_sha1
647
693
fileobj = self.get_file(file_id)
648
694
content = fileobj.read()
649
695
return len(content), sha_string(content)
680
723
ie.executable = self.is_executable(file_id)
681
724
elif kind == 'symlink':
682
725
ie = InventoryLink(file_id, name, parent_id)
683
ie.symlink_target = self.get_symlink_target(file_id)
726
ie.symlink_target = self.get_symlink_target(file_id, path)
684
727
ie.revision = revision_id
686
if kind in ('directory', 'symlink'):
687
ie.text_size, ie.text_sha1 = None, None
689
730
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
690
if (ie.text_size is None) and (kind == 'file'):
691
raise BzrError('Got a text_size of None for file_id %r' % file_id)
731
if ie.text_size is None:
733
'Got a text_size of None for file_id %r' % file_id)
694
736
sorted_entries = self.sorted_path_id()
704
746
# at that instant
705
747
inventory = property(_get_inventory)
708
for path, entry in self.inventory.iter_entries():
749
root_inventory = property(_get_inventory)
751
def all_file_ids(self):
753
[entry.file_id for path, entry in self.inventory.iter_entries()])
755
def list_files(self, include_root=False, from_dir=None, recursive=True):
756
# The only files returned by this are those from the version
761
from_dir_id = inv.path2id(from_dir)
762
if from_dir_id is None:
763
# Directory not versioned
765
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
766
if inv.root is not None and not include_root and from_dir is None:
767
# skip the root for compatability with the current apis.
769
for path, entry in entries:
770
yield path, 'V', entry.kind, entry.file_id, entry
711
772
def sorted_path_id(self):
713
774
for result in self._new_id.iteritems():
714
775
paths.append(result)
715
for id in self.base_tree:
776
for id in self.base_tree.all_file_ids():
716
777
path = self.id2path(id)