28
from bzrlib.bundle import apply_bundle
29
from bzrlib.errors import (
33
from bzrlib.inventory import (
39
from bzrlib.osutils import sha_string, pathjoin
25
from bzrlib.errors import (TestamentMismatch, BzrError,
26
MalformedHeader, MalformedPatches, NotABundle)
27
from bzrlib.inventory import (Inventory, InventoryEntry,
28
InventoryDirectory, InventoryFile,
30
from bzrlib.osutils import sha_file, sha_string, pathjoin
40
31
from bzrlib.revision import Revision, NULL_REVISION
41
32
from bzrlib.testament import StrictTestament
42
33
from bzrlib.trace import mutter, warning
34
import bzrlib.transport
43
35
from bzrlib.tree import Tree
36
import bzrlib.urlutils
44
37
from bzrlib.xml5 import serializer_v5
79
72
if self.properties:
80
73
for property in self.properties:
81
74
key_end = property.find(': ')
83
if not property.endswith(':'):
84
raise ValueError(property)
85
key = str(property[:-1])
88
key = str(property[:key_end])
89
value = property[key_end+2:]
75
assert key_end is not None
76
key = property[:key_end].encode('utf-8')
77
value = property[key_end+2:].encode('utf-8')
90
78
rev.properties[key] = value
95
def from_revision(revision):
96
revision_info = RevisionInfo(revision.revision_id)
97
date = timestamp.format_highres_date(revision.timestamp,
99
revision_info.date = date
100
revision_info.timezone = revision.timezone
101
revision_info.timestamp = revision.timestamp
102
revision_info.message = revision.message.split('\n')
103
revision_info.properties = [': '.join(p) for p in
104
revision.properties.iteritems()]
108
83
class BundleInfo(object):
109
84
"""This contains the meta information. Stuff that allows you to
110
85
recreate the revision or inventory XML.
112
def __init__(self, bundle_format=None):
113
self.bundle_format = None
114
88
self.committer = None
116
90
self.message = None
161
132
def get_base(self, revision):
162
133
revision_info = self.get_revision_info(revision.revision_id)
163
134
if revision_info.base_id is not None:
164
return revision_info.base_id
135
if revision_info.base_id == NULL_REVISION:
138
return revision_info.base_id
165
139
if len(revision.parent_ids) == 0:
166
140
# There is no base listed, and
167
141
# the lowest revision doesn't have a parent
168
142
# so this is probably against the empty tree
169
# and thus base truly is NULL_REVISION
143
# and thus base truly is None
172
146
return revision.parent_ids[-1]
196
170
def revision_tree(self, repository, revision_id, base=None):
197
171
revision = self.get_revision(revision_id)
198
172
base = self.get_base(revision)
199
if base == revision_id:
200
raise AssertionError()
201
if not self._validated_revisions_against_repo:
202
self._validate_references_from_repository(repository)
173
assert base != revision_id
174
self._validate_references_from_repository(repository)
203
175
revision_info = self.get_revision_info(revision_id)
204
176
inventory_revision_id = revision_id
205
bundle_tree = BundleTree(repository.revision_tree(base),
177
bundle_tree = BundleTree(repository.revision_tree(base),
206
178
inventory_revision_id)
207
179
self._update_tree(bundle_tree, revision_id)
209
181
inv = bundle_tree.inventory
210
182
self._validate_inventory(inv, revision_id)
211
self._validate_revision(bundle_tree, revision_id)
183
self._validate_revision(inv, revision_id)
213
185
return bundle_tree
250
222
for revision_id, sha1 in rev_to_sha.iteritems():
251
223
if repository.has_revision(revision_id):
252
testament = StrictTestament.from_revision(repository,
224
testament = StrictTestament.from_revision(repository,
254
local_sha1 = self._testament_sha1_from_revision(repository,
226
local_sha1 = testament.as_sha1()
256
227
if sha1 != local_sha1:
257
raise BzrError('sha1 mismatch. For revision id {%s}'
228
raise BzrError('sha1 mismatch. For revision id {%s}'
258
229
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
261
232
elif revision_id not in checked:
262
233
missing[revision_id] = sha1
235
for inv_id, sha1 in inv_to_sha.iteritems():
236
if repository.has_revision(inv_id):
237
# Note: branch.get_inventory_sha1() just returns the value that
238
# is stored in the revision text, and that value may be out
239
# of date. This is bogus, because that means we aren't
240
# validating the actual text, just that we wrote and read the
241
# string. But for now, what the hell.
242
local_sha1 = repository.get_inventory_sha1(inv_id)
243
if sha1 != local_sha1:
244
raise BzrError('sha1 mismatch. For inventory id {%s}'
245
'local: %s, bundle: %s' %
246
(inv_id, local_sha1, sha1))
264
250
if len(missing) > 0:
265
251
# I don't know if this is an error yet
266
252
warning('Not all revision hashes could be validated.'
267
253
' Unable validate %d hashes' % len(missing))
268
254
mutter('Verified %d sha hashes for the bundle.' % count)
269
self._validated_revisions_against_repo = True
271
256
def _validate_inventory(self, inv, revision_id):
272
257
"""At this point we should have generated the BundleTree,
273
258
so build up an inventory, and make sure the hashes match.
261
assert inv is not None
275
263
# Now we should have a complete inventory entry.
276
264
s = serializer_v5.write_inventory_to_string(inv)
277
265
sha1 = sha_string(s)
278
266
# Target revision is the last entry in the real_revisions list
279
267
rev = self.get_revision(revision_id)
280
if rev.revision_id != revision_id:
281
raise AssertionError()
268
assert rev.revision_id == revision_id
282
269
if sha1 != rev.inventory_sha1:
283
f = open(',,bogus-inv', 'wb')
270
open(',,bogus-inv', 'wb').write(s)
288
271
warning('Inventory sha hash mismatch for revision %s. %s'
289
272
' != %s' % (revision_id, sha1, rev.inventory_sha1))
291
def _validate_revision(self, tree, revision_id):
274
def _validate_revision(self, inventory, revision_id):
292
275
"""Make sure all revision entries match their checksum."""
294
# This is a mapping from each revision id to its sha hash
277
# This is a mapping from each revision id to it's sha hash
297
280
rev = self.get_revision(revision_id)
298
281
rev_info = self.get_revision_info(revision_id)
299
if not (rev.revision_id == rev_info.revision_id):
300
raise AssertionError()
301
if not (rev.revision_id == revision_id):
302
raise AssertionError()
303
sha1 = self._testament_sha1(rev, tree)
282
assert rev.revision_id == rev_info.revision_id
283
assert rev.revision_id == revision_id
284
sha1 = StrictTestament(rev, inventory).as_sha1()
304
285
if sha1 != rev_info.sha1:
305
286
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
306
287
if rev.revision_id in rev_to_sha1:
446
422
' (unrecognized action): %r' % action_line)
447
423
valid_actions[action](kind, extra, lines)
449
def install_revisions(self, target_repo, stream_input=True):
450
"""Install revisions and return the target revision
452
:param target_repo: The repository to install into
453
:param stream_input: Ignored by this implementation.
455
apply_bundle.install_bundle(target_repo, self)
458
def get_merge_request(self, target_repo):
459
"""Provide data for performing a merge
461
Returns suggested base, suggested target, and patch verification status
463
return None, self.target, 'inapplicable'
466
426
class BundleTree(Tree):
468
427
def __init__(self, base_tree, revision_id):
469
428
self.base_tree = base_tree
470
429
self._renamed = {} # Mapping from old_path => new_path
624
579
base_id = self.old_contents_id(file_id)
625
if (base_id is not None and
626
base_id != self.base_tree.inventory.root.file_id):
580
if base_id is not None:
627
581
patch_original = self.base_tree.get_file(base_id)
629
583
patch_original = None
630
584
file_patch = self.patches.get(self.id2path(file_id))
631
585
if file_patch is None:
632
if (patch_original is None and
586
if (patch_original is None and
633
587
self.get_kind(file_id) == 'directory'):
634
588
return StringIO()
635
if patch_original is None:
636
raise AssertionError("None: %s" % file_id)
589
assert patch_original is not None, "None: %s" % file_id
637
590
return patch_original
639
if file_patch.startswith('\\'):
641
'Malformed patch for %s, %r' % (file_id, file_patch))
592
assert not file_patch.startswith('\\'), \
593
'Malformed patch for %s, %r' % (file_id, file_patch)
642
594
return patched_file(file_patch, patch_original)
644
596
def get_symlink_target(self, file_id):
691
643
This need to be called before ever accessing self.inventory
693
645
from os.path import dirname, basename
647
assert self.base_tree is not None
694
648
base_inv = self.base_tree.inventory
695
inv = Inventory(None, self.revision_id)
649
root_id = base_inv.root.file_id
651
# New inventories have a unique root_id
652
inv = Inventory(root_id, self.revision_id)
654
inv = Inventory(revision_id=self.revision_id)
655
inv.root.revision = self.get_last_changed(root_id)
697
657
def add_entry(file_id):
698
658
path = self.id2path(file_id)
661
parent_path = dirname(path)
662
if parent_path == u'':
704
parent_path = dirname(path)
705
665
parent_id = self.path2id(parent_path)
707
667
kind = self.get_kind(file_id)
718
678
ie.symlink_target = self.get_symlink_target(file_id)
719
679
ie.revision = revision_id
681
if kind in ('directory', 'symlink'):
682
ie.text_size, ie.text_sha1 = None, None
722
684
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
723
if ie.text_size is None:
725
'Got a text_size of None for file_id %r' % file_id)
685
if (ie.text_size is None) and (kind == 'file'):
686
raise BzrError('Got a text_size of None for file_id %r' % file_id)
728
689
sorted_entries = self.sorted_path_id()
729
690
for path, file_id in sorted_entries:
691
if file_id == inv.root.file_id:
730
693
add_entry(file_id)
742
705
for path, entry in self.inventory.iter_entries():
743
706
yield entry.file_id
745
def list_files(self, include_root=False, from_dir=None, recursive=True):
746
# The only files returned by this are those from the version
751
from_dir_id = inv.path2id(from_dir)
752
if from_dir_id is None:
753
# Directory not versioned
755
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
756
if inv.root is not None and not include_root and from_dir is None:
757
# skip the root for compatability with the current apis.
759
for path, entry in entries:
760
yield path, 'V', entry.kind, entry.file_id, entry
762
708
def sorted_path_id(self):
764
710
for result in self._new_id.iteritems():