~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: v.ladeuil+lp at free
  • Date: 2007-05-15 17:40:32 UTC
  • mto: (2485.8.44 bzr.connection.sharing)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070515174032-qzdkangpv29l9e7g
Add a test that check that init connect only once. It fails.

* __init__.py:
(test_suite): Register the new test class.

* test_init.py: 
(InstrumentedTransport): A transport that can track connections.
(TransportHooks): Transport specific hooks.
(TestInit): Iniit command behavior tests.

* ftp.py:
(FtpTransport.__init__): Mark place that need fixing regarding
transport connection sharing

* builtins.py:
(cmd_init.run): Mark places that need fixing regarding transport
connection sharing.

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
 
80
77
            for property in self.properties:
81
78
                key_end = property.find(': ')
82
79
                if key_end == -1:
83
 
                    if not property.endswith(':'):
84
 
                        raise ValueError(property)
 
80
                    assert property.endswith(':')
85
81
                    key = str(property[:-1])
86
82
                    value = ''
87
83
                else:
91
87
 
92
88
        return rev
93
89
 
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
90
 
108
91
class BundleInfo(object):
109
92
    """This contains the meta information. Stuff that allows you to
110
93
    recreate the revision or inventory XML.
111
94
    """
112
 
    def __init__(self, bundle_format=None):
113
 
        self.bundle_format = None
 
95
    def __init__(self):
114
96
        self.committer = None
115
97
        self.date = None
116
98
        self.message = None
161
143
    def get_base(self, revision):
162
144
        revision_info = self.get_revision_info(revision.revision_id)
163
145
        if revision_info.base_id is not None:
164
 
            return revision_info.base_id
 
146
            if revision_info.base_id == NULL_REVISION:
 
147
                return None
 
148
            else:
 
149
                return revision_info.base_id
165
150
        if len(revision.parent_ids) == 0:
166
151
            # There is no base listed, and
167
152
            # the lowest revision doesn't have a parent
168
153
            # so this is probably against the empty tree
169
 
            # and thus base truly is NULL_REVISION
170
 
            return NULL_REVISION
 
154
            # and thus base truly is None
 
155
            return None
171
156
        else:
172
157
            return revision.parent_ids[-1]
173
158
 
194
179
        raise KeyError(revision_id)
195
180
 
196
181
    def revision_tree(self, repository, revision_id, base=None):
 
182
        revision_id = osutils.safe_revision_id(revision_id)
197
183
        revision = self.get_revision(revision_id)
198
184
        base = self.get_base(revision)
199
 
        if base == revision_id:
200
 
            raise AssertionError()
 
185
        assert base != revision_id
201
186
        if not self._validated_revisions_against_repo:
202
187
            self._validate_references_from_repository(repository)
203
188
        revision_info = self.get_revision_info(revision_id)
204
189
        inventory_revision_id = revision_id
205
 
        bundle_tree = BundleTree(repository.revision_tree(base),
 
190
        bundle_tree = BundleTree(repository.revision_tree(base), 
206
191
                                  inventory_revision_id)
207
192
        self._update_tree(bundle_tree, revision_id)
208
193
 
209
194
        inv = bundle_tree.inventory
210
195
        self._validate_inventory(inv, revision_id)
211
 
        self._validate_revision(bundle_tree, revision_id)
 
196
        self._validate_revision(inv, revision_id)
212
197
 
213
198
        return bundle_tree
214
199
 
241
226
        for rev_info in self.revisions:
242
227
            checked[rev_info.revision_id] = True
243
228
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
244
 
 
 
229
                
245
230
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
246
231
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
247
232
 
249
234
        missing = {}
250
235
        for revision_id, sha1 in rev_to_sha.iteritems():
251
236
            if repository.has_revision(revision_id):
252
 
                testament = StrictTestament.from_revision(repository,
 
237
                testament = StrictTestament.from_revision(repository, 
253
238
                                                          revision_id)
254
239
                local_sha1 = self._testament_sha1_from_revision(repository,
255
240
                                                                revision_id)
256
241
                if sha1 != local_sha1:
257
 
                    raise BzrError('sha1 mismatch. For revision id {%s}'
 
242
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
258
243
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
259
244
                else:
260
245
                    count += 1
261
246
            elif revision_id not in checked:
262
247
                missing[revision_id] = sha1
263
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
 
264
264
        if len(missing) > 0:
265
265
            # I don't know if this is an error yet
266
266
            warning('Not all revision hashes could be validated.'
272
272
        """At this point we should have generated the BundleTree,
273
273
        so build up an inventory, and make sure the hashes match.
274
274
        """
 
275
 
 
276
        assert inv is not None
 
277
 
275
278
        # Now we should have a complete inventory entry.
276
279
        s = serializer_v5.write_inventory_to_string(inv)
277
280
        sha1 = sha_string(s)
278
281
        # Target revision is the last entry in the real_revisions list
279
282
        rev = self.get_revision(revision_id)
280
 
        if rev.revision_id != revision_id:
281
 
            raise AssertionError()
 
283
        assert rev.revision_id == revision_id
282
284
        if sha1 != rev.inventory_sha1:
283
 
            f = open(',,bogus-inv', 'wb')
284
 
            try:
285
 
                f.write(s)
286
 
            finally:
287
 
                f.close()
 
285
            open(',,bogus-inv', 'wb').write(s)
288
286
            warning('Inventory sha hash mismatch for revision %s. %s'
289
287
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
290
288
 
291
 
    def _validate_revision(self, tree, revision_id):
 
289
    def _validate_revision(self, inventory, revision_id):
292
290
        """Make sure all revision entries match their checksum."""
293
291
 
294
 
        # This is a mapping from each revision id to its sha hash
 
292
        # This is a mapping from each revision id to it's sha hash
295
293
        rev_to_sha1 = {}
296
 
 
 
294
        
297
295
        rev = self.get_revision(revision_id)
298
296
        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)
 
297
        assert rev.revision_id == rev_info.revision_id
 
298
        assert rev.revision_id == revision_id
 
299
        sha1 = self._testament_sha1(rev, inventory)
304
300
        if sha1 != rev_info.sha1:
305
301
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
306
302
        if rev.revision_id in rev_to_sha1:
333
329
                try:
334
330
                    name, value = info_item.split(':', 1)
335
331
                except ValueError:
336
 
                    raise ValueError('Value %r has no colon' % info_item)
 
332
                    raise 'Value %r has no colon' % info_item
337
333
                if name == 'last-changed':
338
334
                    last_changed = value
339
335
                elif name == 'executable':
 
336
                    assert value in ('yes', 'no'), value
340
337
                    val = (value == 'yes')
341
338
                    bundle_tree.note_executable(new_path, val)
342
339
                elif name == 'target':
346
343
            return last_changed, encoding
347
344
 
348
345
        def do_patch(path, lines, encoding):
349
 
            if encoding == 'base64':
 
346
            if encoding is not None:
 
347
                assert encoding == 'base64'
350
348
                patch = base64.decodestring(''.join(lines))
351
 
            elif encoding is None:
 
349
            else:
352
350
                patch =  ''.join(lines)
353
 
            else:
354
 
                raise ValueError(encoding)
355
351
            bundle_tree.note_patch(path, patch)
356
352
 
357
353
        def renamed(kind, extra, lines):
417
413
            revision = get_rev_id(last_modified, path, kind)
418
414
            if lines:
419
415
                do_patch(path, lines, encoding)
420
 
 
 
416
            
421
417
        valid_actions = {
422
418
            'renamed':renamed,
423
419
            'removed':removed,
446
442
                        ' (unrecognized action): %r' % action_line)
447
443
            valid_actions[action](kind, extra, lines)
448
444
 
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
 
        """
 
445
    def install_revisions(self, target_repo):
 
446
        """Install revisions and return the target revision"""
455
447
        apply_bundle.install_bundle(target_repo, self)
456
448
        return self.target
457
449
 
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
450
 
466
451
class BundleTree(Tree):
467
 
 
468
452
    def __init__(self, base_tree, revision_id):
469
453
        self.base_tree = base_tree
470
454
        self._renamed = {} # Mapping from old_path => new_path
486
470
 
487
471
    def note_rename(self, old_path, new_path):
488
472
        """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)
 
473
        assert new_path not in self._renamed
 
474
        assert old_path not in self._renamed_r
493
475
        self._renamed[new_path] = old_path
494
476
        self._renamed_r[old_path] = new_path
495
477
 
525
507
 
526
508
    def old_path(self, new_path):
527
509
        """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)
 
510
        assert new_path[:1] not in ('\\', '/')
530
511
        old_path = self._renamed.get(new_path)
531
512
        if old_path is not None:
532
513
            return old_path
546
527
        #renamed_r
547
528
        if old_path in self._renamed_r:
548
529
            return None
549
 
        return old_path
 
530
        return old_path 
550
531
 
551
532
    def new_path(self, old_path):
552
533
        """Get the new_path (path in the target_tree) for the file at old_path
553
534
        in the base tree.
554
535
        """
555
 
        if old_path[:1] in ('\\', '/'):
556
 
            raise ValueError(old_path)
 
536
        assert old_path[:1] not in ('\\', '/')
557
537
        new_path = self._renamed_r.get(old_path)
558
538
        if new_path is not None:
559
539
            return new_path
572
552
        #renamed_r
573
553
        if new_path in self._renamed:
574
554
            return None
575
 
        return new_path
 
555
        return new_path 
576
556
 
577
557
    def path2id(self, path):
578
558
        """Return the id of the file present at path in the target tree."""
612
592
                return None
613
593
        new_path = self.id2path(file_id)
614
594
        return self.base_tree.path2id(new_path)
615
 
 
 
595
        
616
596
    def get_file(self, file_id):
617
597
        """Return a file-like object containing the new contents of the
618
598
        file given by file_id.
629
609
            patch_original = None
630
610
        file_patch = self.patches.get(self.id2path(file_id))
631
611
        if file_patch is None:
632
 
            if (patch_original is None and
 
612
            if (patch_original is None and 
633
613
                self.get_kind(file_id) == 'directory'):
634
614
                return StringIO()
635
 
            if patch_original is None:
636
 
                raise AssertionError("None: %s" % file_id)
 
615
            assert patch_original is not None, "None: %s" % file_id
637
616
            return patch_original
638
617
 
639
 
        if file_patch.startswith('\\'):
640
 
            raise ValueError(
641
 
                '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)
642
620
        return patched_file(file_patch, patch_original)
643
621
 
644
 
    def get_symlink_target(self, file_id, path=None):
645
 
        if path is None:
646
 
            path = self.id2path(file_id)
 
622
    def get_symlink_target(self, file_id):
 
623
        new_path = self.id2path(file_id)
647
624
        try:
648
 
            return self._targets[path]
 
625
            return self._targets[new_path]
649
626
        except KeyError:
650
627
            return self.base_tree.get_symlink_target(file_id)
651
628
 
665
642
        path = self.id2path(file_id)
666
643
        if path in self._last_changed:
667
644
            return self._last_changed[path]
668
 
        return self.base_tree.get_file_revision(file_id)
 
645
        return self.base_tree.inventory[file_id].revision
669
646
 
670
647
    def get_size_and_sha1(self, file_id):
671
648
        """Return the size and sha1 hash of the given file id.
692
669
        This need to be called before ever accessing self.inventory
693
670
        """
694
671
        from os.path import dirname, basename
 
672
 
 
673
        assert self.base_tree is not None
695
674
        base_inv = self.base_tree.inventory
696
675
        inv = Inventory(None, self.revision_id)
697
676
 
716
695
                ie.executable = self.is_executable(file_id)
717
696
            elif kind == 'symlink':
718
697
                ie = InventoryLink(file_id, name, parent_id)
719
 
                ie.symlink_target = self.get_symlink_target(file_id, path)
 
698
                ie.symlink_target = self.get_symlink_target(file_id)
720
699
            ie.revision = revision_id
721
700
 
722
 
            if kind == 'file':
 
701
            if kind in ('directory', 'symlink'):
 
702
                ie.text_size, ie.text_sha1 = None, None
 
703
            else:
723
704
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
724
 
                if ie.text_size is None:
725
 
                    raise BzrError(
726
 
                        '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)
727
707
            inv.add(ie)
728
708
 
729
709
        sorted_entries = self.sorted_path_id()
743
723
        for path, entry in self.inventory.iter_entries():
744
724
            yield entry.file_id
745
725
 
746
 
    def list_files(self, include_root=False, from_dir=None, recursive=True):
747
 
        # The only files returned by this are those from the version
748
 
        inv = self.inventory
749
 
        if from_dir is None:
750
 
            from_dir_id = None
751
 
        else:
752
 
            from_dir_id = inv.path2id(from_dir)
753
 
            if from_dir_id is None:
754
 
                # Directory not versioned
755
 
                return
756
 
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
757
 
        if inv.root is not None and not include_root and from_dir is None:
758
 
            # skip the root for compatability with the current apis.
759
 
            entries.next()
760
 
        for path, entry in entries:
761
 
            yield path, 'V', entry.kind, entry.file_id, entry
762
 
 
763
726
    def sorted_path_id(self):
764
727
        paths = []
765
728
        for result in self._new_id.iteritems():
766
729
            paths.append(result)
767
 
        for id in self.base_tree.all_file_ids():
 
730
        for id in self.base_tree:
768
731
            path = self.id2path(id)
769
732
            if path is None:
770
733
                continue