~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: Patch Queue Manager
  • Date: 2015-09-30 16:43:21 UTC
  • mfrom: (6603.2.2 fix-keep-dirty)
  • Revision ID: pqm@pqm.ubuntu.com-20150930164321-ct2v2qnmvimqt8qf
(vila) Avoid associating dirty patch headers with the previous file in the
 patch. (Colin Watson)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
 
19
21
import base64
20
22
from cStringIO import StringIO
21
23
import os
25
27
    osutils,
26
28
    timestamp,
27
29
    )
28
 
import bzrlib.errors
29
30
from bzrlib.bundle import apply_bundle
30
 
from bzrlib.errors import (TestamentMismatch, BzrError, 
31
 
                           MalformedHeader, MalformedPatches, NotABundle)
32
 
from bzrlib.inventory import (Inventory, InventoryEntry,
33
 
                              InventoryDirectory, InventoryFile,
34
 
                              InventoryLink)
35
 
from bzrlib.osutils import sha_file, sha_string, pathjoin
 
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
36
42
from bzrlib.revision import Revision, NULL_REVISION
37
43
from bzrlib.testament import StrictTestament
38
44
from bzrlib.trace import mutter, warning
39
 
import bzrlib.transport
40
45
from bzrlib.tree import Tree
41
 
import bzrlib.urlutils
42
46
from bzrlib.xml5 import serializer_v5
43
47
 
44
48
 
78
82
            for property in self.properties:
79
83
                key_end = property.find(': ')
80
84
                if key_end == -1:
81
 
                    assert property.endswith(':')
 
85
                    if not property.endswith(':'):
 
86
                        raise ValueError(property)
82
87
                    key = str(property[:-1])
83
88
                    value = ''
84
89
                else:
158
163
    def get_base(self, revision):
159
164
        revision_info = self.get_revision_info(revision.revision_id)
160
165
        if revision_info.base_id is not None:
161
 
            if revision_info.base_id == NULL_REVISION:
162
 
                return None
163
 
            else:
164
 
                return revision_info.base_id
 
166
            return revision_info.base_id
165
167
        if len(revision.parent_ids) == 0:
166
168
            # There is no base listed, and
167
169
            # the lowest revision doesn't have a parent
168
170
            # so this is probably against the empty tree
169
 
            # and thus base truly is None
170
 
            return None
 
171
            # and thus base truly is NULL_REVISION
 
172
            return NULL_REVISION
171
173
        else:
172
174
            return revision.parent_ids[-1]
173
175
 
196
198
    def revision_tree(self, repository, revision_id, base=None):
197
199
        revision = self.get_revision(revision_id)
198
200
        base = self.get_base(revision)
199
 
        assert base != revision_id
 
201
        if base == revision_id:
 
202
            raise AssertionError()
200
203
        if not self._validated_revisions_against_repo:
201
204
            self._validate_references_from_repository(repository)
202
205
        revision_info = self.get_revision_info(revision_id)
203
206
        inventory_revision_id = revision_id
204
 
        bundle_tree = BundleTree(repository.revision_tree(base), 
 
207
        bundle_tree = BundleTree(repository.revision_tree(base),
205
208
                                  inventory_revision_id)
206
209
        self._update_tree(bundle_tree, revision_id)
207
210
 
208
211
        inv = bundle_tree.inventory
209
212
        self._validate_inventory(inv, revision_id)
210
 
        self._validate_revision(inv, revision_id)
 
213
        self._validate_revision(bundle_tree, revision_id)
211
214
 
212
215
        return bundle_tree
213
216
 
240
243
        for rev_info in self.revisions:
241
244
            checked[rev_info.revision_id] = True
242
245
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
243
 
                
 
246
 
244
247
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
245
248
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
246
249
 
248
251
        missing = {}
249
252
        for revision_id, sha1 in rev_to_sha.iteritems():
250
253
            if repository.has_revision(revision_id):
251
 
                testament = StrictTestament.from_revision(repository, 
 
254
                testament = StrictTestament.from_revision(repository,
252
255
                                                          revision_id)
253
256
                local_sha1 = self._testament_sha1_from_revision(repository,
254
257
                                                                revision_id)
255
258
                if sha1 != local_sha1:
256
 
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
 
259
                    raise BzrError('sha1 mismatch. For revision id {%s}'
257
260
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
258
261
                else:
259
262
                    count += 1
260
263
            elif revision_id not in checked:
261
264
                missing[revision_id] = sha1
262
265
 
263
 
        for inv_id, sha1 in inv_to_sha.iteritems():
264
 
            if repository.has_revision(inv_id):
265
 
                # Note: branch.get_inventory_sha1() just returns the value that
266
 
                # is stored in the revision text, and that value may be out
267
 
                # of date. This is bogus, because that means we aren't
268
 
                # validating the actual text, just that we wrote and read the
269
 
                # string. But for now, what the hell.
270
 
                local_sha1 = repository.get_inventory_sha1(inv_id)
271
 
                if sha1 != local_sha1:
272
 
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
273
 
                                   'local: %s, bundle: %s' % 
274
 
                                   (inv_id, local_sha1, sha1))
275
 
                else:
276
 
                    count += 1
277
 
 
278
266
        if len(missing) > 0:
279
267
            # I don't know if this is an error yet
280
268
            warning('Not all revision hashes could be validated.'
286
274
        """At this point we should have generated the BundleTree,
287
275
        so build up an inventory, and make sure the hashes match.
288
276
        """
289
 
 
290
 
        assert inv is not None
291
 
 
292
277
        # Now we should have a complete inventory entry.
293
278
        s = serializer_v5.write_inventory_to_string(inv)
294
279
        sha1 = sha_string(s)
295
280
        # Target revision is the last entry in the real_revisions list
296
281
        rev = self.get_revision(revision_id)
297
 
        assert rev.revision_id == revision_id
 
282
        if rev.revision_id != revision_id:
 
283
            raise AssertionError()
298
284
        if sha1 != rev.inventory_sha1:
299
 
            open(',,bogus-inv', 'wb').write(s)
 
285
            f = open(',,bogus-inv', 'wb')
 
286
            try:
 
287
                f.write(s)
 
288
            finally:
 
289
                f.close()
300
290
            warning('Inventory sha hash mismatch for revision %s. %s'
301
291
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
302
292
 
303
 
    def _validate_revision(self, inventory, revision_id):
 
293
    def _validate_revision(self, tree, revision_id):
304
294
        """Make sure all revision entries match their checksum."""
305
295
 
306
 
        # This is a mapping from each revision id to it's sha hash
 
296
        # This is a mapping from each revision id to its sha hash
307
297
        rev_to_sha1 = {}
308
 
        
 
298
 
309
299
        rev = self.get_revision(revision_id)
310
300
        rev_info = self.get_revision_info(revision_id)
311
 
        assert rev.revision_id == rev_info.revision_id
312
 
        assert rev.revision_id == revision_id
313
 
        sha1 = self._testament_sha1(rev, inventory)
 
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)
314
306
        if sha1 != rev_info.sha1:
315
307
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
316
308
        if rev.revision_id in rev_to_sha1:
343
335
                try:
344
336
                    name, value = info_item.split(':', 1)
345
337
                except ValueError:
346
 
                    raise 'Value %r has no colon' % info_item
 
338
                    raise ValueError('Value %r has no colon' % info_item)
347
339
                if name == 'last-changed':
348
340
                    last_changed = value
349
341
                elif name == 'executable':
350
 
                    assert value in ('yes', 'no'), value
351
342
                    val = (value == 'yes')
352
343
                    bundle_tree.note_executable(new_path, val)
353
344
                elif name == 'target':
357
348
            return last_changed, encoding
358
349
 
359
350
        def do_patch(path, lines, encoding):
360
 
            if encoding is not None:
361
 
                assert encoding == 'base64'
 
351
            if encoding == 'base64':
362
352
                patch = base64.decodestring(''.join(lines))
363
 
            else:
 
353
            elif encoding is None:
364
354
                patch =  ''.join(lines)
 
355
            else:
 
356
                raise ValueError(encoding)
365
357
            bundle_tree.note_patch(path, patch)
366
358
 
367
359
        def renamed(kind, extra, lines):
427
419
            revision = get_rev_id(last_modified, path, kind)
428
420
            if lines:
429
421
                do_patch(path, lines, encoding)
430
 
            
 
422
 
431
423
        valid_actions = {
432
424
            'renamed':renamed,
433
425
            'removed':removed,
474
466
 
475
467
 
476
468
class BundleTree(Tree):
 
469
 
477
470
    def __init__(self, base_tree, revision_id):
478
471
        self.base_tree = base_tree
479
472
        self._renamed = {} # Mapping from old_path => new_path
495
488
 
496
489
    def note_rename(self, old_path, new_path):
497
490
        """A file/directory has been renamed from old_path => new_path"""
498
 
        assert new_path not in self._renamed
499
 
        assert old_path not in self._renamed_r
 
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)
500
495
        self._renamed[new_path] = old_path
501
496
        self._renamed_r[old_path] = new_path
502
497
 
532
527
 
533
528
    def old_path(self, new_path):
534
529
        """Get the old_path (path in the base_tree) for the file at new_path"""
535
 
        assert new_path[:1] not in ('\\', '/')
 
530
        if new_path[:1] in ('\\', '/'):
 
531
            raise ValueError(new_path)
536
532
        old_path = self._renamed.get(new_path)
537
533
        if old_path is not None:
538
534
            return old_path
552
548
        #renamed_r
553
549
        if old_path in self._renamed_r:
554
550
            return None
555
 
        return old_path 
 
551
        return old_path
556
552
 
557
553
    def new_path(self, old_path):
558
554
        """Get the new_path (path in the target_tree) for the file at old_path
559
555
        in the base tree.
560
556
        """
561
 
        assert old_path[:1] not in ('\\', '/')
 
557
        if old_path[:1] in ('\\', '/'):
 
558
            raise ValueError(old_path)
562
559
        new_path = self._renamed_r.get(old_path)
563
560
        if new_path is not None:
564
561
            return new_path
577
574
        #renamed_r
578
575
        if new_path in self._renamed:
579
576
            return None
580
 
        return new_path 
 
577
        return new_path
 
578
 
 
579
    def get_root_id(self):
 
580
        return self.path2id('')
581
581
 
582
582
    def path2id(self, path):
583
583
        """Return the id of the file present at path in the target tree."""
589
589
            return None
590
590
        if old_path in self.deleted:
591
591
            return None
592
 
        if getattr(self.base_tree, 'path2id', None) is not None:
593
 
            return self.base_tree.path2id(old_path)
594
 
        else:
595
 
            return self.base_tree.inventory.path2id(old_path)
 
592
        return self.base_tree.path2id(old_path)
596
593
 
597
594
    def id2path(self, file_id):
598
595
        """Return the new path in the target tree of the file with id file_id"""
617
614
                return None
618
615
        new_path = self.id2path(file_id)
619
616
        return self.base_tree.path2id(new_path)
620
 
        
 
617
 
621
618
    def get_file(self, file_id):
622
619
        """Return a file-like object containing the new contents of the
623
620
        file given by file_id.
628
625
        """
629
626
        base_id = self.old_contents_id(file_id)
630
627
        if (base_id is not None and
631
 
            base_id != self.base_tree.inventory.root.file_id):
 
628
            base_id != self.base_tree.get_root_id()):
632
629
            patch_original = self.base_tree.get_file(base_id)
633
630
        else:
634
631
            patch_original = None
635
632
        file_patch = self.patches.get(self.id2path(file_id))
636
633
        if file_patch is None:
637
 
            if (patch_original is None and 
638
 
                self.get_kind(file_id) == 'directory'):
 
634
            if (patch_original is None and
 
635
                self.kind(file_id) == 'directory'):
639
636
                return StringIO()
640
 
            assert patch_original is not None, "None: %s" % file_id
 
637
            if patch_original is None:
 
638
                raise AssertionError("None: %s" % file_id)
641
639
            return patch_original
642
640
 
643
 
        assert not file_patch.startswith('\\'), \
644
 
            'Malformed patch for %s, %r' % (file_id, file_patch)
 
641
        if file_patch.startswith('\\'):
 
642
            raise ValueError(
 
643
                'Malformed patch for %s, %r' % (file_id, file_patch))
645
644
        return patched_file(file_patch, patch_original)
646
645
 
647
 
    def get_symlink_target(self, file_id):
648
 
        new_path = self.id2path(file_id)
 
646
    def get_symlink_target(self, file_id, path=None):
 
647
        if path is None:
 
648
            path = self.id2path(file_id)
649
649
        try:
650
 
            return self._targets[new_path]
 
650
            return self._targets[path]
651
651
        except KeyError:
652
652
            return self.base_tree.get_symlink_target(file_id)
653
653
 
654
 
    def get_kind(self, file_id):
 
654
    def kind(self, file_id):
655
655
        if file_id in self._kinds:
656
656
            return self._kinds[file_id]
657
 
        return self.base_tree.inventory[file_id].kind
 
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)
658
665
 
659
666
    def is_executable(self, file_id):
660
667
        path = self.id2path(file_id)
661
668
        if path in self._executable:
662
669
            return self._executable[path]
663
670
        else:
664
 
            return self.base_tree.inventory[file_id].executable
 
671
            return self.base_tree.is_executable(file_id)
665
672
 
666
673
    def get_last_changed(self, file_id):
667
674
        path = self.id2path(file_id)
668
675
        if path in self._last_changed:
669
676
            return self._last_changed[path]
670
 
        return self.base_tree.inventory[file_id].revision
 
677
        return self.base_tree.get_file_revision(file_id)
671
678
 
672
679
    def get_size_and_sha1(self, file_id):
673
680
        """Return the size and sha1 hash of the given file id.
680
687
        if new_path not in self.patches:
681
688
            # If the entry does not have a patch, then the
682
689
            # contents must be the same as in the base_tree
683
 
            ie = self.base_tree.inventory[file_id]
684
 
            if ie.text_size is None:
685
 
                return ie.text_size, ie.text_sha1
686
 
            return int(ie.text_size), ie.text_sha1
 
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
687
693
        fileobj = self.get_file(file_id)
688
694
        content = fileobj.read()
689
695
        return len(content), sha_string(content)
694
700
        This need to be called before ever accessing self.inventory
695
701
        """
696
702
        from os.path import dirname, basename
697
 
 
698
 
        assert self.base_tree is not None
699
 
        base_inv = self.base_tree.inventory
700
703
        inv = Inventory(None, self.revision_id)
701
704
 
702
705
        def add_entry(file_id):
709
712
                parent_path = dirname(path)
710
713
                parent_id = self.path2id(parent_path)
711
714
 
712
 
            kind = self.get_kind(file_id)
 
715
            kind = self.kind(file_id)
713
716
            revision_id = self.get_last_changed(file_id)
714
717
 
715
718
            name = basename(path)
720
723
                ie.executable = self.is_executable(file_id)
721
724
            elif kind == 'symlink':
722
725
                ie = InventoryLink(file_id, name, parent_id)
723
 
                ie.symlink_target = self.get_symlink_target(file_id)
 
726
                ie.symlink_target = self.get_symlink_target(file_id, path)
724
727
            ie.revision = revision_id
725
728
 
726
 
            if kind in ('directory', 'symlink'):
727
 
                ie.text_size, ie.text_sha1 = None, None
728
 
            else:
 
729
            if kind == 'file':
729
730
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
730
 
            if (ie.text_size is None) and (kind == 'file'):
731
 
                raise BzrError('Got a text_size of None for file_id %r' % 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)
732
734
            inv.add(ie)
733
735
 
734
736
        sorted_entries = self.sorted_path_id()
744
746
    # at that instant
745
747
    inventory = property(_get_inventory)
746
748
 
747
 
    def __iter__(self):
748
 
        for path, entry in self.inventory.iter_entries():
749
 
            yield entry.file_id
 
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
750
771
 
751
772
    def sorted_path_id(self):
752
773
        paths = []
753
774
        for result in self._new_id.iteritems():
754
775
            paths.append(result)
755
 
        for id in self.base_tree:
 
776
        for id in self.base_tree.all_file_ids():
756
777
            path = self.id2path(id)
757
778
            if path is None:
758
779
                continue