~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
import os
22
22
import pprint
23
23
 
 
24
from bzrlib import (
 
25
    osutils,
 
26
    timestamp,
 
27
    )
24
28
import bzrlib.errors
 
29
from bzrlib.bundle import apply_bundle
25
30
from bzrlib.errors import (TestamentMismatch, BzrError, 
26
31
                           MalformedHeader, MalformedPatches, NotABundle)
27
32
from bzrlib.inventory import (Inventory, InventoryEntry,
72
77
        if self.properties:
73
78
            for property in self.properties:
74
79
                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')
 
80
                if key_end == -1:
 
81
                    if not property.endswith(':'):
 
82
                        raise ValueError(property)
 
83
                    key = str(property[:-1])
 
84
                    value = ''
 
85
                else:
 
86
                    key = str(property[:key_end])
 
87
                    value = property[key_end+2:]
78
88
                rev.properties[key] = value
79
89
 
80
90
        return rev
81
91
 
 
92
    @staticmethod
 
93
    def from_revision(revision):
 
94
        revision_info = RevisionInfo(revision.revision_id)
 
95
        date = timestamp.format_highres_date(revision.timestamp,
 
96
                                             revision.timezone)
 
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()]
 
103
        return revision_info
 
104
 
82
105
 
83
106
class BundleInfo(object):
84
107
    """This contains the meta information. Stuff that allows you to
85
108
    recreate the revision or inventory XML.
86
109
    """
87
 
    def __init__(self):
 
110
    def __init__(self, bundle_format=None):
 
111
        self.bundle_format = None
88
112
        self.committer = None
89
113
        self.date = None
90
114
        self.message = None
101
125
        self.timestamp = None
102
126
        self.timezone = None
103
127
 
 
128
        # Have we checked the repository yet?
 
129
        self._validated_revisions_against_repo = False
 
130
 
104
131
    def __str__(self):
105
132
        return pprint.pformat(self.__dict__)
106
133
 
109
136
        split up, based on the assumptions that can be made
110
137
        when information is missing.
111
138
        """
112
 
        from bzrlib.bundle.serializer import unpack_highres_date
 
139
        from bzrlib.timestamp import unpack_highres_date
113
140
        # Put in all of the guessable information.
114
141
        if not self.timestamp and self.date:
115
142
            self.timestamp, self.timezone = unpack_highres_date(self.date)
170
197
    def revision_tree(self, repository, revision_id, base=None):
171
198
        revision = self.get_revision(revision_id)
172
199
        base = self.get_base(revision)
173
 
        assert base != revision_id
174
 
        self._validate_references_from_repository(repository)
 
200
        if base == revision_id:
 
201
            raise AssertionError()
 
202
        if not self._validated_revisions_against_repo:
 
203
            self._validate_references_from_repository(repository)
175
204
        revision_info = self.get_revision_info(revision_id)
176
205
        inventory_revision_id = revision_id
177
206
        bundle_tree = BundleTree(repository.revision_tree(base), 
233
262
            elif revision_id not in checked:
234
263
                missing[revision_id] = sha1
235
264
 
236
 
        for inv_id, sha1 in inv_to_sha.iteritems():
237
 
            if repository.has_revision(inv_id):
238
 
                # Note: branch.get_inventory_sha1() just returns the value that
239
 
                # is stored in the revision text, and that value may be out
240
 
                # of date. This is bogus, because that means we aren't
241
 
                # validating the actual text, just that we wrote and read the
242
 
                # string. But for now, what the hell.
243
 
                local_sha1 = repository.get_inventory_sha1(inv_id)
244
 
                if sha1 != local_sha1:
245
 
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
246
 
                                   'local: %s, bundle: %s' % 
247
 
                                   (inv_id, local_sha1, sha1))
248
 
                else:
249
 
                    count += 1
250
 
 
251
265
        if len(missing) > 0:
252
266
            # I don't know if this is an error yet
253
267
            warning('Not all revision hashes could be validated.'
254
268
                    ' Unable validate %d hashes' % len(missing))
255
269
        mutter('Verified %d sha hashes for the bundle.' % count)
 
270
        self._validated_revisions_against_repo = True
256
271
 
257
272
    def _validate_inventory(self, inv, revision_id):
258
273
        """At this point we should have generated the BundleTree,
259
274
        so build up an inventory, and make sure the hashes match.
260
275
        """
261
 
 
262
 
        assert inv is not None
263
 
 
264
276
        # Now we should have a complete inventory entry.
265
277
        s = serializer_v5.write_inventory_to_string(inv)
266
278
        sha1 = sha_string(s)
267
279
        # Target revision is the last entry in the real_revisions list
268
280
        rev = self.get_revision(revision_id)
269
 
        assert rev.revision_id == revision_id
 
281
        if rev.revision_id != revision_id:
 
282
            raise AssertionError()
270
283
        if sha1 != rev.inventory_sha1:
271
284
            open(',,bogus-inv', 'wb').write(s)
272
285
            warning('Inventory sha hash mismatch for revision %s. %s'
280
293
        
281
294
        rev = self.get_revision(revision_id)
282
295
        rev_info = self.get_revision_info(revision_id)
283
 
        assert rev.revision_id == rev_info.revision_id
284
 
        assert rev.revision_id == revision_id
 
296
        if not (rev.revision_id == rev_info.revision_id):
 
297
            raise AssertionError()
 
298
        if not (rev.revision_id == revision_id):
 
299
            raise AssertionError()
285
300
        sha1 = self._testament_sha1(rev, inventory)
286
301
        if sha1 != rev_info.sha1:
287
302
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
299
314
 
300
315
        def get_rev_id(last_changed, path, kind):
301
316
            if last_changed is not None:
302
 
                changed_revision_id = last_changed.decode('utf-8')
 
317
                # last_changed will be a Unicode string because of how it was
 
318
                # read. Convert it back to utf8.
 
319
                changed_revision_id = osutils.safe_revision_id(last_changed,
 
320
                                                               warn=False)
303
321
            else:
304
322
                changed_revision_id = revision_id
305
323
            bundle_tree.note_last_changed(path, changed_revision_id)
316
334
                if name == 'last-changed':
317
335
                    last_changed = value
318
336
                elif name == 'executable':
319
 
                    assert value in ('yes', 'no'), value
320
337
                    val = (value == 'yes')
321
338
                    bundle_tree.note_executable(new_path, val)
322
339
                elif name == 'target':
326
343
            return last_changed, encoding
327
344
 
328
345
        def do_patch(path, lines, encoding):
329
 
            if encoding is not None:
330
 
                assert encoding == 'base64'
 
346
            if encoding == 'base64':
331
347
                patch = base64.decodestring(''.join(lines))
332
 
            else:
 
348
            elif encoding is None:
333
349
                patch =  ''.join(lines)
 
350
            else:
 
351
                raise ValueError(encoding)
334
352
            bundle_tree.note_patch(path, patch)
335
353
 
336
354
        def renamed(kind, extra, lines):
372
390
            if not info[1].startswith('file-id:'):
373
391
                raise BzrError('The file-id should follow the path for an add'
374
392
                        ': %r' % extra)
375
 
            file_id = info[1][8:]
 
393
            # This will be Unicode because of how the stream is read. Turn it
 
394
            # back into a utf8 file_id
 
395
            file_id = osutils.safe_file_id(info[1][8:], warn=False)
376
396
 
377
397
            bundle_tree.note_id(file_id, path, kind)
378
398
            # this will be overridden in extra_info if executable is specified.
423
443
                        ' (unrecognized action): %r' % action_line)
424
444
            valid_actions[action](kind, extra, lines)
425
445
 
 
446
    def install_revisions(self, target_repo, stream_input=True):
 
447
        """Install revisions and return the target revision
 
448
 
 
449
        :param target_repo: The repository to install into
 
450
        :param stream_input: Ignored by this implementation.
 
451
        """
 
452
        apply_bundle.install_bundle(target_repo, self)
 
453
        return self.target
 
454
 
 
455
    def get_merge_request(self, target_repo):
 
456
        """Provide data for performing a merge
 
457
 
 
458
        Returns suggested base, suggested target, and patch verification status
 
459
        """
 
460
        return None, self.target, 'inapplicable'
 
461
 
426
462
 
427
463
class BundleTree(Tree):
428
464
    def __init__(self, base_tree, revision_id):
446
482
 
447
483
    def note_rename(self, old_path, new_path):
448
484
        """A file/directory has been renamed from old_path => new_path"""
449
 
        assert new_path not in self._renamed
450
 
        assert old_path not in self._renamed_r
 
485
        if new_path in self._renamed:
 
486
            raise AssertionError(new_path)
 
487
        if old_path in self._renamed_r:
 
488
            raise AssertionError(old_path)
451
489
        self._renamed[new_path] = old_path
452
490
        self._renamed_r[old_path] = new_path
453
491
 
483
521
 
484
522
    def old_path(self, new_path):
485
523
        """Get the old_path (path in the base_tree) for the file at new_path"""
486
 
        assert new_path[:1] not in ('\\', '/')
 
524
        if new_path[:1] in ('\\', '/'):
 
525
            raise ValueError(new_path)
487
526
        old_path = self._renamed.get(new_path)
488
527
        if old_path is not None:
489
528
            return old_path
509
548
        """Get the new_path (path in the target_tree) for the file at old_path
510
549
        in the base tree.
511
550
        """
512
 
        assert old_path[:1] not in ('\\', '/')
 
551
        if old_path[:1] in ('\\', '/'):
 
552
            raise ValueError(old_path)
513
553
        new_path = self._renamed_r.get(old_path)
514
554
        if new_path is not None:
515
555
            return new_path
588
628
            if (patch_original is None and 
589
629
                self.get_kind(file_id) == 'directory'):
590
630
                return StringIO()
591
 
            assert patch_original is not None, "None: %s" % file_id
 
631
            if patch_original is None:
 
632
                raise AssertionError("None: %s" % file_id)
592
633
            return patch_original
593
634
 
594
 
        assert not file_patch.startswith('\\'), \
595
 
            'Malformed patch for %s, %r' % (file_id, file_patch)
 
635
        if file_patch.startswith('\\'):
 
636
            raise ValueError(
 
637
                'Malformed patch for %s, %r' % (file_id, file_patch))
596
638
        return patched_file(file_patch, patch_original)
597
639
 
598
640
    def get_symlink_target(self, file_id):
645
687
        This need to be called before ever accessing self.inventory
646
688
        """
647
689
        from os.path import dirname, basename
648
 
 
649
 
        assert self.base_tree is not None
650
690
        base_inv = self.base_tree.inventory
651
691
        inv = Inventory(None, self.revision_id)
652
692