~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: Alexander Belchenko
  • Date: 2006-07-30 16:43:12 UTC
  • mto: (1711.2.111 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060730164312-b025fd3ff0cee59e
rename  gpl.txt => COPYING.txt

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2006 by 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
 
21
21
import os
22
22
import pprint
23
23
 
24
 
from bzrlib import (
25
 
    osutils,
26
 
    timestamp,
27
 
    )
28
24
import bzrlib.errors
29
 
from bzrlib.bundle import apply_bundle
30
 
from bzrlib.errors import (TestamentMismatch, BzrError,
 
25
from bzrlib.errors import (TestamentMismatch, BzrError, 
31
26
                           MalformedHeader, MalformedPatches, NotABundle)
32
27
from bzrlib.inventory import (Inventory, InventoryEntry,
33
28
                              InventoryDirectory, InventoryFile,
77
72
        if self.properties:
78
73
            for property in self.properties:
79
74
                key_end = property.find(': ')
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:]
 
75
                assert key_end is not None
 
76
                key = property[:key_end].encode('utf-8')
 
77
                value = property[key_end+2:].encode('utf-8')
88
78
                rev.properties[key] = value
89
79
 
90
80
        return rev
91
81
 
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
82
 
106
83
class BundleInfo(object):
107
84
    """This contains the meta information. Stuff that allows you to
108
85
    recreate the revision or inventory XML.
109
86
    """
110
 
    def __init__(self, bundle_format=None):
111
 
        self.bundle_format = None
 
87
    def __init__(self):
112
88
        self.committer = None
113
89
        self.date = None
114
90
        self.message = None
125
101
        self.timestamp = None
126
102
        self.timezone = None
127
103
 
128
 
        # Have we checked the repository yet?
129
 
        self._validated_revisions_against_repo = False
130
 
 
131
104
    def __str__(self):
132
105
        return pprint.pformat(self.__dict__)
133
106
 
136
109
        split up, based on the assumptions that can be made
137
110
        when information is missing.
138
111
        """
139
 
        from bzrlib.timestamp import unpack_highres_date
 
112
        from bzrlib.bundle.serializer import unpack_highres_date
140
113
        # Put in all of the guessable information.
141
114
        if not self.timestamp and self.date:
142
115
            self.timestamp, self.timezone = unpack_highres_date(self.date)
159
132
    def get_base(self, revision):
160
133
        revision_info = self.get_revision_info(revision.revision_id)
161
134
        if revision_info.base_id is not None:
162
 
            return revision_info.base_id
 
135
            if revision_info.base_id == NULL_REVISION:
 
136
                return None
 
137
            else:
 
138
                return revision_info.base_id
163
139
        if len(revision.parent_ids) == 0:
164
140
            # There is no base listed, and
165
141
            # the lowest revision doesn't have a parent
166
142
            # so this is probably against the empty tree
167
 
            # and thus base truly is NULL_REVISION
168
 
            return NULL_REVISION
 
143
            # and thus base truly is None
 
144
            return None
169
145
        else:
170
146
            return revision.parent_ids[-1]
171
147
 
194
170
    def revision_tree(self, repository, revision_id, base=None):
195
171
        revision = self.get_revision(revision_id)
196
172
        base = self.get_base(revision)
197
 
        if base == revision_id:
198
 
            raise AssertionError()
199
 
        if not self._validated_revisions_against_repo:
200
 
            self._validate_references_from_repository(repository)
 
173
        assert base != revision_id
 
174
        self._validate_references_from_repository(repository)
201
175
        revision_info = self.get_revision_info(revision_id)
202
176
        inventory_revision_id = revision_id
203
 
        bundle_tree = BundleTree(repository.revision_tree(base),
 
177
        bundle_tree = BundleTree(repository.revision_tree(base), 
204
178
                                  inventory_revision_id)
205
179
        self._update_tree(bundle_tree, revision_id)
206
180
 
239
213
        for rev_info in self.revisions:
240
214
            checked[rev_info.revision_id] = True
241
215
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
242
 
 
 
216
                
243
217
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
244
218
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
245
219
 
247
221
        missing = {}
248
222
        for revision_id, sha1 in rev_to_sha.iteritems():
249
223
            if repository.has_revision(revision_id):
250
 
                testament = StrictTestament.from_revision(repository,
 
224
                testament = StrictTestament.from_revision(repository, 
251
225
                                                          revision_id)
252
 
                local_sha1 = self._testament_sha1_from_revision(repository,
253
 
                                                                revision_id)
 
226
                local_sha1 = testament.as_sha1()
254
227
                if sha1 != local_sha1:
255
 
                    raise BzrError('sha1 mismatch. For revision id {%s}'
 
228
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
256
229
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
257
230
                else:
258
231
                    count += 1
259
232
            elif revision_id not in checked:
260
233
                missing[revision_id] = sha1
261
234
 
 
235
        for inv_id, sha1 in inv_to_sha.iteritems():
 
236
            if repository.has_revision(inv_id):
 
237
                # Note: branch.get_inventory_sha1() just returns the value that
 
238
                # is stored in the revision text, and that value may be out
 
239
                # of date. This is bogus, because that means we aren't
 
240
                # validating the actual text, just that we wrote and read the
 
241
                # string. But for now, what the hell.
 
242
                local_sha1 = repository.get_inventory_sha1(inv_id)
 
243
                if sha1 != local_sha1:
 
244
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
 
245
                                   'local: %s, bundle: %s' % 
 
246
                                   (inv_id, local_sha1, sha1))
 
247
                else:
 
248
                    count += 1
 
249
 
262
250
        if len(missing) > 0:
263
251
            # I don't know if this is an error yet
264
252
            warning('Not all revision hashes could be validated.'
265
253
                    ' Unable validate %d hashes' % len(missing))
266
254
        mutter('Verified %d sha hashes for the bundle.' % count)
267
 
        self._validated_revisions_against_repo = True
268
255
 
269
256
    def _validate_inventory(self, inv, revision_id):
270
257
        """At this point we should have generated the BundleTree,
271
258
        so build up an inventory, and make sure the hashes match.
272
259
        """
 
260
 
 
261
        assert inv is not None
 
262
 
273
263
        # Now we should have a complete inventory entry.
274
264
        s = serializer_v5.write_inventory_to_string(inv)
275
265
        sha1 = sha_string(s)
276
266
        # Target revision is the last entry in the real_revisions list
277
267
        rev = self.get_revision(revision_id)
278
 
        if rev.revision_id != revision_id:
279
 
            raise AssertionError()
 
268
        assert rev.revision_id == revision_id
280
269
        if sha1 != rev.inventory_sha1:
281
 
            f = open(',,bogus-inv', 'wb')
282
 
            try:
283
 
                f.write(s)
284
 
            finally:
285
 
                f.close()
 
270
            open(',,bogus-inv', 'wb').write(s)
286
271
            warning('Inventory sha hash mismatch for revision %s. %s'
287
272
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
288
273
 
291
276
 
292
277
        # This is a mapping from each revision id to it's sha hash
293
278
        rev_to_sha1 = {}
294
 
 
 
279
        
295
280
        rev = self.get_revision(revision_id)
296
281
        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()
301
 
        sha1 = self._testament_sha1(rev, inventory)
 
282
        assert rev.revision_id == rev_info.revision_id
 
283
        assert rev.revision_id == revision_id
 
284
        sha1 = StrictTestament(rev, inventory).as_sha1()
302
285
        if sha1 != rev_info.sha1:
303
286
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
304
 
        if rev.revision_id in rev_to_sha1:
 
287
        if rev_to_sha1.has_key(rev.revision_id):
305
288
            raise BzrError('Revision {%s} given twice in the list'
306
289
                    % (rev.revision_id))
307
290
        rev_to_sha1[rev.revision_id] = sha1
315
298
 
316
299
        def get_rev_id(last_changed, path, kind):
317
300
            if last_changed is not None:
318
 
                # last_changed will be a Unicode string because of how it was
319
 
                # read. Convert it back to utf8.
320
 
                changed_revision_id = osutils.safe_revision_id(last_changed,
321
 
                                                               warn=False)
 
301
                changed_revision_id = last_changed.decode('utf-8')
322
302
            else:
323
303
                changed_revision_id = revision_id
324
304
            bundle_tree.note_last_changed(path, changed_revision_id)
331
311
                try:
332
312
                    name, value = info_item.split(':', 1)
333
313
                except ValueError:
334
 
                    raise ValueError('Value %r has no colon' % info_item)
 
314
                    raise 'Value %r has no colon' % info_item
335
315
                if name == 'last-changed':
336
316
                    last_changed = value
337
317
                elif name == 'executable':
 
318
                    assert value in ('yes', 'no'), value
338
319
                    val = (value == 'yes')
339
320
                    bundle_tree.note_executable(new_path, val)
340
321
                elif name == 'target':
344
325
            return last_changed, encoding
345
326
 
346
327
        def do_patch(path, lines, encoding):
347
 
            if encoding == 'base64':
 
328
            if encoding is not None:
 
329
                assert encoding == 'base64'
348
330
                patch = base64.decodestring(''.join(lines))
349
 
            elif encoding is None:
 
331
            else:
350
332
                patch =  ''.join(lines)
351
 
            else:
352
 
                raise ValueError(encoding)
353
333
            bundle_tree.note_patch(path, patch)
354
334
 
355
335
        def renamed(kind, extra, lines):
391
371
            if not info[1].startswith('file-id:'):
392
372
                raise BzrError('The file-id should follow the path for an add'
393
373
                        ': %r' % extra)
394
 
            # This will be Unicode because of how the stream is read. Turn it
395
 
            # back into a utf8 file_id
396
 
            file_id = osutils.safe_file_id(info[1][8:], warn=False)
 
374
            file_id = info[1][8:]
397
375
 
398
376
            bundle_tree.note_id(file_id, path, kind)
399
377
            # this will be overridden in extra_info if executable is specified.
415
393
            revision = get_rev_id(last_modified, path, kind)
416
394
            if lines:
417
395
                do_patch(path, lines, encoding)
418
 
 
 
396
            
419
397
        valid_actions = {
420
398
            'renamed':renamed,
421
399
            'removed':removed,
444
422
                        ' (unrecognized action): %r' % action_line)
445
423
            valid_actions[action](kind, extra, lines)
446
424
 
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
 
        """
453
 
        apply_bundle.install_bundle(target_repo, self)
454
 
        return self.target
455
 
 
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
425
 
464
426
class BundleTree(Tree):
465
427
    def __init__(self, base_tree, revision_id):
483
445
 
484
446
    def note_rename(self, old_path, new_path):
485
447
        """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)
 
448
        assert not self._renamed.has_key(new_path)
 
449
        assert not self._renamed_r.has_key(old_path)
490
450
        self._renamed[new_path] = old_path
491
451
        self._renamed_r[old_path] = new_path
492
452
 
497
457
        self._kinds[new_id] = kind
498
458
 
499
459
    def note_last_changed(self, file_id, revision_id):
500
 
        if (file_id in self._last_changed
 
460
        if (self._last_changed.has_key(file_id)
501
461
                and self._last_changed[file_id] != revision_id):
502
462
            raise BzrError('Mismatched last-changed revision for file_id {%s}'
503
463
                    ': %s != %s' % (file_id,
522
482
 
523
483
    def old_path(self, new_path):
524
484
        """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)
 
485
        assert new_path[:1] not in ('\\', '/')
527
486
        old_path = self._renamed.get(new_path)
528
487
        if old_path is not None:
529
488
            return old_path
541
500
            old_path = new_path
542
501
        #If the new path wasn't in renamed, the old one shouldn't be in
543
502
        #renamed_r
544
 
        if old_path in self._renamed_r:
 
503
        if self._renamed_r.has_key(old_path):
545
504
            return None
546
 
        return old_path
 
505
        return old_path 
547
506
 
548
507
    def new_path(self, old_path):
549
508
        """Get the new_path (path in the target_tree) for the file at old_path
550
509
        in the base tree.
551
510
        """
552
 
        if old_path[:1] in ('\\', '/'):
553
 
            raise ValueError(old_path)
 
511
        assert old_path[:1] not in ('\\', '/')
554
512
        new_path = self._renamed_r.get(old_path)
555
513
        if new_path is not None:
556
514
            return new_path
557
 
        if new_path in self._renamed:
 
515
        if self._renamed.has_key(new_path):
558
516
            return None
559
517
        dirname,basename = os.path.split(old_path)
560
518
        if dirname != '':
567
525
            new_path = old_path
568
526
        #If the old path wasn't in renamed, the new one shouldn't be in
569
527
        #renamed_r
570
 
        if new_path in self._renamed:
 
528
        if self._renamed.has_key(new_path):
571
529
            return None
572
 
        return new_path
 
530
        return new_path 
573
531
 
574
532
    def path2id(self, path):
575
533
        """Return the id of the file present at path in the target tree."""
581
539
            return None
582
540
        if old_path in self.deleted:
583
541
            return None
584
 
        if getattr(self.base_tree, 'path2id', None) is not None:
 
542
        if hasattr(self.base_tree, 'path2id'):
585
543
            return self.base_tree.path2id(old_path)
586
544
        else:
587
545
            return self.base_tree.inventory.path2id(old_path)
609
567
                return None
610
568
        new_path = self.id2path(file_id)
611
569
        return self.base_tree.path2id(new_path)
612
 
 
 
570
        
613
571
    def get_file(self, file_id):
614
572
        """Return a file-like object containing the new contents of the
615
573
        file given by file_id.
619
577
                then be cached.
620
578
        """
621
579
        base_id = self.old_contents_id(file_id)
622
 
        if (base_id is not None and
623
 
            base_id != self.base_tree.inventory.root.file_id):
 
580
        if base_id is not None:
624
581
            patch_original = self.base_tree.get_file(base_id)
625
582
        else:
626
583
            patch_original = None
627
584
        file_patch = self.patches.get(self.id2path(file_id))
628
585
        if file_patch is None:
629
 
            if (patch_original is None and
 
586
            if (patch_original is None and 
630
587
                self.get_kind(file_id) == 'directory'):
631
588
                return StringIO()
632
 
            if patch_original is None:
633
 
                raise AssertionError("None: %s" % file_id)
 
589
            assert patch_original is not None, "None: %s" % file_id
634
590
            return patch_original
635
591
 
636
 
        if file_patch.startswith('\\'):
637
 
            raise ValueError(
638
 
                'Malformed patch for %s, %r' % (file_id, file_patch))
 
592
        assert not file_patch.startswith('\\'), \
 
593
            'Malformed patch for %s, %r' % (file_id, file_patch)
639
594
        return patched_file(file_patch, patch_original)
640
595
 
641
596
    def get_symlink_target(self, file_id):
688
643
        This need to be called before ever accessing self.inventory
689
644
        """
690
645
        from os.path import dirname, basename
 
646
 
 
647
        assert self.base_tree is not None
691
648
        base_inv = self.base_tree.inventory
692
 
        inv = Inventory(None, self.revision_id)
 
649
        root_id = base_inv.root.file_id
 
650
        try:
 
651
            # New inventories have a unique root_id
 
652
            inv = Inventory(root_id, self.revision_id)
 
653
        except TypeError:
 
654
            inv = Inventory(revision_id=self.revision_id)
693
655
 
694
656
        def add_entry(file_id):
695
657
            path = self.id2path(file_id)
696
658
            if path is None:
697
659
                return
698
 
            if path == '':
699
 
                parent_id = None
 
660
            parent_path = dirname(path)
 
661
            if parent_path == u'':
 
662
                parent_id = root_id
700
663
            else:
701
 
                parent_path = dirname(path)
702
664
                parent_id = self.path2id(parent_path)
703
665
 
704
666
            kind = self.get_kind(file_id)
715
677
                ie.symlink_target = self.get_symlink_target(file_id)
716
678
            ie.revision = revision_id
717
679
 
718
 
            if kind == 'file':
 
680
            if kind in ('directory', 'symlink'):
 
681
                ie.text_size, ie.text_sha1 = None, None
 
682
            else:
719
683
                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)
 
684
            if (ie.text_size is None) and (kind == 'file'):
 
685
                raise BzrError('Got a text_size of None for file_id %r' % file_id)
723
686
            inv.add(ie)
724
687
 
725
688
        sorted_entries = self.sorted_path_id()
726
689
        for path, file_id in sorted_entries:
 
690
            if file_id == inv.root.file_id:
 
691
                continue
727
692
            add_entry(file_id)
728
693
 
729
694
        return inv