~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: John Arbash Meinel
  • Date: 2007-04-12 20:27:42 UTC
  • mto: (2399.1.15 doc-cleanup)
  • mto: This revision was merged to the branch mainline in revision 2431.
  • Revision ID: john@arbash-meinel.com-20070412202742-4cr2qmchdfe9mg7n
Cherrypick just the epydoc builder changes.
This is just the piece of change that makes 'make api-docs' work,
without any actual documentation changes.

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
 
19
 
from __future__ import absolute_import
20
 
 
21
19
import base64
22
20
from cStringIO import StringIO
23
21
import os
25
23
 
26
24
from bzrlib import (
27
25
    osutils,
28
 
    timestamp,
29
26
    )
 
27
import bzrlib.errors
30
28
from bzrlib.bundle import apply_bundle
31
 
from bzrlib.errors import (
32
 
    TestamentMismatch,
33
 
    BzrError,
34
 
    )
35
 
from bzrlib.inventory import (
36
 
    Inventory,
37
 
    InventoryDirectory,
38
 
    InventoryFile,
39
 
    InventoryLink,
40
 
    )
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,
 
33
                              InventoryLink)
 
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
47
42
 
48
43
 
81
76
        if self.properties:
82
77
            for property in self.properties:
83
78
                key_end = property.find(': ')
84
 
                if key_end == -1:
85
 
                    if not property.endswith(':'):
86
 
                        raise ValueError(property)
87
 
                    key = str(property[:-1])
88
 
                    value = ''
89
 
                else:
90
 
                    key = str(property[:key_end])
91
 
                    value = property[key_end+2:]
 
79
                assert key_end is not None
 
80
                key = property[:key_end].encode('utf-8')
 
81
                value = property[key_end+2:].encode('utf-8')
92
82
                rev.properties[key] = value
93
83
 
94
84
        return rev
95
85
 
96
 
    @staticmethod
97
 
    def from_revision(revision):
98
 
        revision_info = RevisionInfo(revision.revision_id)
99
 
        date = timestamp.format_highres_date(revision.timestamp,
100
 
                                             revision.timezone)
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()]
107
 
        return revision_info
108
 
 
109
86
 
110
87
class BundleInfo(object):
111
88
    """This contains the meta information. Stuff that allows you to
112
89
    recreate the revision or inventory XML.
113
90
    """
114
 
    def __init__(self, bundle_format=None):
115
 
        self.bundle_format = None
 
91
    def __init__(self):
116
92
        self.committer = None
117
93
        self.date = None
118
94
        self.message = None
129
105
        self.timestamp = None
130
106
        self.timezone = None
131
107
 
132
 
        # Have we checked the repository yet?
133
 
        self._validated_revisions_against_repo = False
134
 
 
135
108
    def __str__(self):
136
109
        return pprint.pformat(self.__dict__)
137
110
 
163
136
    def get_base(self, revision):
164
137
        revision_info = self.get_revision_info(revision.revision_id)
165
138
        if revision_info.base_id is not None:
166
 
            return revision_info.base_id
 
139
            if revision_info.base_id == NULL_REVISION:
 
140
                return None
 
141
            else:
 
142
                return revision_info.base_id
167
143
        if len(revision.parent_ids) == 0:
168
144
            # There is no base listed, and
169
145
            # the lowest revision doesn't have a parent
170
146
            # so this is probably against the empty tree
171
 
            # and thus base truly is NULL_REVISION
172
 
            return NULL_REVISION
 
147
            # and thus base truly is None
 
148
            return None
173
149
        else:
174
150
            return revision.parent_ids[-1]
175
151
 
196
172
        raise KeyError(revision_id)
197
173
 
198
174
    def revision_tree(self, repository, revision_id, base=None):
 
175
        revision_id = osutils.safe_revision_id(revision_id)
199
176
        revision = self.get_revision(revision_id)
200
177
        base = self.get_base(revision)
201
 
        if base == revision_id:
202
 
            raise AssertionError()
203
 
        if not self._validated_revisions_against_repo:
204
 
            self._validate_references_from_repository(repository)
 
178
        assert base != revision_id
 
179
        self._validate_references_from_repository(repository)
205
180
        revision_info = self.get_revision_info(revision_id)
206
181
        inventory_revision_id = revision_id
207
 
        bundle_tree = BundleTree(repository.revision_tree(base),
 
182
        bundle_tree = BundleTree(repository.revision_tree(base), 
208
183
                                  inventory_revision_id)
209
184
        self._update_tree(bundle_tree, revision_id)
210
185
 
211
186
        inv = bundle_tree.inventory
212
187
        self._validate_inventory(inv, revision_id)
213
 
        self._validate_revision(bundle_tree, revision_id)
 
188
        self._validate_revision(inv, revision_id)
214
189
 
215
190
        return bundle_tree
216
191
 
243
218
        for rev_info in self.revisions:
244
219
            checked[rev_info.revision_id] = True
245
220
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
246
 
 
 
221
                
247
222
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
248
223
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
249
224
 
251
226
        missing = {}
252
227
        for revision_id, sha1 in rev_to_sha.iteritems():
253
228
            if repository.has_revision(revision_id):
254
 
                testament = StrictTestament.from_revision(repository,
 
229
                testament = StrictTestament.from_revision(repository, 
255
230
                                                          revision_id)
256
231
                local_sha1 = self._testament_sha1_from_revision(repository,
257
232
                                                                revision_id)
258
233
                if sha1 != local_sha1:
259
 
                    raise BzrError('sha1 mismatch. For revision id {%s}'
 
234
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
260
235
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
261
236
                else:
262
237
                    count += 1
263
238
            elif revision_id not in checked:
264
239
                missing[revision_id] = sha1
265
240
 
 
241
        for inv_id, sha1 in inv_to_sha.iteritems():
 
242
            if repository.has_revision(inv_id):
 
243
                # Note: branch.get_inventory_sha1() just returns the value that
 
244
                # is stored in the revision text, and that value may be out
 
245
                # of date. This is bogus, because that means we aren't
 
246
                # validating the actual text, just that we wrote and read the
 
247
                # string. But for now, what the hell.
 
248
                local_sha1 = repository.get_inventory_sha1(inv_id)
 
249
                if sha1 != local_sha1:
 
250
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
 
251
                                   'local: %s, bundle: %s' % 
 
252
                                   (inv_id, local_sha1, sha1))
 
253
                else:
 
254
                    count += 1
 
255
 
266
256
        if len(missing) > 0:
267
257
            # I don't know if this is an error yet
268
258
            warning('Not all revision hashes could be validated.'
269
259
                    ' Unable validate %d hashes' % len(missing))
270
260
        mutter('Verified %d sha hashes for the bundle.' % count)
271
 
        self._validated_revisions_against_repo = True
272
261
 
273
262
    def _validate_inventory(self, inv, revision_id):
274
263
        """At this point we should have generated the BundleTree,
275
264
        so build up an inventory, and make sure the hashes match.
276
265
        """
 
266
 
 
267
        assert inv is not None
 
268
 
277
269
        # Now we should have a complete inventory entry.
278
270
        s = serializer_v5.write_inventory_to_string(inv)
279
271
        sha1 = sha_string(s)
280
272
        # Target revision is the last entry in the real_revisions list
281
273
        rev = self.get_revision(revision_id)
282
 
        if rev.revision_id != revision_id:
283
 
            raise AssertionError()
 
274
        assert rev.revision_id == revision_id
284
275
        if sha1 != rev.inventory_sha1:
285
 
            f = open(',,bogus-inv', 'wb')
286
 
            try:
287
 
                f.write(s)
288
 
            finally:
289
 
                f.close()
 
276
            open(',,bogus-inv', 'wb').write(s)
290
277
            warning('Inventory sha hash mismatch for revision %s. %s'
291
278
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
292
279
 
293
 
    def _validate_revision(self, tree, revision_id):
 
280
    def _validate_revision(self, inventory, revision_id):
294
281
        """Make sure all revision entries match their checksum."""
295
282
 
296
 
        # This is a mapping from each revision id to its sha hash
 
283
        # This is a mapping from each revision id to it's sha hash
297
284
        rev_to_sha1 = {}
298
 
 
 
285
        
299
286
        rev = self.get_revision(revision_id)
300
287
        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)
 
288
        assert rev.revision_id == rev_info.revision_id
 
289
        assert rev.revision_id == revision_id
 
290
        sha1 = self._testament_sha1(rev, inventory)
306
291
        if sha1 != rev_info.sha1:
307
292
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
308
293
        if rev.revision_id in rev_to_sha1:
335
320
                try:
336
321
                    name, value = info_item.split(':', 1)
337
322
                except ValueError:
338
 
                    raise ValueError('Value %r has no colon' % info_item)
 
323
                    raise 'Value %r has no colon' % info_item
339
324
                if name == 'last-changed':
340
325
                    last_changed = value
341
326
                elif name == 'executable':
 
327
                    assert value in ('yes', 'no'), value
342
328
                    val = (value == 'yes')
343
329
                    bundle_tree.note_executable(new_path, val)
344
330
                elif name == 'target':
348
334
            return last_changed, encoding
349
335
 
350
336
        def do_patch(path, lines, encoding):
351
 
            if encoding == 'base64':
 
337
            if encoding is not None:
 
338
                assert encoding == 'base64'
352
339
                patch = base64.decodestring(''.join(lines))
353
 
            elif encoding is None:
 
340
            else:
354
341
                patch =  ''.join(lines)
355
 
            else:
356
 
                raise ValueError(encoding)
357
342
            bundle_tree.note_patch(path, patch)
358
343
 
359
344
        def renamed(kind, extra, lines):
419
404
            revision = get_rev_id(last_modified, path, kind)
420
405
            if lines:
421
406
                do_patch(path, lines, encoding)
422
 
 
 
407
            
423
408
        valid_actions = {
424
409
            'renamed':renamed,
425
410
            'removed':removed,
448
433
                        ' (unrecognized action): %r' % action_line)
449
434
            valid_actions[action](kind, extra, lines)
450
435
 
451
 
    def install_revisions(self, target_repo, stream_input=True):
452
 
        """Install revisions and return the target revision
453
 
 
454
 
        :param target_repo: The repository to install into
455
 
        :param stream_input: Ignored by this implementation.
456
 
        """
 
436
    def install_revisions(self, target_repo):
 
437
        """Install revisions and return the target revision"""
457
438
        apply_bundle.install_bundle(target_repo, self)
458
439
        return self.target
459
440
 
460
 
    def get_merge_request(self, target_repo):
461
 
        """Provide data for performing a merge
462
 
 
463
 
        Returns suggested base, suggested target, and patch verification status
464
 
        """
465
 
        return None, self.target, 'inapplicable'
466
 
 
467
441
 
468
442
class BundleTree(Tree):
469
 
 
470
443
    def __init__(self, base_tree, revision_id):
471
444
        self.base_tree = base_tree
472
445
        self._renamed = {} # Mapping from old_path => new_path
488
461
 
489
462
    def note_rename(self, old_path, new_path):
490
463
        """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)
 
464
        assert new_path not in self._renamed
 
465
        assert old_path not in self._renamed_r
495
466
        self._renamed[new_path] = old_path
496
467
        self._renamed_r[old_path] = new_path
497
468
 
527
498
 
528
499
    def old_path(self, new_path):
529
500
        """Get the old_path (path in the base_tree) for the file at new_path"""
530
 
        if new_path[:1] in ('\\', '/'):
531
 
            raise ValueError(new_path)
 
501
        assert new_path[:1] not in ('\\', '/')
532
502
        old_path = self._renamed.get(new_path)
533
503
        if old_path is not None:
534
504
            return old_path
548
518
        #renamed_r
549
519
        if old_path in self._renamed_r:
550
520
            return None
551
 
        return old_path
 
521
        return old_path 
552
522
 
553
523
    def new_path(self, old_path):
554
524
        """Get the new_path (path in the target_tree) for the file at old_path
555
525
        in the base tree.
556
526
        """
557
 
        if old_path[:1] in ('\\', '/'):
558
 
            raise ValueError(old_path)
 
527
        assert old_path[:1] not in ('\\', '/')
559
528
        new_path = self._renamed_r.get(old_path)
560
529
        if new_path is not None:
561
530
            return new_path
574
543
        #renamed_r
575
544
        if new_path in self._renamed:
576
545
            return None
577
 
        return new_path
578
 
 
579
 
    def get_root_id(self):
580
 
        return self.path2id('')
 
546
        return new_path 
581
547
 
582
548
    def path2id(self, path):
583
549
        """Return the id of the file present at path in the target tree."""
589
555
            return None
590
556
        if old_path in self.deleted:
591
557
            return None
592
 
        return self.base_tree.path2id(old_path)
 
558
        if getattr(self.base_tree, 'path2id', None) is not None:
 
559
            return self.base_tree.path2id(old_path)
 
560
        else:
 
561
            return self.base_tree.inventory.path2id(old_path)
593
562
 
594
563
    def id2path(self, file_id):
595
564
        """Return the new path in the target tree of the file with id file_id"""
614
583
                return None
615
584
        new_path = self.id2path(file_id)
616
585
        return self.base_tree.path2id(new_path)
617
 
 
 
586
        
618
587
    def get_file(self, file_id):
619
588
        """Return a file-like object containing the new contents of the
620
589
        file given by file_id.
625
594
        """
626
595
        base_id = self.old_contents_id(file_id)
627
596
        if (base_id is not None and
628
 
            base_id != self.base_tree.get_root_id()):
 
597
            base_id != self.base_tree.inventory.root.file_id):
629
598
            patch_original = self.base_tree.get_file(base_id)
630
599
        else:
631
600
            patch_original = None
632
601
        file_patch = self.patches.get(self.id2path(file_id))
633
602
        if file_patch is None:
634
 
            if (patch_original is None and
635
 
                self.kind(file_id) == 'directory'):
 
603
            if (patch_original is None and 
 
604
                self.get_kind(file_id) == 'directory'):
636
605
                return StringIO()
637
 
            if patch_original is None:
638
 
                raise AssertionError("None: %s" % file_id)
 
606
            assert patch_original is not None, "None: %s" % file_id
639
607
            return patch_original
640
608
 
641
 
        if file_patch.startswith('\\'):
642
 
            raise ValueError(
643
 
                'Malformed patch for %s, %r' % (file_id, file_patch))
 
609
        assert not file_patch.startswith('\\'), \
 
610
            'Malformed patch for %s, %r' % (file_id, file_patch)
644
611
        return patched_file(file_patch, patch_original)
645
612
 
646
 
    def get_symlink_target(self, file_id, path=None):
647
 
        if path is None:
648
 
            path = self.id2path(file_id)
 
613
    def get_symlink_target(self, file_id):
 
614
        new_path = self.id2path(file_id)
649
615
        try:
650
 
            return self._targets[path]
 
616
            return self._targets[new_path]
651
617
        except KeyError:
652
618
            return self.base_tree.get_symlink_target(file_id)
653
619
 
654
 
    def kind(self, file_id):
 
620
    def get_kind(self, file_id):
655
621
        if file_id in self._kinds:
656
622
            return self._kinds[file_id]
657
 
        return self.base_tree.kind(file_id)
658
 
 
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]
663
 
        else:
664
 
            return self.base_tree.get_file_revision(file_id)
 
623
        return self.base_tree.inventory[file_id].kind
665
624
 
666
625
    def is_executable(self, file_id):
667
626
        path = self.id2path(file_id)
668
627
        if path in self._executable:
669
628
            return self._executable[path]
670
629
        else:
671
 
            return self.base_tree.is_executable(file_id)
 
630
            return self.base_tree.inventory[file_id].executable
672
631
 
673
632
    def get_last_changed(self, file_id):
674
633
        path = self.id2path(file_id)
675
634
        if path in self._last_changed:
676
635
            return self._last_changed[path]
677
 
        return self.base_tree.get_file_revision(file_id)
 
636
        return self.base_tree.inventory[file_id].revision
678
637
 
679
638
    def get_size_and_sha1(self, file_id):
680
639
        """Return the size and sha1 hash of the given file id.
687
646
        if new_path not in self.patches:
688
647
            # If the entry does not have a patch, then the
689
648
            # 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
 
649
            ie = self.base_tree.inventory[file_id]
 
650
            if ie.text_size is None:
 
651
                return ie.text_size, ie.text_sha1
 
652
            return int(ie.text_size), ie.text_sha1
693
653
        fileobj = self.get_file(file_id)
694
654
        content = fileobj.read()
695
655
        return len(content), sha_string(content)
700
660
        This need to be called before ever accessing self.inventory
701
661
        """
702
662
        from os.path import dirname, basename
 
663
 
 
664
        assert self.base_tree is not None
 
665
        base_inv = self.base_tree.inventory
703
666
        inv = Inventory(None, self.revision_id)
704
667
 
705
668
        def add_entry(file_id):
712
675
                parent_path = dirname(path)
713
676
                parent_id = self.path2id(parent_path)
714
677
 
715
 
            kind = self.kind(file_id)
 
678
            kind = self.get_kind(file_id)
716
679
            revision_id = self.get_last_changed(file_id)
717
680
 
718
681
            name = basename(path)
723
686
                ie.executable = self.is_executable(file_id)
724
687
            elif kind == 'symlink':
725
688
                ie = InventoryLink(file_id, name, parent_id)
726
 
                ie.symlink_target = self.get_symlink_target(file_id, path)
 
689
                ie.symlink_target = self.get_symlink_target(file_id)
727
690
            ie.revision = revision_id
728
691
 
729
 
            if kind == 'file':
 
692
            if kind in ('directory', 'symlink'):
 
693
                ie.text_size, ie.text_sha1 = None, None
 
694
            else:
730
695
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
731
 
                if ie.text_size is None:
732
 
                    raise BzrError(
733
 
                        'Got a text_size of None for file_id %r' % file_id)
 
696
            if (ie.text_size is None) and (kind == 'file'):
 
697
                raise BzrError('Got a text_size of None for file_id %r' % file_id)
734
698
            inv.add(ie)
735
699
 
736
700
        sorted_entries = self.sorted_path_id()
746
710
    # at that instant
747
711
    inventory = property(_get_inventory)
748
712
 
749
 
    root_inventory = property(_get_inventory)
750
 
 
751
 
    def all_file_ids(self):
752
 
        return set(
753
 
            [entry.file_id for path, entry in self.inventory.iter_entries()])
754
 
 
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
757
 
        inv = self.inventory
758
 
        if from_dir is None:
759
 
            from_dir_id = None
760
 
        else:
761
 
            from_dir_id = inv.path2id(from_dir)
762
 
            if from_dir_id is None:
763
 
                # Directory not versioned
764
 
                return
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.
768
 
            entries.next()
769
 
        for path, entry in entries:
770
 
            yield path, 'V', entry.kind, entry.file_id, entry
 
713
    def __iter__(self):
 
714
        for path, entry in self.inventory.iter_entries():
 
715
            yield entry.file_id
771
716
 
772
717
    def sorted_path_id(self):
773
718
        paths = []
774
719
        for result in self._new_id.iteritems():
775
720
            paths.append(result)
776
 
        for id in self.base_tree.all_file_ids():
 
721
        for id in self.base_tree:
777
722
            path = self.id2path(id)
778
723
            if path is None:
779
724
                continue