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