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
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
91
class BundleInfo(object):
111
92
"""This contains the meta information. Stuff that allows you to
112
93
recreate the revision or inventory XML.
114
def __init__(self, bundle_format=None):
115
self.bundle_format = None
116
96
self.committer = None
118
98
self.message = None
163
143
def get_base(self, revision):
164
144
revision_info = self.get_revision_info(revision.revision_id)
165
145
if revision_info.base_id is not None:
166
return revision_info.base_id
146
if revision_info.base_id == NULL_REVISION:
149
return revision_info.base_id
167
150
if len(revision.parent_ids) == 0:
168
151
# There is no base listed, and
169
152
# the lowest revision doesn't have a parent
170
153
# so this is probably against the empty tree
171
# and thus base truly is NULL_REVISION
154
# and thus base truly is None
174
157
return revision.parent_ids[-1]
196
179
raise KeyError(revision_id)
198
181
def revision_tree(self, repository, revision_id, base=None):
182
revision_id = osutils.safe_revision_id(revision_id)
199
183
revision = self.get_revision(revision_id)
200
184
base = self.get_base(revision)
201
if base == revision_id:
202
raise AssertionError()
185
assert base != revision_id
203
186
if not self._validated_revisions_against_repo:
204
187
self._validate_references_from_repository(repository)
205
188
revision_info = self.get_revision_info(revision_id)
206
189
inventory_revision_id = revision_id
207
bundle_tree = BundleTree(repository.revision_tree(base),
190
bundle_tree = BundleTree(repository.revision_tree(base),
208
191
inventory_revision_id)
209
192
self._update_tree(bundle_tree, revision_id)
211
194
inv = bundle_tree.inventory
212
195
self._validate_inventory(inv, revision_id)
213
self._validate_revision(bundle_tree, revision_id)
196
self._validate_revision(inv, revision_id)
215
198
return bundle_tree
243
226
for rev_info in self.revisions:
244
227
checked[rev_info.revision_id] = True
245
228
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
247
230
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
248
231
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
252
235
for revision_id, sha1 in rev_to_sha.iteritems():
253
236
if repository.has_revision(revision_id):
254
testament = StrictTestament.from_revision(repository,
237
testament = StrictTestament.from_revision(repository,
256
239
local_sha1 = self._testament_sha1_from_revision(repository,
258
241
if sha1 != local_sha1:
259
raise BzrError('sha1 mismatch. For revision id {%s}'
242
raise BzrError('sha1 mismatch. For revision id {%s}'
260
243
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
263
246
elif revision_id not in checked:
264
247
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))
266
264
if len(missing) > 0:
267
265
# I don't know if this is an error yet
268
266
warning('Not all revision hashes could be validated.'
274
272
"""At this point we should have generated the BundleTree,
275
273
so build up an inventory, and make sure the hashes match.
276
assert inv is not None
277
278
# Now we should have a complete inventory entry.
278
279
s = serializer_v5.write_inventory_to_string(inv)
279
280
sha1 = sha_string(s)
280
281
# Target revision is the last entry in the real_revisions list
281
282
rev = self.get_revision(revision_id)
282
if rev.revision_id != revision_id:
283
raise AssertionError()
283
assert rev.revision_id == revision_id
284
284
if sha1 != rev.inventory_sha1:
285
f = open(',,bogus-inv', 'wb')
285
open(',,bogus-inv', 'wb').write(s)
290
286
warning('Inventory sha hash mismatch for revision %s. %s'
291
287
' != %s' % (revision_id, sha1, rev.inventory_sha1))
293
def _validate_revision(self, tree, revision_id):
289
def _validate_revision(self, inventory, revision_id):
294
290
"""Make sure all revision entries match their checksum."""
296
# This is a mapping from each revision id to its sha hash
292
# This is a mapping from each revision id to it's sha hash
299
295
rev = self.get_revision(revision_id)
300
296
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)
297
assert rev.revision_id == rev_info.revision_id
298
assert rev.revision_id == revision_id
299
sha1 = self._testament_sha1(rev, inventory)
306
300
if sha1 != rev_info.sha1:
307
301
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
308
302
if rev.revision_id in rev_to_sha1:
336
330
name, value = info_item.split(':', 1)
337
331
except ValueError:
338
raise ValueError('Value %r has no colon' % info_item)
332
raise 'Value %r has no colon' % info_item
339
333
if name == 'last-changed':
340
334
last_changed = value
341
335
elif name == 'executable':
336
assert value in ('yes', 'no'), value
342
337
val = (value == 'yes')
343
338
bundle_tree.note_executable(new_path, val)
344
339
elif name == 'target':
348
343
return last_changed, encoding
350
345
def do_patch(path, lines, encoding):
351
if encoding == 'base64':
346
if encoding is not None:
347
assert encoding == 'base64'
352
348
patch = base64.decodestring(''.join(lines))
353
elif encoding is None:
354
350
patch = ''.join(lines)
356
raise ValueError(encoding)
357
351
bundle_tree.note_patch(path, patch)
359
353
def renamed(kind, extra, lines):
448
442
' (unrecognized action): %r' % action_line)
449
443
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.
445
def install_revisions(self, target_repo):
446
"""Install revisions and return the target revision"""
457
447
apply_bundle.install_bundle(target_repo, self)
458
448
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
451
class BundleTree(Tree):
470
452
def __init__(self, base_tree, revision_id):
471
453
self.base_tree = base_tree
472
454
self._renamed = {} # Mapping from old_path => new_path
489
471
def note_rename(self, old_path, new_path):
490
472
"""A file/directory has been renamed from old_path => new_path"""
491
if new_path in self._renamed:
492
raise AssertionError(new_path)
493
if old_path in self._renamed_r:
494
raise AssertionError(old_path)
473
assert new_path not in self._renamed
474
assert old_path not in self._renamed_r
495
475
self._renamed[new_path] = old_path
496
476
self._renamed_r[old_path] = new_path
549
528
if old_path in self._renamed_r:
553
532
def new_path(self, old_path):
554
533
"""Get the new_path (path in the target_tree) for the file at old_path
555
534
in the base tree.
557
if old_path[:1] in ('\\', '/'):
558
raise ValueError(old_path)
536
assert old_path[:1] not in ('\\', '/')
559
537
new_path = self._renamed_r.get(old_path)
560
538
if new_path is not None:
590
565
if old_path in self.deleted:
592
return self.base_tree.path2id(old_path)
567
if getattr(self.base_tree, 'path2id', None) is not None:
568
return self.base_tree.path2id(old_path)
570
return self.base_tree.inventory.path2id(old_path)
594
572
def id2path(self, file_id):
595
573
"""Return the new path in the target tree of the file with id file_id"""
626
604
base_id = self.old_contents_id(file_id)
627
605
if (base_id is not None and
628
base_id != self.base_tree.get_root_id()):
606
base_id != self.base_tree.inventory.root.file_id):
629
607
patch_original = self.base_tree.get_file(base_id)
631
609
patch_original = None
632
610
file_patch = self.patches.get(self.id2path(file_id))
633
611
if file_patch is None:
634
if (patch_original is None and
635
self.kind(file_id) == 'directory'):
612
if (patch_original is None and
613
self.get_kind(file_id) == 'directory'):
636
614
return StringIO()
637
if patch_original is None:
638
raise AssertionError("None: %s" % file_id)
615
assert patch_original is not None, "None: %s" % file_id
639
616
return patch_original
641
if file_patch.startswith('\\'):
643
'Malformed patch for %s, %r' % (file_id, file_patch))
618
assert not file_patch.startswith('\\'), \
619
'Malformed patch for %s, %r' % (file_id, file_patch)
644
620
return patched_file(file_patch, patch_original)
646
def get_symlink_target(self, file_id, path=None):
648
path = self.id2path(file_id)
622
def get_symlink_target(self, file_id):
623
new_path = self.id2path(file_id)
650
return self._targets[path]
625
return self._targets[new_path]
652
627
return self.base_tree.get_symlink_target(file_id)
654
def kind(self, file_id):
629
def get_kind(self, file_id):
655
630
if file_id in self._kinds:
656
631
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)
632
return self.base_tree.inventory[file_id].kind
666
634
def is_executable(self, file_id):
667
635
path = self.id2path(file_id)
668
636
if path in self._executable:
669
637
return self._executable[path]
671
return self.base_tree.is_executable(file_id)
639
return self.base_tree.inventory[file_id].executable
673
641
def get_last_changed(self, file_id):
674
642
path = self.id2path(file_id)
675
643
if path in self._last_changed:
676
644
return self._last_changed[path]
677
return self.base_tree.get_file_revision(file_id)
645
return self.base_tree.inventory[file_id].revision
679
647
def get_size_and_sha1(self, file_id):
680
648
"""Return the size and sha1 hash of the given file id.
687
655
if new_path not in self.patches:
688
656
# If the entry does not have a patch, then the
689
657
# 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
658
ie = self.base_tree.inventory[file_id]
659
if ie.text_size is None:
660
return ie.text_size, ie.text_sha1
661
return int(ie.text_size), ie.text_sha1
693
662
fileobj = self.get_file(file_id)
694
663
content = fileobj.read()
695
664
return len(content), sha_string(content)
723
695
ie.executable = self.is_executable(file_id)
724
696
elif kind == 'symlink':
725
697
ie = InventoryLink(file_id, name, parent_id)
726
ie.symlink_target = self.get_symlink_target(file_id, path)
698
ie.symlink_target = self.get_symlink_target(file_id)
727
699
ie.revision = revision_id
701
if kind in ('directory', 'symlink'):
702
ie.text_size, ie.text_sha1 = None, None
730
704
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)
705
if (ie.text_size is None) and (kind == 'file'):
706
raise BzrError('Got a text_size of None for file_id %r' % file_id)
736
709
sorted_entries = self.sorted_path_id()
746
719
# at that instant
747
720
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
723
for path, entry in self.inventory.iter_entries():
772
726
def sorted_path_id(self):
774
728
for result in self._new_id.iteritems():
775
729
paths.append(result)
776
for id in self.base_tree.all_file_ids():
730
for id in self.base_tree:
777
731
path = self.id2path(id)