/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

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
    )
 
27
import bzrlib.errors
28
28
from bzrlib.bundle import apply_bundle
29
 
from bzrlib.errors import (
30
 
    TestamentMismatch,
31
 
    BzrError,
32
 
    )
33
 
from bzrlib.inventory import (
34
 
    Inventory,
35
 
    InventoryDirectory,
36
 
    InventoryFile,
37
 
    InventoryLink,
38
 
    )
39
 
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
40
35
from bzrlib.revision import Revision, NULL_REVISION
41
36
from bzrlib.testament import StrictTestament
42
37
from bzrlib.trace import mutter, warning
 
38
import bzrlib.transport
43
39
from bzrlib.tree import Tree
 
40
import bzrlib.urlutils
44
41
from bzrlib.xml5 import serializer_v5
45
42
 
46
43
 
79
76
        if self.properties:
80
77
            for property in self.properties:
81
78
                key_end = property.find(': ')
82
 
                if key_end == -1:
83
 
                    if not property.endswith(':'):
84
 
                        raise ValueError(property)
85
 
                    key = str(property[:-1])
86
 
                    value = ''
87
 
                else:
88
 
                    key = str(property[:key_end])
89
 
                    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')
90
82
                rev.properties[key] = value
91
83
 
92
84
        return rev
93
85
 
94
 
    @staticmethod
95
 
    def from_revision(revision):
96
 
        revision_info = RevisionInfo(revision.revision_id)
97
 
        date = timestamp.format_highres_date(revision.timestamp,
98
 
                                             revision.timezone)
99
 
        revision_info.date = date
100
 
        revision_info.timezone = revision.timezone
101
 
        revision_info.timestamp = revision.timestamp
102
 
        revision_info.message = revision.message.split('\n')
103
 
        revision_info.properties = [': '.join(p) for p in
104
 
                                    revision.properties.iteritems()]
105
 
        return revision_info
106
 
 
107
86
 
108
87
class BundleInfo(object):
109
88
    """This contains the meta information. Stuff that allows you to
110
89
    recreate the revision or inventory XML.
111
90
    """
112
 
    def __init__(self, bundle_format=None):
113
 
        self.bundle_format = None
 
91
    def __init__(self):
114
92
        self.committer = None
115
93
        self.date = None
116
94
        self.message = None
127
105
        self.timestamp = None
128
106
        self.timezone = None
129
107
 
130
 
        # Have we checked the repository yet?
131
 
        self._validated_revisions_against_repo = False
132
 
 
133
108
    def __str__(self):
134
109
        return pprint.pformat(self.__dict__)
135
110
 
161
136
    def get_base(self, revision):
162
137
        revision_info = self.get_revision_info(revision.revision_id)
163
138
        if revision_info.base_id is not None:
164
 
            return revision_info.base_id
 
139
            if revision_info.base_id == NULL_REVISION:
 
140
                return None
 
141
            else:
 
142
                return revision_info.base_id
165
143
        if len(revision.parent_ids) == 0:
166
144
            # There is no base listed, and
167
145
            # the lowest revision doesn't have a parent
168
146
            # so this is probably against the empty tree
169
 
            # and thus base truly is NULL_REVISION
170
 
            return NULL_REVISION
 
147
            # and thus base truly is None
 
148
            return None
171
149
        else:
172
150
            return revision.parent_ids[-1]
173
151
 
194
172
        raise KeyError(revision_id)
195
173
 
196
174
    def revision_tree(self, repository, revision_id, base=None):
 
175
        revision_id = osutils.safe_revision_id(revision_id)
197
176
        revision = self.get_revision(revision_id)
198
177
        base = self.get_base(revision)
199
 
        if base == revision_id:
200
 
            raise AssertionError()
201
 
        if not self._validated_revisions_against_repo:
202
 
            self._validate_references_from_repository(repository)
 
178
        assert base != revision_id
 
179
        self._validate_references_from_repository(repository)
203
180
        revision_info = self.get_revision_info(revision_id)
204
181
        inventory_revision_id = revision_id
205
 
        bundle_tree = BundleTree(repository.revision_tree(base),
 
182
        bundle_tree = BundleTree(repository.revision_tree(base), 
206
183
                                  inventory_revision_id)
207
184
        self._update_tree(bundle_tree, revision_id)
208
185
 
209
186
        inv = bundle_tree.inventory
210
187
        self._validate_inventory(inv, revision_id)
211
 
        self._validate_revision(bundle_tree, revision_id)
 
188
        self._validate_revision(inv, revision_id)
212
189
 
213
190
        return bundle_tree
214
191
 
241
218
        for rev_info in self.revisions:
242
219
            checked[rev_info.revision_id] = True
243
220
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
244
 
 
 
221
                
245
222
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
246
223
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
247
224
 
249
226
        missing = {}
250
227
        for revision_id, sha1 in rev_to_sha.iteritems():
251
228
            if repository.has_revision(revision_id):
252
 
                testament = StrictTestament.from_revision(repository,
 
229
                testament = StrictTestament.from_revision(repository, 
253
230
                                                          revision_id)
254
231
                local_sha1 = self._testament_sha1_from_revision(repository,
255
232
                                                                revision_id)
256
233
                if sha1 != local_sha1:
257
 
                    raise BzrError('sha1 mismatch. For revision id {%s}'
 
234
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
258
235
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
259
236
                else:
260
237
                    count += 1
261
238
            elif revision_id not in checked:
262
239
                missing[revision_id] = sha1
263
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
 
264
256
        if len(missing) > 0:
265
257
            # I don't know if this is an error yet
266
258
            warning('Not all revision hashes could be validated.'
267
259
                    ' Unable validate %d hashes' % len(missing))
268
260
        mutter('Verified %d sha hashes for the bundle.' % count)
269
 
        self._validated_revisions_against_repo = True
270
261
 
271
262
    def _validate_inventory(self, inv, revision_id):
272
263
        """At this point we should have generated the BundleTree,
273
264
        so build up an inventory, and make sure the hashes match.
274
265
        """
 
266
 
 
267
        assert inv is not None
 
268
 
275
269
        # Now we should have a complete inventory entry.
276
270
        s = serializer_v5.write_inventory_to_string(inv)
277
271
        sha1 = sha_string(s)
278
272
        # Target revision is the last entry in the real_revisions list
279
273
        rev = self.get_revision(revision_id)
280
 
        if rev.revision_id != revision_id:
281
 
            raise AssertionError()
 
274
        assert rev.revision_id == revision_id
282
275
        if sha1 != rev.inventory_sha1:
283
 
            f = open(',,bogus-inv', 'wb')
284
 
            try:
285
 
                f.write(s)
286
 
            finally:
287
 
                f.close()
 
276
            open(',,bogus-inv', 'wb').write(s)
288
277
            warning('Inventory sha hash mismatch for revision %s. %s'
289
278
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
290
279
 
291
 
    def _validate_revision(self, tree, revision_id):
 
280
    def _validate_revision(self, inventory, revision_id):
292
281
        """Make sure all revision entries match their checksum."""
293
282
 
294
 
        # 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
295
284
        rev_to_sha1 = {}
296
 
 
 
285
        
297
286
        rev = self.get_revision(revision_id)
298
287
        rev_info = self.get_revision_info(revision_id)
299
 
        if not (rev.revision_id == rev_info.revision_id):
300
 
            raise AssertionError()
301
 
        if not (rev.revision_id == revision_id):
302
 
            raise AssertionError()
303
 
        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)
304
291
        if sha1 != rev_info.sha1:
305
292
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
306
293
        if rev.revision_id in rev_to_sha1:
333
320
                try:
334
321
                    name, value = info_item.split(':', 1)
335
322
                except ValueError:
336
 
                    raise ValueError('Value %r has no colon' % info_item)
 
323
                    raise 'Value %r has no colon' % info_item
337
324
                if name == 'last-changed':
338
325
                    last_changed = value
339
326
                elif name == 'executable':
 
327
                    assert value in ('yes', 'no'), value
340
328
                    val = (value == 'yes')
341
329
                    bundle_tree.note_executable(new_path, val)
342
330
                elif name == 'target':
346
334
            return last_changed, encoding
347
335
 
348
336
        def do_patch(path, lines, encoding):
349
 
            if encoding == 'base64':
 
337
            if encoding is not None:
 
338
                assert encoding == 'base64'
350
339
                patch = base64.decodestring(''.join(lines))
351
 
            elif encoding is None:
 
340
            else:
352
341
                patch =  ''.join(lines)
353
 
            else:
354
 
                raise ValueError(encoding)
355
342
            bundle_tree.note_patch(path, patch)
356
343
 
357
344
        def renamed(kind, extra, lines):
417
404
            revision = get_rev_id(last_modified, path, kind)
418
405
            if lines:
419
406
                do_patch(path, lines, encoding)
420
 
 
 
407
            
421
408
        valid_actions = {
422
409
            'renamed':renamed,
423
410
            'removed':removed,
446
433
                        ' (unrecognized action): %r' % action_line)
447
434
            valid_actions[action](kind, extra, lines)
448
435
 
449
 
    def install_revisions(self, target_repo, stream_input=True):
450
 
        """Install revisions and return the target revision
451
 
 
452
 
        :param target_repo: The repository to install into
453
 
        :param stream_input: Ignored by this implementation.
454
 
        """
 
436
    def install_revisions(self, target_repo):
 
437
        """Install revisions and return the target revision"""
455
438
        apply_bundle.install_bundle(target_repo, self)
456
439
        return self.target
457
440
 
458
 
    def get_merge_request(self, target_repo):
459
 
        """Provide data for performing a merge
460
 
 
461
 
        Returns suggested base, suggested target, and patch verification status
462
 
        """
463
 
        return None, self.target, 'inapplicable'
464
 
 
465
441
 
466
442
class BundleTree(Tree):
467
 
 
468
443
    def __init__(self, base_tree, revision_id):
469
444
        self.base_tree = base_tree
470
445
        self._renamed = {} # Mapping from old_path => new_path
486
461
 
487
462
    def note_rename(self, old_path, new_path):
488
463
        """A file/directory has been renamed from old_path => new_path"""
489
 
        if new_path in self._renamed:
490
 
            raise AssertionError(new_path)
491
 
        if old_path in self._renamed_r:
492
 
            raise AssertionError(old_path)
 
464
        assert new_path not in self._renamed
 
465
        assert old_path not in self._renamed_r
493
466
        self._renamed[new_path] = old_path
494
467
        self._renamed_r[old_path] = new_path
495
468
 
525
498
 
526
499
    def old_path(self, new_path):
527
500
        """Get the old_path (path in the base_tree) for the file at new_path"""
528
 
        if new_path[:1] in ('\\', '/'):
529
 
            raise ValueError(new_path)
 
501
        assert new_path[:1] not in ('\\', '/')
530
502
        old_path = self._renamed.get(new_path)
531
503
        if old_path is not None:
532
504
            return old_path
546
518
        #renamed_r
547
519
        if old_path in self._renamed_r:
548
520
            return None
549
 
        return old_path
 
521
        return old_path 
550
522
 
551
523
    def new_path(self, old_path):
552
524
        """Get the new_path (path in the target_tree) for the file at old_path
553
525
        in the base tree.
554
526
        """
555
 
        if old_path[:1] in ('\\', '/'):
556
 
            raise ValueError(old_path)
 
527
        assert old_path[:1] not in ('\\', '/')
557
528
        new_path = self._renamed_r.get(old_path)
558
529
        if new_path is not None:
559
530
            return new_path
572
543
        #renamed_r
573
544
        if new_path in self._renamed:
574
545
            return None
575
 
        return new_path
 
546
        return new_path 
576
547
 
577
548
    def path2id(self, path):
578
549
        """Return the id of the file present at path in the target tree."""
612
583
                return None
613
584
        new_path = self.id2path(file_id)
614
585
        return self.base_tree.path2id(new_path)
615
 
 
 
586
        
616
587
    def get_file(self, file_id):
617
588
        """Return a file-like object containing the new contents of the
618
589
        file given by file_id.
629
600
            patch_original = None
630
601
        file_patch = self.patches.get(self.id2path(file_id))
631
602
        if file_patch is None:
632
 
            if (patch_original is None and
 
603
            if (patch_original is None and 
633
604
                self.get_kind(file_id) == 'directory'):
634
605
                return StringIO()
635
 
            if patch_original is None:
636
 
                raise AssertionError("None: %s" % file_id)
 
606
            assert patch_original is not None, "None: %s" % file_id
637
607
            return patch_original
638
608
 
639
 
        if file_patch.startswith('\\'):
640
 
            raise ValueError(
641
 
                '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)
642
611
        return patched_file(file_patch, patch_original)
643
612
 
644
613
    def get_symlink_target(self, file_id):
664
633
        path = self.id2path(file_id)
665
634
        if path in self._last_changed:
666
635
            return self._last_changed[path]
667
 
        return self.base_tree.get_file_revision(file_id)
 
636
        return self.base_tree.inventory[file_id].revision
668
637
 
669
638
    def get_size_and_sha1(self, file_id):
670
639
        """Return the size and sha1 hash of the given file id.
691
660
        This need to be called before ever accessing self.inventory
692
661
        """
693
662
        from os.path import dirname, basename
 
663
 
 
664
        assert self.base_tree is not None
694
665
        base_inv = self.base_tree.inventory
695
666
        inv = Inventory(None, self.revision_id)
696
667
 
718
689
                ie.symlink_target = self.get_symlink_target(file_id)
719
690
            ie.revision = revision_id
720
691
 
721
 
            if kind == 'file':
 
692
            if kind in ('directory', 'symlink'):
 
693
                ie.text_size, ie.text_sha1 = None, None
 
694
            else:
722
695
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
723
 
                if ie.text_size is None:
724
 
                    raise BzrError(
725
 
                        '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)
726
698
            inv.add(ie)
727
699
 
728
700
        sorted_entries = self.sorted_path_id()
742
714
        for path, entry in self.inventory.iter_entries():
743
715
            yield entry.file_id
744
716
 
745
 
    def list_files(self, include_root=False, from_dir=None, recursive=True):
746
 
        # The only files returned by this are those from the version
747
 
        inv = self.inventory
748
 
        if from_dir is None:
749
 
            from_dir_id = None
750
 
        else:
751
 
            from_dir_id = inv.path2id(from_dir)
752
 
            if from_dir_id is None:
753
 
                # Directory not versioned
754
 
                return
755
 
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
756
 
        if inv.root is not None and not include_root and from_dir is None:
757
 
            # skip the root for compatability with the current apis.
758
 
            entries.next()
759
 
        for path, entry in entries:
760
 
            yield path, 'V', entry.kind, entry.file_id, entry
761
 
 
762
717
    def sorted_path_id(self):
763
718
        paths = []
764
719
        for result in self._new_id.iteritems():