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
226
local_sha1 = self._testament_sha1_from_revision(repository,
256
228
if sha1 != local_sha1:
257
raise BzrError('sha1 mismatch. For revision id {%s}'
229
raise BzrError('sha1 mismatch. For revision id {%s}'
258
230
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
261
233
elif revision_id not in checked:
262
234
missing[revision_id] = sha1
236
for inv_id, sha1 in inv_to_sha.iteritems():
237
if repository.has_revision(inv_id):
238
# Note: branch.get_inventory_sha1() just returns the value that
239
# is stored in the revision text, and that value may be out
240
# of date. This is bogus, because that means we aren't
241
# validating the actual text, just that we wrote and read the
242
# string. But for now, what the hell.
243
local_sha1 = repository.get_inventory_sha1(inv_id)
244
if sha1 != local_sha1:
245
raise BzrError('sha1 mismatch. For inventory id {%s}'
246
'local: %s, bundle: %s' %
247
(inv_id, local_sha1, sha1))
264
251
if len(missing) > 0:
265
252
# I don't know if this is an error yet
266
253
warning('Not all revision hashes could be validated.'
267
254
' Unable validate %d hashes' % len(missing))
268
255
mutter('Verified %d sha hashes for the bundle.' % count)
269
self._validated_revisions_against_repo = True
271
257
def _validate_inventory(self, inv, revision_id):
272
258
"""At this point we should have generated the BundleTree,
273
259
so build up an inventory, and make sure the hashes match.
262
assert inv is not None
275
264
# Now we should have a complete inventory entry.
276
265
s = serializer_v5.write_inventory_to_string(inv)
277
266
sha1 = sha_string(s)
278
267
# Target revision is the last entry in the real_revisions list
279
268
rev = self.get_revision(revision_id)
280
if rev.revision_id != revision_id:
281
raise AssertionError()
269
assert rev.revision_id == revision_id
282
270
if sha1 != rev.inventory_sha1:
283
f = open(',,bogus-inv', 'wb')
271
open(',,bogus-inv', 'wb').write(s)
288
272
warning('Inventory sha hash mismatch for revision %s. %s'
289
273
' != %s' % (revision_id, sha1, rev.inventory_sha1))
291
def _validate_revision(self, tree, revision_id):
275
def _validate_revision(self, inventory, revision_id):
292
276
"""Make sure all revision entries match their checksum."""
294
# This is a mapping from each revision id to its sha hash
278
# This is a mapping from each revision id to it's sha hash
297
281
rev = self.get_revision(revision_id)
298
282
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)
283
assert rev.revision_id == rev_info.revision_id
284
assert rev.revision_id == revision_id
285
sha1 = self._testament_sha1(rev, inventory)
304
286
if sha1 != rev_info.sha1:
305
287
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
306
288
if rev.revision_id in rev_to_sha1:
446
423
' (unrecognized action): %r' % action_line)
447
424
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
427
class BundleTree(Tree):
468
428
def __init__(self, base_tree, revision_id):
469
429
self.base_tree = base_tree
470
430
self._renamed = {} # Mapping from old_path => new_path
629
585
patch_original = None
630
586
file_patch = self.patches.get(self.id2path(file_id))
631
587
if file_patch is None:
632
if (patch_original is None and
588
if (patch_original is None and
633
589
self.get_kind(file_id) == 'directory'):
634
590
return StringIO()
635
if patch_original is None:
636
raise AssertionError("None: %s" % file_id)
591
assert patch_original is not None, "None: %s" % file_id
637
592
return patch_original
639
if file_patch.startswith('\\'):
641
'Malformed patch for %s, %r' % (file_id, file_patch))
594
assert not file_patch.startswith('\\'), \
595
'Malformed patch for %s, %r' % (file_id, file_patch)
642
596
return patched_file(file_patch, patch_original)
644
def get_symlink_target(self, file_id, path=None):
646
path = self.id2path(file_id)
598
def get_symlink_target(self, file_id):
599
new_path = self.id2path(file_id)
648
return self._targets[path]
601
return self._targets[new_path]
650
603
return self.base_tree.get_symlink_target(file_id)
716
671
ie.executable = self.is_executable(file_id)
717
672
elif kind == 'symlink':
718
673
ie = InventoryLink(file_id, name, parent_id)
719
ie.symlink_target = self.get_symlink_target(file_id, path)
674
ie.symlink_target = self.get_symlink_target(file_id)
720
675
ie.revision = revision_id
677
if kind in ('directory', 'symlink'):
678
ie.text_size, ie.text_sha1 = None, None
723
680
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
724
if ie.text_size is None:
726
'Got a text_size of None for file_id %r' % file_id)
681
if (ie.text_size is None) and (kind == 'file'):
682
raise BzrError('Got a text_size of None for file_id %r' % file_id)
729
685
sorted_entries = self.sorted_path_id()
743
699
for path, entry in self.inventory.iter_entries():
744
700
yield entry.file_id
746
def list_files(self, include_root=False, from_dir=None, recursive=True):
747
# The only files returned by this are those from the version
752
from_dir_id = inv.path2id(from_dir)
753
if from_dir_id is None:
754
# Directory not versioned
756
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
757
if inv.root is not None and not include_root and from_dir is None:
758
# skip the root for compatability with the current apis.
760
for path, entry in entries:
761
yield path, 'V', entry.kind, entry.file_id, entry
763
702
def sorted_path_id(self):
765
704
for result in self._new_id.iteritems():
766
705
paths.append(result)
767
for id in self.base_tree.all_file_ids():
706
for id in self.base_tree:
768
707
path = self.id2path(id)