~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: Martin Pool
  • Date: 2007-06-25 05:12:57 UTC
  • mto: This revision was merged to the branch mainline in revision 2546.
  • Revision ID: mbp@sourcefrog.net-20070625051257-fpzcv067ye6a341c
Fix typo and remove version number from README

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
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
16
16
 
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
18
18
 
23
23
 
24
24
from bzrlib import (
25
25
    osutils,
26
 
    timestamp,
27
26
    )
28
27
import bzrlib.errors
29
28
from bzrlib.bundle import apply_bundle
30
 
from bzrlib.errors import (TestamentMismatch, BzrError,
 
29
from bzrlib.errors import (TestamentMismatch, BzrError, 
31
30
                           MalformedHeader, MalformedPatches, NotABundle)
32
31
from bzrlib.inventory import (Inventory, InventoryEntry,
33
32
                              InventoryDirectory, InventoryFile,
78
77
            for property in self.properties:
79
78
                key_end = property.find(': ')
80
79
                if key_end == -1:
81
 
                    if not property.endswith(':'):
82
 
                        raise ValueError(property)
 
80
                    assert property.endswith(':')
83
81
                    key = str(property[:-1])
84
82
                    value = ''
85
83
                else:
89
87
 
90
88
        return rev
91
89
 
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
 
 
105
90
 
106
91
class BundleInfo(object):
107
92
    """This contains the meta information. Stuff that allows you to
108
93
    recreate the revision or inventory XML.
109
94
    """
110
 
    def __init__(self, bundle_format=None):
111
 
        self.bundle_format = None
 
95
    def __init__(self):
112
96
        self.committer = None
113
97
        self.date = None
114
98
        self.message = None
159
143
    def get_base(self, revision):
160
144
        revision_info = self.get_revision_info(revision.revision_id)
161
145
        if revision_info.base_id is not None:
162
 
            return revision_info.base_id
 
146
            if revision_info.base_id == NULL_REVISION:
 
147
                return None
 
148
            else:
 
149
                return revision_info.base_id
163
150
        if len(revision.parent_ids) == 0:
164
151
            # There is no base listed, and
165
152
            # the lowest revision doesn't have a parent
166
153
            # so this is probably against the empty tree
167
 
            # and thus base truly is NULL_REVISION
168
 
            return NULL_REVISION
 
154
            # and thus base truly is None
 
155
            return None
169
156
        else:
170
157
            return revision.parent_ids[-1]
171
158
 
192
179
        raise KeyError(revision_id)
193
180
 
194
181
    def revision_tree(self, repository, revision_id, base=None):
 
182
        revision_id = osutils.safe_revision_id(revision_id)
195
183
        revision = self.get_revision(revision_id)
196
184
        base = self.get_base(revision)
197
 
        if base == revision_id:
198
 
            raise AssertionError()
 
185
        assert base != revision_id
199
186
        if not self._validated_revisions_against_repo:
200
187
            self._validate_references_from_repository(repository)
201
188
        revision_info = self.get_revision_info(revision_id)
202
189
        inventory_revision_id = revision_id
203
 
        bundle_tree = BundleTree(repository.revision_tree(base),
 
190
        bundle_tree = BundleTree(repository.revision_tree(base), 
204
191
                                  inventory_revision_id)
205
192
        self._update_tree(bundle_tree, revision_id)
206
193
 
239
226
        for rev_info in self.revisions:
240
227
            checked[rev_info.revision_id] = True
241
228
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
242
 
 
 
229
                
243
230
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
244
231
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
245
232
 
247
234
        missing = {}
248
235
        for revision_id, sha1 in rev_to_sha.iteritems():
249
236
            if repository.has_revision(revision_id):
250
 
                testament = StrictTestament.from_revision(repository,
 
237
                testament = StrictTestament.from_revision(repository, 
251
238
                                                          revision_id)
252
239
                local_sha1 = self._testament_sha1_from_revision(repository,
253
240
                                                                revision_id)
254
241
                if sha1 != local_sha1:
255
 
                    raise BzrError('sha1 mismatch. For revision id {%s}'
 
242
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
256
243
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
257
244
                else:
258
245
                    count += 1
259
246
            elif revision_id not in checked:
260
247
                missing[revision_id] = sha1
261
248
 
 
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))
 
261
                else:
 
262
                    count += 1
 
263
 
262
264
        if len(missing) > 0:
263
265
            # I don't know if this is an error yet
264
266
            warning('Not all revision hashes could be validated.'
270
272
        """At this point we should have generated the BundleTree,
271
273
        so build up an inventory, and make sure the hashes match.
272
274
        """
 
275
 
 
276
        assert inv is not None
 
277
 
273
278
        # Now we should have a complete inventory entry.
274
279
        s = serializer_v5.write_inventory_to_string(inv)
275
280
        sha1 = sha_string(s)
276
281
        # Target revision is the last entry in the real_revisions list
277
282
        rev = self.get_revision(revision_id)
278
 
        if rev.revision_id != revision_id:
279
 
            raise AssertionError()
 
283
        assert rev.revision_id == revision_id
280
284
        if sha1 != rev.inventory_sha1:
281
 
            f = open(',,bogus-inv', 'wb')
282
 
            try:
283
 
                f.write(s)
284
 
            finally:
285
 
                f.close()
 
285
            open(',,bogus-inv', 'wb').write(s)
286
286
            warning('Inventory sha hash mismatch for revision %s. %s'
287
287
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
288
288
 
291
291
 
292
292
        # This is a mapping from each revision id to it's sha hash
293
293
        rev_to_sha1 = {}
294
 
 
 
294
        
295
295
        rev = self.get_revision(revision_id)
296
296
        rev_info = self.get_revision_info(revision_id)
297
 
        if not (rev.revision_id == rev_info.revision_id):
298
 
            raise AssertionError()
299
 
        if not (rev.revision_id == revision_id):
300
 
            raise AssertionError()
 
297
        assert rev.revision_id == rev_info.revision_id
 
298
        assert rev.revision_id == revision_id
301
299
        sha1 = self._testament_sha1(rev, inventory)
302
300
        if sha1 != rev_info.sha1:
303
301
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
331
329
                try:
332
330
                    name, value = info_item.split(':', 1)
333
331
                except ValueError:
334
 
                    raise ValueError('Value %r has no colon' % info_item)
 
332
                    raise 'Value %r has no colon' % info_item
335
333
                if name == 'last-changed':
336
334
                    last_changed = value
337
335
                elif name == 'executable':
 
336
                    assert value in ('yes', 'no'), value
338
337
                    val = (value == 'yes')
339
338
                    bundle_tree.note_executable(new_path, val)
340
339
                elif name == 'target':
344
343
            return last_changed, encoding
345
344
 
346
345
        def do_patch(path, lines, encoding):
347
 
            if encoding == 'base64':
 
346
            if encoding is not None:
 
347
                assert encoding == 'base64'
348
348
                patch = base64.decodestring(''.join(lines))
349
 
            elif encoding is None:
 
349
            else:
350
350
                patch =  ''.join(lines)
351
 
            else:
352
 
                raise ValueError(encoding)
353
351
            bundle_tree.note_patch(path, patch)
354
352
 
355
353
        def renamed(kind, extra, lines):
415
413
            revision = get_rev_id(last_modified, path, kind)
416
414
            if lines:
417
415
                do_patch(path, lines, encoding)
418
 
 
 
416
            
419
417
        valid_actions = {
420
418
            'renamed':renamed,
421
419
            'removed':removed,
444
442
                        ' (unrecognized action): %r' % action_line)
445
443
            valid_actions[action](kind, extra, lines)
446
444
 
447
 
    def install_revisions(self, target_repo, stream_input=True):
448
 
        """Install revisions and return the target revision
449
 
 
450
 
        :param target_repo: The repository to install into
451
 
        :param stream_input: Ignored by this implementation.
452
 
        """
 
445
    def install_revisions(self, target_repo):
 
446
        """Install revisions and return the target revision"""
453
447
        apply_bundle.install_bundle(target_repo, self)
454
448
        return self.target
455
449
 
456
 
    def get_merge_request(self, target_repo):
457
 
        """Provide data for performing a merge
458
 
 
459
 
        Returns suggested base, suggested target, and patch verification status
460
 
        """
461
 
        return None, self.target, 'inapplicable'
462
 
 
463
450
 
464
451
class BundleTree(Tree):
465
452
    def __init__(self, base_tree, revision_id):
483
470
 
484
471
    def note_rename(self, old_path, new_path):
485
472
        """A file/directory has been renamed from old_path => new_path"""
486
 
        if new_path in self._renamed:
487
 
            raise AssertionError(new_path)
488
 
        if old_path in self._renamed_r:
489
 
            raise AssertionError(old_path)
 
473
        assert new_path not in self._renamed
 
474
        assert old_path not in self._renamed_r
490
475
        self._renamed[new_path] = old_path
491
476
        self._renamed_r[old_path] = new_path
492
477
 
522
507
 
523
508
    def old_path(self, new_path):
524
509
        """Get the old_path (path in the base_tree) for the file at new_path"""
525
 
        if new_path[:1] in ('\\', '/'):
526
 
            raise ValueError(new_path)
 
510
        assert new_path[:1] not in ('\\', '/')
527
511
        old_path = self._renamed.get(new_path)
528
512
        if old_path is not None:
529
513
            return old_path
543
527
        #renamed_r
544
528
        if old_path in self._renamed_r:
545
529
            return None
546
 
        return old_path
 
530
        return old_path 
547
531
 
548
532
    def new_path(self, old_path):
549
533
        """Get the new_path (path in the target_tree) for the file at old_path
550
534
        in the base tree.
551
535
        """
552
 
        if old_path[:1] in ('\\', '/'):
553
 
            raise ValueError(old_path)
 
536
        assert old_path[:1] not in ('\\', '/')
554
537
        new_path = self._renamed_r.get(old_path)
555
538
        if new_path is not None:
556
539
            return new_path
569
552
        #renamed_r
570
553
        if new_path in self._renamed:
571
554
            return None
572
 
        return new_path
 
555
        return new_path 
573
556
 
574
557
    def path2id(self, path):
575
558
        """Return the id of the file present at path in the target tree."""
609
592
                return None
610
593
        new_path = self.id2path(file_id)
611
594
        return self.base_tree.path2id(new_path)
612
 
 
 
595
        
613
596
    def get_file(self, file_id):
614
597
        """Return a file-like object containing the new contents of the
615
598
        file given by file_id.
626
609
            patch_original = None
627
610
        file_patch = self.patches.get(self.id2path(file_id))
628
611
        if file_patch is None:
629
 
            if (patch_original is None and
 
612
            if (patch_original is None and 
630
613
                self.get_kind(file_id) == 'directory'):
631
614
                return StringIO()
632
 
            if patch_original is None:
633
 
                raise AssertionError("None: %s" % file_id)
 
615
            assert patch_original is not None, "None: %s" % file_id
634
616
            return patch_original
635
617
 
636
 
        if file_patch.startswith('\\'):
637
 
            raise ValueError(
638
 
                '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)
639
620
        return patched_file(file_patch, patch_original)
640
621
 
641
622
    def get_symlink_target(self, file_id):
688
669
        This need to be called before ever accessing self.inventory
689
670
        """
690
671
        from os.path import dirname, basename
 
672
 
 
673
        assert self.base_tree is not None
691
674
        base_inv = self.base_tree.inventory
692
675
        inv = Inventory(None, self.revision_id)
693
676
 
715
698
                ie.symlink_target = self.get_symlink_target(file_id)
716
699
            ie.revision = revision_id
717
700
 
718
 
            if kind == 'file':
 
701
            if kind in ('directory', 'symlink'):
 
702
                ie.text_size, ie.text_sha1 = None, None
 
703
            else:
719
704
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
720
 
                if ie.text_size is None:
721
 
                    raise BzrError(
722
 
                        '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)
723
707
            inv.add(ie)
724
708
 
725
709
        sorted_entries = self.sorted_path_id()