24
24
from bzrlib import (
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
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
40
34
from bzrlib.revision import Revision, NULL_REVISION
41
35
from bzrlib.testament import StrictTestament
42
36
from bzrlib.trace import mutter, warning
37
import bzrlib.transport
43
38
from bzrlib.tree import Tree
39
import bzrlib.urlutils
44
40
from bzrlib.xml5 import serializer_v5
79
75
if self.properties:
80
76
for property in self.properties:
81
77
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:]
78
assert key_end is not None
79
key = property[:key_end].encode('utf-8')
80
value = property[key_end+2:].encode('utf-8')
90
81
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
86
class BundleInfo(object):
109
87
"""This contains the meta information. Stuff that allows you to
110
88
recreate the revision or inventory XML.
112
def __init__(self, bundle_format=None):
113
self.bundle_format = None
114
91
self.committer = None
116
93
self.message = None
161
135
def get_base(self, revision):
162
136
revision_info = self.get_revision_info(revision.revision_id)
163
137
if revision_info.base_id is not None:
164
return revision_info.base_id
138
if revision_info.base_id == NULL_REVISION:
141
return revision_info.base_id
165
142
if len(revision.parent_ids) == 0:
166
143
# There is no base listed, and
167
144
# the lowest revision doesn't have a parent
168
145
# so this is probably against the empty tree
169
# and thus base truly is NULL_REVISION
146
# and thus base truly is None
172
149
return revision.parent_ids[-1]
196
173
def revision_tree(self, repository, revision_id, base=None):
197
174
revision = self.get_revision(revision_id)
198
175
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)
176
assert base != revision_id
177
self._validate_references_from_repository(repository)
203
178
revision_info = self.get_revision_info(revision_id)
204
179
inventory_revision_id = revision_id
205
bundle_tree = BundleTree(repository.revision_tree(base),
180
bundle_tree = BundleTree(repository.revision_tree(base),
206
181
inventory_revision_id)
207
182
self._update_tree(bundle_tree, revision_id)
209
184
inv = bundle_tree.inventory
210
185
self._validate_inventory(inv, revision_id)
211
self._validate_revision(bundle_tree, revision_id)
186
self._validate_revision(inv, revision_id)
213
188
return bundle_tree
250
225
for revision_id, sha1 in rev_to_sha.iteritems():
251
226
if repository.has_revision(revision_id):
252
testament = StrictTestament.from_revision(repository,
227
testament = StrictTestament.from_revision(repository,
254
229
local_sha1 = self._testament_sha1_from_revision(repository,
256
231
if sha1 != local_sha1:
257
raise BzrError('sha1 mismatch. For revision id {%s}'
232
raise BzrError('sha1 mismatch. For revision id {%s}'
258
233
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
261
236
elif revision_id not in checked:
262
237
missing[revision_id] = sha1
239
for inv_id, sha1 in inv_to_sha.iteritems():
240
if repository.has_revision(inv_id):
241
# Note: branch.get_inventory_sha1() just returns the value that
242
# is stored in the revision text, and that value may be out
243
# of date. This is bogus, because that means we aren't
244
# validating the actual text, just that we wrote and read the
245
# string. But for now, what the hell.
246
local_sha1 = repository.get_inventory_sha1(inv_id)
247
if sha1 != local_sha1:
248
raise BzrError('sha1 mismatch. For inventory id {%s}'
249
'local: %s, bundle: %s' %
250
(inv_id, local_sha1, sha1))
264
254
if len(missing) > 0:
265
255
# I don't know if this is an error yet
266
256
warning('Not all revision hashes could be validated.'
267
257
' Unable validate %d hashes' % len(missing))
268
258
mutter('Verified %d sha hashes for the bundle.' % count)
269
self._validated_revisions_against_repo = True
271
260
def _validate_inventory(self, inv, revision_id):
272
261
"""At this point we should have generated the BundleTree,
273
262
so build up an inventory, and make sure the hashes match.
265
assert inv is not None
275
267
# Now we should have a complete inventory entry.
276
268
s = serializer_v5.write_inventory_to_string(inv)
277
269
sha1 = sha_string(s)
278
270
# Target revision is the last entry in the real_revisions list
279
271
rev = self.get_revision(revision_id)
280
if rev.revision_id != revision_id:
281
raise AssertionError()
272
assert rev.revision_id == revision_id
282
273
if sha1 != rev.inventory_sha1:
283
f = open(',,bogus-inv', 'wb')
274
open(',,bogus-inv', 'wb').write(s)
288
275
warning('Inventory sha hash mismatch for revision %s. %s'
289
276
' != %s' % (revision_id, sha1, rev.inventory_sha1))
291
def _validate_revision(self, tree, revision_id):
278
def _validate_revision(self, inventory, revision_id):
292
279
"""Make sure all revision entries match their checksum."""
294
# This is a mapping from each revision id to its sha hash
281
# This is a mapping from each revision id to it's sha hash
297
284
rev = self.get_revision(revision_id)
298
285
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)
286
assert rev.revision_id == rev_info.revision_id
287
assert rev.revision_id == revision_id
288
sha1 = self._testament_sha1(rev, inventory)
304
289
if sha1 != rev_info.sha1:
305
290
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
306
291
if rev.revision_id in rev_to_sha1:
446
428
' (unrecognized action): %r' % action_line)
447
429
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
432
class BundleTree(Tree):
468
433
def __init__(self, base_tree, revision_id):
469
434
self.base_tree = base_tree
470
435
self._renamed = {} # Mapping from old_path => new_path
629
590
patch_original = None
630
591
file_patch = self.patches.get(self.id2path(file_id))
631
592
if file_patch is None:
632
if (patch_original is None and
593
if (patch_original is None and
633
594
self.get_kind(file_id) == 'directory'):
634
595
return StringIO()
635
if patch_original is None:
636
raise AssertionError("None: %s" % file_id)
596
assert patch_original is not None, "None: %s" % file_id
637
597
return patch_original
639
if file_patch.startswith('\\'):
641
'Malformed patch for %s, %r' % (file_id, file_patch))
599
assert not file_patch.startswith('\\'), \
600
'Malformed patch for %s, %r' % (file_id, file_patch)
642
601
return patched_file(file_patch, patch_original)
644
603
def get_symlink_target(self, file_id):
742
704
for path, entry in self.inventory.iter_entries():
743
705
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
707
def sorted_path_id(self):
764
709
for result in self._new_id.iteritems():