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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
19
from __future__ import absolute_import
22
20
from cStringIO import StringIO
26
24
from bzrlib import (
30
28
from bzrlib.bundle import apply_bundle
31
from bzrlib.errors import (
35
from bzrlib.inventory import (
41
from bzrlib.osutils import sha_string, pathjoin
29
from bzrlib.errors import (TestamentMismatch, BzrError,
30
MalformedHeader, MalformedPatches, NotABundle)
31
from bzrlib.inventory import (Inventory, InventoryEntry,
32
InventoryDirectory, InventoryFile,
34
from bzrlib.osutils import sha_file, sha_string, pathjoin
42
35
from bzrlib.revision import Revision, NULL_REVISION
43
36
from bzrlib.testament import StrictTestament
44
37
from bzrlib.trace import mutter, warning
38
import bzrlib.transport
45
39
from bzrlib.tree import Tree
40
import bzrlib.urlutils
46
41
from bzrlib.xml5 import serializer_v5
81
76
if self.properties:
82
77
for property in self.properties:
83
78
key_end = property.find(': ')
85
if not property.endswith(':'):
86
raise ValueError(property)
87
key = str(property[:-1])
90
key = str(property[:key_end])
91
value = property[key_end+2:]
79
assert key_end is not None
80
key = property[:key_end].encode('utf-8')
81
value = property[key_end+2:].encode('utf-8')
92
82
rev.properties[key] = value
97
def from_revision(revision):
98
revision_info = RevisionInfo(revision.revision_id)
99
date = timestamp.format_highres_date(revision.timestamp,
101
revision_info.date = date
102
revision_info.timezone = revision.timezone
103
revision_info.timestamp = revision.timestamp
104
revision_info.message = revision.message.split('\n')
105
revision_info.properties = [': '.join(p) for p in
106
revision.properties.iteritems()]
110
87
class BundleInfo(object):
111
88
"""This contains the meta information. Stuff that allows you to
112
89
recreate the revision or inventory XML.
114
def __init__(self, bundle_format=None):
115
self.bundle_format = None
116
92
self.committer = None
118
94
self.message = None
163
136
def get_base(self, revision):
164
137
revision_info = self.get_revision_info(revision.revision_id)
165
138
if revision_info.base_id is not None:
166
return revision_info.base_id
139
if revision_info.base_id == NULL_REVISION:
142
return revision_info.base_id
167
143
if len(revision.parent_ids) == 0:
168
144
# There is no base listed, and
169
145
# the lowest revision doesn't have a parent
170
146
# so this is probably against the empty tree
171
# and thus base truly is NULL_REVISION
147
# and thus base truly is None
174
150
return revision.parent_ids[-1]
196
172
raise KeyError(revision_id)
198
174
def revision_tree(self, repository, revision_id, base=None):
175
revision_id = osutils.safe_revision_id(revision_id)
199
176
revision = self.get_revision(revision_id)
200
177
base = self.get_base(revision)
201
if base == revision_id:
202
raise AssertionError()
203
if not self._validated_revisions_against_repo:
204
self._validate_references_from_repository(repository)
178
assert base != revision_id
179
self._validate_references_from_repository(repository)
205
180
revision_info = self.get_revision_info(revision_id)
206
181
inventory_revision_id = revision_id
207
bundle_tree = BundleTree(repository.revision_tree(base),
182
bundle_tree = BundleTree(repository.revision_tree(base),
208
183
inventory_revision_id)
209
184
self._update_tree(bundle_tree, revision_id)
211
186
inv = bundle_tree.inventory
212
187
self._validate_inventory(inv, revision_id)
213
self._validate_revision(bundle_tree, revision_id)
188
self._validate_revision(inv, revision_id)
215
190
return bundle_tree
252
227
for revision_id, sha1 in rev_to_sha.iteritems():
253
228
if repository.has_revision(revision_id):
254
testament = StrictTestament.from_revision(repository,
229
testament = StrictTestament.from_revision(repository,
256
231
local_sha1 = self._testament_sha1_from_revision(repository,
258
233
if sha1 != local_sha1:
259
raise BzrError('sha1 mismatch. For revision id {%s}'
234
raise BzrError('sha1 mismatch. For revision id {%s}'
260
235
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
263
238
elif revision_id not in checked:
264
239
missing[revision_id] = sha1
241
for inv_id, sha1 in inv_to_sha.iteritems():
242
if repository.has_revision(inv_id):
243
# Note: branch.get_inventory_sha1() just returns the value that
244
# is stored in the revision text, and that value may be out
245
# of date. This is bogus, because that means we aren't
246
# validating the actual text, just that we wrote and read the
247
# string. But for now, what the hell.
248
local_sha1 = repository.get_inventory_sha1(inv_id)
249
if sha1 != local_sha1:
250
raise BzrError('sha1 mismatch. For inventory id {%s}'
251
'local: %s, bundle: %s' %
252
(inv_id, local_sha1, sha1))
266
256
if len(missing) > 0:
267
257
# I don't know if this is an error yet
268
258
warning('Not all revision hashes could be validated.'
269
259
' Unable validate %d hashes' % len(missing))
270
260
mutter('Verified %d sha hashes for the bundle.' % count)
271
self._validated_revisions_against_repo = True
273
262
def _validate_inventory(self, inv, revision_id):
274
263
"""At this point we should have generated the BundleTree,
275
264
so build up an inventory, and make sure the hashes match.
267
assert inv is not None
277
269
# Now we should have a complete inventory entry.
278
270
s = serializer_v5.write_inventory_to_string(inv)
279
271
sha1 = sha_string(s)
280
272
# Target revision is the last entry in the real_revisions list
281
273
rev = self.get_revision(revision_id)
282
if rev.revision_id != revision_id:
283
raise AssertionError()
274
assert rev.revision_id == revision_id
284
275
if sha1 != rev.inventory_sha1:
285
f = open(',,bogus-inv', 'wb')
276
open(',,bogus-inv', 'wb').write(s)
290
277
warning('Inventory sha hash mismatch for revision %s. %s'
291
278
' != %s' % (revision_id, sha1, rev.inventory_sha1))
293
def _validate_revision(self, tree, revision_id):
280
def _validate_revision(self, inventory, revision_id):
294
281
"""Make sure all revision entries match their checksum."""
296
# This is a mapping from each revision id to its sha hash
283
# This is a mapping from each revision id to it's sha hash
299
286
rev = self.get_revision(revision_id)
300
287
rev_info = self.get_revision_info(revision_id)
301
if not (rev.revision_id == rev_info.revision_id):
302
raise AssertionError()
303
if not (rev.revision_id == revision_id):
304
raise AssertionError()
305
sha1 = self._testament_sha1(rev, tree)
288
assert rev.revision_id == rev_info.revision_id
289
assert rev.revision_id == revision_id
290
sha1 = self._testament_sha1(rev, inventory)
306
291
if sha1 != rev_info.sha1:
307
292
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
308
293
if rev.revision_id in rev_to_sha1:
448
433
' (unrecognized action): %r' % action_line)
449
434
valid_actions[action](kind, extra, lines)
451
def install_revisions(self, target_repo, stream_input=True):
452
"""Install revisions and return the target revision
454
:param target_repo: The repository to install into
455
:param stream_input: Ignored by this implementation.
436
def install_revisions(self, target_repo):
437
"""Install revisions and return the target revision"""
457
438
apply_bundle.install_bundle(target_repo, self)
458
439
return self.target
460
def get_merge_request(self, target_repo):
461
"""Provide data for performing a merge
463
Returns suggested base, suggested target, and patch verification status
465
return None, self.target, 'inapplicable'
468
442
class BundleTree(Tree):
470
443
def __init__(self, base_tree, revision_id):
471
444
self.base_tree = base_tree
472
445
self._renamed = {} # Mapping from old_path => new_path
626
595
base_id = self.old_contents_id(file_id)
627
596
if (base_id is not None and
628
base_id != self.base_tree.get_root_id()):
597
base_id != self.base_tree.inventory.root.file_id):
629
598
patch_original = self.base_tree.get_file(base_id)
631
600
patch_original = None
632
601
file_patch = self.patches.get(self.id2path(file_id))
633
602
if file_patch is None:
634
if (patch_original is None and
635
self.kind(file_id) == 'directory'):
603
if (patch_original is None and
604
self.get_kind(file_id) == 'directory'):
636
605
return StringIO()
637
if patch_original is None:
638
raise AssertionError("None: %s" % file_id)
606
assert patch_original is not None, "None: %s" % file_id
639
607
return patch_original
641
if file_patch.startswith('\\'):
643
'Malformed patch for %s, %r' % (file_id, file_patch))
609
assert not file_patch.startswith('\\'), \
610
'Malformed patch for %s, %r' % (file_id, file_patch)
644
611
return patched_file(file_patch, patch_original)
646
def get_symlink_target(self, file_id, path=None):
648
path = self.id2path(file_id)
613
def get_symlink_target(self, file_id):
614
new_path = self.id2path(file_id)
650
return self._targets[path]
616
return self._targets[new_path]
652
618
return self.base_tree.get_symlink_target(file_id)
654
def kind(self, file_id):
620
def get_kind(self, file_id):
655
621
if file_id in self._kinds:
656
622
return self._kinds[file_id]
657
return self.base_tree.kind(file_id)
659
def get_file_revision(self, file_id):
660
path = self.id2path(file_id)
661
if path in self._last_changed:
662
return self._last_changed[path]
664
return self.base_tree.get_file_revision(file_id)
623
return self.base_tree.inventory[file_id].kind
666
625
def is_executable(self, file_id):
667
626
path = self.id2path(file_id)
668
627
if path in self._executable:
669
628
return self._executable[path]
671
return self.base_tree.is_executable(file_id)
630
return self.base_tree.inventory[file_id].executable
673
632
def get_last_changed(self, file_id):
674
633
path = self.id2path(file_id)
675
634
if path in self._last_changed:
676
635
return self._last_changed[path]
677
return self.base_tree.get_file_revision(file_id)
636
return self.base_tree.inventory[file_id].revision
679
638
def get_size_and_sha1(self, file_id):
680
639
"""Return the size and sha1 hash of the given file id.
687
646
if new_path not in self.patches:
688
647
# If the entry does not have a patch, then the
689
648
# contents must be the same as in the base_tree
690
text_size = self.base_tree.get_file_size(file_id)
691
text_sha1 = self.base_tree.get_file_sha1(file_id)
692
return text_size, text_sha1
649
ie = self.base_tree.inventory[file_id]
650
if ie.text_size is None:
651
return ie.text_size, ie.text_sha1
652
return int(ie.text_size), ie.text_sha1
693
653
fileobj = self.get_file(file_id)
694
654
content = fileobj.read()
695
655
return len(content), sha_string(content)
723
686
ie.executable = self.is_executable(file_id)
724
687
elif kind == 'symlink':
725
688
ie = InventoryLink(file_id, name, parent_id)
726
ie.symlink_target = self.get_symlink_target(file_id, path)
689
ie.symlink_target = self.get_symlink_target(file_id)
727
690
ie.revision = revision_id
692
if kind in ('directory', 'symlink'):
693
ie.text_size, ie.text_sha1 = None, None
730
695
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
731
if ie.text_size is None:
733
'Got a text_size of None for file_id %r' % file_id)
696
if (ie.text_size is None) and (kind == 'file'):
697
raise BzrError('Got a text_size of None for file_id %r' % file_id)
736
700
sorted_entries = self.sorted_path_id()
746
710
# at that instant
747
711
inventory = property(_get_inventory)
749
root_inventory = property(_get_inventory)
751
def all_file_ids(self):
753
[entry.file_id for path, entry in self.inventory.iter_entries()])
755
def list_files(self, include_root=False, from_dir=None, recursive=True):
756
# The only files returned by this are those from the version
761
from_dir_id = inv.path2id(from_dir)
762
if from_dir_id is None:
763
# Directory not versioned
765
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
766
if inv.root is not None and not include_root and from_dir is None:
767
# skip the root for compatability with the current apis.
769
for path, entry in entries:
770
yield path, 'V', entry.kind, entry.file_id, entry
714
for path, entry in self.inventory.iter_entries():
772
717
def sorted_path_id(self):
774
719
for result in self._new_id.iteritems():
775
720
paths.append(result)
776
for id in self.base_tree.all_file_ids():
721
for id in self.base_tree:
777
722
path = self.id2path(id)