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
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
31
40
from bzrlib.revision import Revision, NULL_REVISION
32
41
from bzrlib.testament import StrictTestament
33
42
from bzrlib.trace import mutter, warning
34
import bzrlib.transport
35
43
from bzrlib.tree import Tree
36
import bzrlib.urlutils
37
44
from bzrlib.xml5 import serializer_v5
72
79
if self.properties:
73
80
for property in self.properties:
74
81
key_end = property.find(': ')
75
assert key_end is not None
76
key = property[:key_end].encode('utf-8')
77
value = property[key_end+2:].encode('utf-8')
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
90
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()]
83
108
class BundleInfo(object):
84
109
"""This contains the meta information. Stuff that allows you to
85
110
recreate the revision or inventory XML.
112
def __init__(self, bundle_format=None):
113
self.bundle_format = None
88
114
self.committer = None
90
116
self.message = None
132
161
def get_base(self, revision):
133
162
revision_info = self.get_revision_info(revision.revision_id)
134
163
if revision_info.base_id is not None:
135
if revision_info.base_id == NULL_REVISION:
138
return revision_info.base_id
164
return revision_info.base_id
139
165
if len(revision.parent_ids) == 0:
140
166
# There is no base listed, and
141
167
# the lowest revision doesn't have a parent
142
168
# so this is probably against the empty tree
143
# and thus base truly is None
169
# and thus base truly is NULL_REVISION
146
172
return revision.parent_ids[-1]
170
196
def revision_tree(self, repository, revision_id, base=None):
171
197
revision = self.get_revision(revision_id)
172
198
base = self.get_base(revision)
173
assert base != revision_id
174
self._validate_references_from_repository(repository)
199
if base == revision_id:
200
raise AssertionError()
201
if not self._validated_revisions_against_repo:
202
self._validate_references_from_repository(repository)
175
203
revision_info = self.get_revision_info(revision_id)
176
204
inventory_revision_id = revision_id
177
bundle_tree = BundleTree(repository.revision_tree(base),
205
bundle_tree = BundleTree(repository.revision_tree(base),
178
206
inventory_revision_id)
179
207
self._update_tree(bundle_tree, revision_id)
181
209
inv = bundle_tree.inventory
182
210
self._validate_inventory(inv, revision_id)
183
self._validate_revision(inv, revision_id)
211
self._validate_revision(bundle_tree, revision_id)
185
213
return bundle_tree
222
250
for revision_id, sha1 in rev_to_sha.iteritems():
223
251
if repository.has_revision(revision_id):
224
testament = StrictTestament.from_revision(repository,
252
testament = StrictTestament.from_revision(repository,
226
254
local_sha1 = self._testament_sha1_from_revision(repository,
228
256
if sha1 != local_sha1:
229
raise BzrError('sha1 mismatch. For revision id {%s}'
257
raise BzrError('sha1 mismatch. For revision id {%s}'
230
258
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
233
261
elif revision_id not in checked:
234
262
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))
251
264
if len(missing) > 0:
252
265
# I don't know if this is an error yet
253
266
warning('Not all revision hashes could be validated.'
254
267
' Unable validate %d hashes' % len(missing))
255
268
mutter('Verified %d sha hashes for the bundle.' % count)
269
self._validated_revisions_against_repo = True
257
271
def _validate_inventory(self, inv, revision_id):
258
272
"""At this point we should have generated the BundleTree,
259
273
so build up an inventory, and make sure the hashes match.
262
assert inv is not None
264
275
# Now we should have a complete inventory entry.
265
276
s = serializer_v5.write_inventory_to_string(inv)
266
277
sha1 = sha_string(s)
267
278
# Target revision is the last entry in the real_revisions list
268
279
rev = self.get_revision(revision_id)
269
assert rev.revision_id == revision_id
280
if rev.revision_id != revision_id:
281
raise AssertionError()
270
282
if sha1 != rev.inventory_sha1:
271
open(',,bogus-inv', 'wb').write(s)
283
f = open(',,bogus-inv', 'wb')
272
288
warning('Inventory sha hash mismatch for revision %s. %s'
273
289
' != %s' % (revision_id, sha1, rev.inventory_sha1))
275
def _validate_revision(self, inventory, revision_id):
291
def _validate_revision(self, tree, revision_id):
276
292
"""Make sure all revision entries match their checksum."""
278
# This is a mapping from each revision id to it's sha hash
294
# This is a mapping from each revision id to its sha hash
281
297
rev = self.get_revision(revision_id)
282
298
rev_info = self.get_revision_info(revision_id)
283
assert rev.revision_id == rev_info.revision_id
284
assert rev.revision_id == revision_id
285
sha1 = self._testament_sha1(rev, inventory)
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
304
if sha1 != rev_info.sha1:
287
305
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
288
306
if rev.revision_id in rev_to_sha1:
423
446
' (unrecognized action): %r' % action_line)
424
447
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'
427
466
class BundleTree(Tree):
428
468
def __init__(self, base_tree, revision_id):
429
469
self.base_tree = base_tree
430
470
self._renamed = {} # Mapping from old_path => new_path
585
629
patch_original = None
586
630
file_patch = self.patches.get(self.id2path(file_id))
587
631
if file_patch is None:
588
if (patch_original is None and
632
if (patch_original is None and
589
633
self.get_kind(file_id) == 'directory'):
590
634
return StringIO()
591
assert patch_original is not None, "None: %s" % file_id
635
if patch_original is None:
636
raise AssertionError("None: %s" % file_id)
592
637
return patch_original
594
assert not file_patch.startswith('\\'), \
595
'Malformed patch for %s, %r' % (file_id, file_patch)
639
if file_patch.startswith('\\'):
641
'Malformed patch for %s, %r' % (file_id, file_patch))
596
642
return patched_file(file_patch, patch_original)
598
644
def get_symlink_target(self, file_id):
699
742
for path, entry in self.inventory.iter_entries():
700
743
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
702
762
def sorted_path_id(self):
704
764
for result in self._new_id.iteritems():