1
# Copyright (C) 2006 Canonical Ltd
1
# Copyright (C) 2005-2010 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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."""
24
24
from bzrlib import (
27
28
import bzrlib.errors
28
29
from bzrlib.bundle import apply_bundle
29
from bzrlib.errors import (TestamentMismatch, BzrError,
30
from bzrlib.errors import (TestamentMismatch, BzrError,
30
31
MalformedHeader, MalformedPatches, NotABundle)
31
32
from bzrlib.inventory import (Inventory, InventoryEntry,
32
33
InventoryDirectory, InventoryFile,
77
78
for property in self.properties:
78
79
key_end = property.find(': ')
80
assert property.endswith(':')
81
if not property.endswith(':'):
82
raise ValueError(property)
81
83
key = str(property[:-1])
93
def from_revision(revision):
94
revision_info = RevisionInfo(revision.revision_id)
95
date = timestamp.format_highres_date(revision.timestamp,
97
revision_info.date = date
98
revision_info.timezone = revision.timezone
99
revision_info.timestamp = revision.timestamp
100
revision_info.message = revision.message.split('\n')
101
revision_info.properties = [': '.join(p) for p in
102
revision.properties.iteritems()]
91
106
class BundleInfo(object):
92
107
"""This contains the meta information. Stuff that allows you to
93
108
recreate the revision or inventory XML.
110
def __init__(self, bundle_format=None):
111
self.bundle_format = None
96
112
self.committer = None
98
114
self.message = None
143
159
def get_base(self, revision):
144
160
revision_info = self.get_revision_info(revision.revision_id)
145
161
if revision_info.base_id is not None:
146
if revision_info.base_id == NULL_REVISION:
149
return revision_info.base_id
162
return revision_info.base_id
150
163
if len(revision.parent_ids) == 0:
151
164
# There is no base listed, and
152
165
# the lowest revision doesn't have a parent
153
166
# so this is probably against the empty tree
154
# and thus base truly is None
167
# and thus base truly is NULL_REVISION
157
170
return revision.parent_ids[-1]
179
192
raise KeyError(revision_id)
181
194
def revision_tree(self, repository, revision_id, base=None):
182
revision_id = osutils.safe_revision_id(revision_id)
183
195
revision = self.get_revision(revision_id)
184
196
base = self.get_base(revision)
185
assert base != revision_id
197
if base == revision_id:
198
raise AssertionError()
186
199
if not self._validated_revisions_against_repo:
187
200
self._validate_references_from_repository(repository)
188
201
revision_info = self.get_revision_info(revision_id)
189
202
inventory_revision_id = revision_id
190
bundle_tree = BundleTree(repository.revision_tree(base),
203
bundle_tree = BundleTree(repository.revision_tree(base),
191
204
inventory_revision_id)
192
205
self._update_tree(bundle_tree, revision_id)
226
239
for rev_info in self.revisions:
227
240
checked[rev_info.revision_id] = True
228
241
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
230
243
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
231
244
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
235
248
for revision_id, sha1 in rev_to_sha.iteritems():
236
249
if repository.has_revision(revision_id):
237
testament = StrictTestament.from_revision(repository,
250
testament = StrictTestament.from_revision(repository,
239
252
local_sha1 = self._testament_sha1_from_revision(repository,
241
254
if sha1 != local_sha1:
242
raise BzrError('sha1 mismatch. For revision id {%s}'
255
raise BzrError('sha1 mismatch. For revision id {%s}'
243
256
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
246
259
elif revision_id not in checked:
247
260
missing[revision_id] = sha1
249
for inv_id, sha1 in inv_to_sha.iteritems():
250
if repository.has_revision(inv_id):
251
# Note: branch.get_inventory_sha1() just returns the value that
252
# is stored in the revision text, and that value may be out
253
# of date. This is bogus, because that means we aren't
254
# validating the actual text, just that we wrote and read the
255
# string. But for now, what the hell.
256
local_sha1 = repository.get_inventory_sha1(inv_id)
257
if sha1 != local_sha1:
258
raise BzrError('sha1 mismatch. For inventory id {%s}'
259
'local: %s, bundle: %s' %
260
(inv_id, local_sha1, sha1))
264
262
if len(missing) > 0:
265
263
# I don't know if this is an error yet
266
264
warning('Not all revision hashes could be validated.'
272
270
"""At this point we should have generated the BundleTree,
273
271
so build up an inventory, and make sure the hashes match.
276
assert inv is not None
278
273
# Now we should have a complete inventory entry.
279
274
s = serializer_v5.write_inventory_to_string(inv)
280
275
sha1 = sha_string(s)
281
276
# Target revision is the last entry in the real_revisions list
282
277
rev = self.get_revision(revision_id)
283
assert rev.revision_id == revision_id
278
if rev.revision_id != revision_id:
279
raise AssertionError()
284
280
if sha1 != rev.inventory_sha1:
285
open(',,bogus-inv', 'wb').write(s)
281
f = open(',,bogus-inv', 'wb')
286
286
warning('Inventory sha hash mismatch for revision %s. %s'
287
287
' != %s' % (revision_id, sha1, rev.inventory_sha1))
292
292
# This is a mapping from each revision id to it's sha hash
295
295
rev = self.get_revision(revision_id)
296
296
rev_info = self.get_revision_info(revision_id)
297
assert rev.revision_id == rev_info.revision_id
298
assert rev.revision_id == revision_id
297
if not (rev.revision_id == rev_info.revision_id):
298
raise AssertionError()
299
if not (rev.revision_id == revision_id):
300
raise AssertionError()
299
301
sha1 = self._testament_sha1(rev, inventory)
300
302
if sha1 != rev_info.sha1:
301
303
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
330
332
name, value = info_item.split(':', 1)
331
333
except ValueError:
332
raise 'Value %r has no colon' % info_item
334
raise ValueError('Value %r has no colon' % info_item)
333
335
if name == 'last-changed':
334
336
last_changed = value
335
337
elif name == 'executable':
336
assert value in ('yes', 'no'), value
337
338
val = (value == 'yes')
338
339
bundle_tree.note_executable(new_path, val)
339
340
elif name == 'target':
343
344
return last_changed, encoding
345
346
def do_patch(path, lines, encoding):
346
if encoding is not None:
347
assert encoding == 'base64'
347
if encoding == 'base64':
348
348
patch = base64.decodestring(''.join(lines))
349
elif encoding is None:
350
350
patch = ''.join(lines)
352
raise ValueError(encoding)
351
353
bundle_tree.note_patch(path, patch)
353
355
def renamed(kind, extra, lines):
442
444
' (unrecognized action): %r' % action_line)
443
445
valid_actions[action](kind, extra, lines)
445
def install_revisions(self, target_repo):
446
"""Install revisions and return the target revision"""
447
def install_revisions(self, target_repo, stream_input=True):
448
"""Install revisions and return the target revision
450
:param target_repo: The repository to install into
451
:param stream_input: Ignored by this implementation.
447
453
apply_bundle.install_bundle(target_repo, self)
448
454
return self.target
456
def get_merge_request(self, target_repo):
457
"""Provide data for performing a merge
459
Returns suggested base, suggested target, and patch verification status
461
return None, self.target, 'inapplicable'
451
464
class BundleTree(Tree):
452
465
def __init__(self, base_tree, revision_id):
471
484
def note_rename(self, old_path, new_path):
472
485
"""A file/directory has been renamed from old_path => new_path"""
473
assert new_path not in self._renamed
474
assert old_path not in self._renamed_r
486
if new_path in self._renamed:
487
raise AssertionError(new_path)
488
if old_path in self._renamed_r:
489
raise AssertionError(old_path)
475
490
self._renamed[new_path] = old_path
476
491
self._renamed_r[old_path] = new_path
508
523
def old_path(self, new_path):
509
524
"""Get the old_path (path in the base_tree) for the file at new_path"""
510
assert new_path[:1] not in ('\\', '/')
525
if new_path[:1] in ('\\', '/'):
526
raise ValueError(new_path)
511
527
old_path = self._renamed.get(new_path)
512
528
if old_path is not None:
528
544
if old_path in self._renamed_r:
532
548
def new_path(self, old_path):
533
549
"""Get the new_path (path in the target_tree) for the file at old_path
534
550
in the base tree.
536
assert old_path[:1] not in ('\\', '/')
552
if old_path[:1] in ('\\', '/'):
553
raise ValueError(old_path)
537
554
new_path = self._renamed_r.get(old_path)
538
555
if new_path is not None:
593
610
new_path = self.id2path(file_id)
594
611
return self.base_tree.path2id(new_path)
596
613
def get_file(self, file_id):
597
614
"""Return a file-like object containing the new contents of the
598
615
file given by file_id.
609
626
patch_original = None
610
627
file_patch = self.patches.get(self.id2path(file_id))
611
628
if file_patch is None:
612
if (patch_original is None and
629
if (patch_original is None and
613
630
self.get_kind(file_id) == 'directory'):
614
631
return StringIO()
615
assert patch_original is not None, "None: %s" % file_id
632
if patch_original is None:
633
raise AssertionError("None: %s" % file_id)
616
634
return patch_original
618
assert not file_patch.startswith('\\'), \
619
'Malformed patch for %s, %r' % (file_id, file_patch)
636
if file_patch.startswith('\\'):
638
'Malformed patch for %s, %r' % (file_id, file_patch))
620
639
return patched_file(file_patch, patch_original)
622
641
def get_symlink_target(self, file_id):
669
688
This need to be called before ever accessing self.inventory
671
690
from os.path import dirname, basename
673
assert self.base_tree is not None
674
691
base_inv = self.base_tree.inventory
675
692
inv = Inventory(None, self.revision_id)