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."""
19
from __future__ import absolute_import
20
22
from cStringIO import StringIO
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
30
from bzrlib.bundle import apply_bundle
31
from bzrlib.errors import (
35
from bzrlib.inventory import (
41
from bzrlib.osutils import sha_string, pathjoin
31
42
from bzrlib.revision import Revision, NULL_REVISION
32
43
from bzrlib.testament import StrictTestament
33
44
from bzrlib.trace import mutter, warning
34
import bzrlib.transport
35
45
from bzrlib.tree import Tree
36
import bzrlib.urlutils
37
46
from bzrlib.xml5 import serializer_v5
72
81
if self.properties:
73
82
for property in self.properties:
74
83
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')
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:]
78
92
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()]
83
110
class BundleInfo(object):
84
111
"""This contains the meta information. Stuff that allows you to
85
112
recreate the revision or inventory XML.
114
def __init__(self, bundle_format=None):
115
self.bundle_format = None
88
116
self.committer = None
90
118
self.message = None
132
163
def get_base(self, revision):
133
164
revision_info = self.get_revision_info(revision.revision_id)
134
165
if revision_info.base_id is not None:
135
if revision_info.base_id == NULL_REVISION:
138
return revision_info.base_id
166
return revision_info.base_id
139
167
if len(revision.parent_ids) == 0:
140
168
# There is no base listed, and
141
169
# the lowest revision doesn't have a parent
142
170
# so this is probably against the empty tree
143
# and thus base truly is None
171
# and thus base truly is NULL_REVISION
146
174
return revision.parent_ids[-1]
170
198
def revision_tree(self, repository, revision_id, base=None):
171
199
revision = self.get_revision(revision_id)
172
200
base = self.get_base(revision)
173
assert base != revision_id
174
self._validate_references_from_repository(repository)
201
if base == revision_id:
202
raise AssertionError()
203
if not self._validated_revisions_against_repo:
204
self._validate_references_from_repository(repository)
175
205
revision_info = self.get_revision_info(revision_id)
176
206
inventory_revision_id = revision_id
177
bundle_tree = BundleTree(repository.revision_tree(base),
207
bundle_tree = BundleTree(repository.revision_tree(base),
178
208
inventory_revision_id)
179
209
self._update_tree(bundle_tree, revision_id)
181
211
inv = bundle_tree.inventory
182
212
self._validate_inventory(inv, revision_id)
183
self._validate_revision(inv, revision_id)
213
self._validate_revision(bundle_tree, revision_id)
185
215
return bundle_tree
222
252
for revision_id, sha1 in rev_to_sha.iteritems():
223
253
if repository.has_revision(revision_id):
224
testament = StrictTestament.from_revision(repository,
254
testament = StrictTestament.from_revision(repository,
226
local_sha1 = testament.as_sha1()
256
local_sha1 = self._testament_sha1_from_revision(repository,
227
258
if sha1 != local_sha1:
228
raise BzrError('sha1 mismatch. For revision id {%s}'
259
raise BzrError('sha1 mismatch. For revision id {%s}'
229
260
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
232
263
elif revision_id not in checked:
233
264
missing[revision_id] = sha1
235
for inv_id, sha1 in inv_to_sha.iteritems():
236
if repository.has_revision(inv_id):
237
# Note: branch.get_inventory_sha1() just returns the value that
238
# is stored in the revision text, and that value may be out
239
# of date. This is bogus, because that means we aren't
240
# validating the actual text, just that we wrote and read the
241
# string. But for now, what the hell.
242
local_sha1 = repository.get_inventory_sha1(inv_id)
243
if sha1 != local_sha1:
244
raise BzrError('sha1 mismatch. For inventory id {%s}'
245
'local: %s, bundle: %s' %
246
(inv_id, local_sha1, sha1))
250
266
if len(missing) > 0:
251
267
# I don't know if this is an error yet
252
268
warning('Not all revision hashes could be validated.'
253
269
' Unable validate %d hashes' % len(missing))
254
270
mutter('Verified %d sha hashes for the bundle.' % count)
271
self._validated_revisions_against_repo = True
256
273
def _validate_inventory(self, inv, revision_id):
257
274
"""At this point we should have generated the BundleTree,
258
275
so build up an inventory, and make sure the hashes match.
261
assert inv is not None
263
277
# Now we should have a complete inventory entry.
264
278
s = serializer_v5.write_inventory_to_string(inv)
265
279
sha1 = sha_string(s)
266
280
# Target revision is the last entry in the real_revisions list
267
281
rev = self.get_revision(revision_id)
268
assert rev.revision_id == revision_id
282
if rev.revision_id != revision_id:
283
raise AssertionError()
269
284
if sha1 != rev.inventory_sha1:
270
open(',,bogus-inv', 'wb').write(s)
285
f = open(',,bogus-inv', 'wb')
271
290
warning('Inventory sha hash mismatch for revision %s. %s'
272
291
' != %s' % (revision_id, sha1, rev.inventory_sha1))
274
def _validate_revision(self, inventory, revision_id):
293
def _validate_revision(self, tree, revision_id):
275
294
"""Make sure all revision entries match their checksum."""
277
# This is a mapping from each revision id to it's sha hash
296
# This is a mapping from each revision id to its sha hash
280
299
rev = self.get_revision(revision_id)
281
300
rev_info = self.get_revision_info(revision_id)
282
assert rev.revision_id == rev_info.revision_id
283
assert rev.revision_id == revision_id
284
sha1 = StrictTestament(rev, inventory).as_sha1()
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)
285
306
if sha1 != rev_info.sha1:
286
307
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
287
if rev_to_sha1.has_key(rev.revision_id):
308
if rev.revision_id in rev_to_sha1:
288
309
raise BzrError('Revision {%s} given twice in the list'
289
310
% (rev.revision_id))
290
311
rev_to_sha1[rev.revision_id] = sha1
422
448
' (unrecognized action): %r' % action_line)
423
449
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.
457
apply_bundle.install_bundle(target_repo, self)
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'
426
468
class BundleTree(Tree):
427
470
def __init__(self, base_tree, revision_id):
428
471
self.base_tree = base_tree
429
472
self._renamed = {} # Mapping from old_path => new_path
500
546
old_path = new_path
501
547
#If the new path wasn't in renamed, the old one shouldn't be in
503
if self._renamed_r.has_key(old_path):
549
if old_path in self._renamed_r:
507
553
def new_path(self, old_path):
508
554
"""Get the new_path (path in the target_tree) for the file at old_path
509
555
in the base tree.
511
assert old_path[:1] not in ('\\', '/')
557
if old_path[:1] in ('\\', '/'):
558
raise ValueError(old_path)
512
559
new_path = self._renamed_r.get(old_path)
513
560
if new_path is not None:
515
if self._renamed.has_key(new_path):
562
if new_path in self._renamed:
517
564
dirname,basename = os.path.split(old_path)
518
565
if dirname != '':
579
626
base_id = self.old_contents_id(file_id)
580
if base_id is not None:
627
if (base_id is not None and
628
base_id != self.base_tree.get_root_id()):
581
629
patch_original = self.base_tree.get_file(base_id)
583
631
patch_original = None
584
632
file_patch = self.patches.get(self.id2path(file_id))
585
633
if file_patch is None:
586
if (patch_original is None and
587
self.get_kind(file_id) == 'directory'):
634
if (patch_original is None and
635
self.kind(file_id) == 'directory'):
588
636
return StringIO()
589
assert patch_original is not None, "None: %s" % file_id
637
if patch_original is None:
638
raise AssertionError("None: %s" % file_id)
590
639
return patch_original
592
assert not file_patch.startswith('\\'), \
593
'Malformed patch for %s, %r' % (file_id, file_patch)
641
if file_patch.startswith('\\'):
643
'Malformed patch for %s, %r' % (file_id, file_patch))
594
644
return patched_file(file_patch, patch_original)
596
def get_symlink_target(self, file_id):
597
new_path = self.id2path(file_id)
646
def get_symlink_target(self, file_id, path=None):
648
path = self.id2path(file_id)
599
return self._targets[new_path]
650
return self._targets[path]
601
652
return self.base_tree.get_symlink_target(file_id)
603
def get_kind(self, file_id):
654
def kind(self, file_id):
604
655
if file_id in self._kinds:
605
656
return self._kinds[file_id]
606
return self.base_tree.inventory[file_id].kind
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)
608
666
def is_executable(self, file_id):
609
667
path = self.id2path(file_id)
610
668
if path in self._executable:
611
669
return self._executable[path]
613
return self.base_tree.inventory[file_id].executable
671
return self.base_tree.is_executable(file_id)
615
673
def get_last_changed(self, file_id):
616
674
path = self.id2path(file_id)
617
675
if path in self._last_changed:
618
676
return self._last_changed[path]
619
return self.base_tree.inventory[file_id].revision
677
return self.base_tree.get_file_revision(file_id)
621
679
def get_size_and_sha1(self, file_id):
622
680
"""Return the size and sha1 hash of the given file id.
629
687
if new_path not in self.patches:
630
688
# If the entry does not have a patch, then the
631
689
# contents must be the same as in the base_tree
632
ie = self.base_tree.inventory[file_id]
633
if ie.text_size is None:
634
return ie.text_size, ie.text_sha1
635
return int(ie.text_size), ie.text_sha1
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
636
693
fileobj = self.get_file(file_id)
637
694
content = fileobj.read()
638
695
return len(content), sha_string(content)
643
700
This need to be called before ever accessing self.inventory
645
702
from os.path import dirname, basename
647
assert self.base_tree is not None
648
base_inv = self.base_tree.inventory
649
root_id = base_inv.root.file_id
651
# New inventories have a unique root_id
652
inv = Inventory(root_id, self.revision_id)
654
inv = Inventory(revision_id=self.revision_id)
703
inv = Inventory(None, self.revision_id)
656
705
def add_entry(file_id):
657
706
path = self.id2path(file_id)
660
parent_path = dirname(path)
661
if parent_path == u'':
712
parent_path = dirname(path)
664
713
parent_id = self.path2id(parent_path)
666
kind = self.get_kind(file_id)
715
kind = self.kind(file_id)
667
716
revision_id = self.get_last_changed(file_id)
669
718
name = basename(path)
674
723
ie.executable = self.is_executable(file_id)
675
724
elif kind == 'symlink':
676
725
ie = InventoryLink(file_id, name, parent_id)
677
ie.symlink_target = self.get_symlink_target(file_id)
726
ie.symlink_target = self.get_symlink_target(file_id, path)
678
727
ie.revision = revision_id
680
if kind in ('directory', 'symlink'):
681
ie.text_size, ie.text_sha1 = None, None
683
730
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
684
if (ie.text_size is None) and (kind == 'file'):
685
raise BzrError('Got a text_size of None for file_id %r' % file_id)
731
if ie.text_size is None:
733
'Got a text_size of None for file_id %r' % file_id)
688
736
sorted_entries = self.sorted_path_id()
689
737
for path, file_id in sorted_entries:
690
if file_id == inv.root.file_id:
692
738
add_entry(file_id)
700
746
# at that instant
701
747
inventory = property(_get_inventory)
704
for path, entry in self.inventory.iter_entries():
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
707
772
def sorted_path_id(self):
709
774
for result in self._new_id.iteritems():
710
775
paths.append(result)
711
for id in self.base_tree:
776
for id in self.base_tree.all_file_ids():
712
777
path = self.id2path(id)