/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-03-06 12:28:18 UTC
  • mto: (2321.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: robertc@robertcollins.net-20070306122818-xk0lc3l01ecl6vbc
Get merge_nested finally working: change nested tree iterators to take file_ids, and ensure the right branch is connected to in the merge logic. May not be suitable for shared repositories yet.

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
 
    )
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
 
26
    )
 
27
import bzrlib.errors
 
28
from bzrlib.errors import (TestamentMismatch, BzrError, 
 
29
                           MalformedHeader, MalformedPatches, NotABundle)
 
30
from bzrlib.inventory import (Inventory, InventoryEntry,
 
31
                              InventoryDirectory, InventoryFile,
 
32
                              InventoryLink)
 
33
from bzrlib.osutils import sha_file, sha_string, pathjoin
40
34
from bzrlib.revision import Revision, NULL_REVISION
41
35
from bzrlib.testament import StrictTestament
42
36
from bzrlib.trace import mutter, warning
 
37
import bzrlib.transport
43
38
from bzrlib.tree import Tree
 
39
import bzrlib.urlutils
44
40
from bzrlib.xml5 import serializer_v5
45
41
 
46
42
 
79
75
        if self.properties:
80
76
            for property in self.properties:
81
77
                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:]
 
78
                assert key_end is not None
 
79
                key = property[:key_end].encode('utf-8')
 
80
                value = property[key_end+2:].encode('utf-8')
90
81
                rev.properties[key] = value
91
82
 
92
83
        return rev
93
84
 
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
85
 
108
86
class BundleInfo(object):
109
87
    """This contains the meta information. Stuff that allows you to
110
88
    recreate the revision or inventory XML.
111
89
    """
112
 
    def __init__(self, bundle_format=None):
113
 
        self.bundle_format = None
 
90
    def __init__(self):
114
91
        self.committer = None
115
92
        self.date = None
116
93
        self.message = None
127
104
        self.timestamp = None
128
105
        self.timezone = None
129
106
 
130
 
        # Have we checked the repository yet?
131
 
        self._validated_revisions_against_repo = False
132
 
 
133
107
    def __str__(self):
134
108
        return pprint.pformat(self.__dict__)
135
109
 
138
112
        split up, based on the assumptions that can be made
139
113
        when information is missing.
140
114
        """
141
 
        from bzrlib.timestamp import unpack_highres_date
 
115
        from bzrlib.bundle.serializer import unpack_highres_date
142
116
        # Put in all of the guessable information.
143
117
        if not self.timestamp and self.date:
144
118
            self.timestamp, self.timezone = unpack_highres_date(self.date)
161
135
    def get_base(self, revision):
162
136
        revision_info = self.get_revision_info(revision.revision_id)
163
137
        if revision_info.base_id is not None:
164
 
            return revision_info.base_id
 
138
            if revision_info.base_id == NULL_REVISION:
 
139
                return None
 
140
            else:
 
141
                return revision_info.base_id
165
142
        if len(revision.parent_ids) == 0:
166
143
            # There is no base listed, and
167
144
            # the lowest revision doesn't have a parent
168
145
            # so this is probably against the empty tree
169
 
            # and thus base truly is NULL_REVISION
170
 
            return NULL_REVISION
 
146
            # and thus base truly is None
 
147
            return None
171
148
        else:
172
149
            return revision.parent_ids[-1]
173
150
 
196
173
    def revision_tree(self, repository, revision_id, base=None):
197
174
        revision = self.get_revision(revision_id)
198
175
        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)
 
176
        assert base != revision_id
 
177
        self._validate_references_from_repository(repository)
203
178
        revision_info = self.get_revision_info(revision_id)
204
179
        inventory_revision_id = revision_id
205
 
        bundle_tree = BundleTree(repository.revision_tree(base),
 
180
        bundle_tree = BundleTree(repository.revision_tree(base), 
206
181
                                  inventory_revision_id)
207
182
        self._update_tree(bundle_tree, revision_id)
208
183
 
209
184
        inv = bundle_tree.inventory
210
185
        self._validate_inventory(inv, revision_id)
211
 
        self._validate_revision(bundle_tree, revision_id)
 
186
        self._validate_revision(inv, revision_id)
212
187
 
213
188
        return bundle_tree
214
189
 
241
216
        for rev_info in self.revisions:
242
217
            checked[rev_info.revision_id] = True
243
218
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
244
 
 
 
219
                
245
220
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
246
221
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
247
222
 
249
224
        missing = {}
250
225
        for revision_id, sha1 in rev_to_sha.iteritems():
251
226
            if repository.has_revision(revision_id):
252
 
                testament = StrictTestament.from_revision(repository,
 
227
                testament = StrictTestament.from_revision(repository, 
253
228
                                                          revision_id)
254
229
                local_sha1 = self._testament_sha1_from_revision(repository,
255
230
                                                                revision_id)
256
231
                if sha1 != local_sha1:
257
 
                    raise BzrError('sha1 mismatch. For revision id {%s}'
 
232
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
258
233
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
259
234
                else:
260
235
                    count += 1
261
236
            elif revision_id not in checked:
262
237
                missing[revision_id] = sha1
263
238
 
 
239
        for inv_id, sha1 in inv_to_sha.iteritems():
 
240
            if repository.has_revision(inv_id):
 
241
                # Note: branch.get_inventory_sha1() just returns the value that
 
242
                # is stored in the revision text, and that value may be out
 
243
                # of date. This is bogus, because that means we aren't
 
244
                # validating the actual text, just that we wrote and read the
 
245
                # string. But for now, what the hell.
 
246
                local_sha1 = repository.get_inventory_sha1(inv_id)
 
247
                if sha1 != local_sha1:
 
248
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
 
249
                                   'local: %s, bundle: %s' % 
 
250
                                   (inv_id, local_sha1, sha1))
 
251
                else:
 
252
                    count += 1
 
253
 
264
254
        if len(missing) > 0:
265
255
            # I don't know if this is an error yet
266
256
            warning('Not all revision hashes could be validated.'
267
257
                    ' Unable validate %d hashes' % len(missing))
268
258
        mutter('Verified %d sha hashes for the bundle.' % count)
269
 
        self._validated_revisions_against_repo = True
270
259
 
271
260
    def _validate_inventory(self, inv, revision_id):
272
261
        """At this point we should have generated the BundleTree,
273
262
        so build up an inventory, and make sure the hashes match.
274
263
        """
 
264
 
 
265
        assert inv is not None
 
266
 
275
267
        # Now we should have a complete inventory entry.
276
268
        s = serializer_v5.write_inventory_to_string(inv)
277
269
        sha1 = sha_string(s)
278
270
        # Target revision is the last entry in the real_revisions list
279
271
        rev = self.get_revision(revision_id)
280
 
        if rev.revision_id != revision_id:
281
 
            raise AssertionError()
 
272
        assert rev.revision_id == revision_id
282
273
        if sha1 != rev.inventory_sha1:
283
 
            f = open(',,bogus-inv', 'wb')
284
 
            try:
285
 
                f.write(s)
286
 
            finally:
287
 
                f.close()
 
274
            open(',,bogus-inv', 'wb').write(s)
288
275
            warning('Inventory sha hash mismatch for revision %s. %s'
289
276
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
290
277
 
291
 
    def _validate_revision(self, tree, revision_id):
 
278
    def _validate_revision(self, inventory, revision_id):
292
279
        """Make sure all revision entries match their checksum."""
293
280
 
294
 
        # This is a mapping from each revision id to its sha hash
 
281
        # This is a mapping from each revision id to it's sha hash
295
282
        rev_to_sha1 = {}
296
 
 
 
283
        
297
284
        rev = self.get_revision(revision_id)
298
285
        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)
 
286
        assert rev.revision_id == rev_info.revision_id
 
287
        assert rev.revision_id == revision_id
 
288
        sha1 = self._testament_sha1(rev, inventory)
304
289
        if sha1 != rev_info.sha1:
305
290
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
306
291
        if rev.revision_id in rev_to_sha1:
317
302
 
318
303
        def get_rev_id(last_changed, path, kind):
319
304
            if last_changed is not None:
320
 
                # last_changed will be a Unicode string because of how it was
321
 
                # read. Convert it back to utf8.
322
 
                changed_revision_id = osutils.safe_revision_id(last_changed,
323
 
                                                               warn=False)
 
305
                changed_revision_id = osutils.safe_revision_id(last_changed)
324
306
            else:
325
307
                changed_revision_id = revision_id
326
308
            bundle_tree.note_last_changed(path, changed_revision_id)
333
315
                try:
334
316
                    name, value = info_item.split(':', 1)
335
317
                except ValueError:
336
 
                    raise ValueError('Value %r has no colon' % info_item)
 
318
                    raise 'Value %r has no colon' % info_item
337
319
                if name == 'last-changed':
338
320
                    last_changed = value
339
321
                elif name == 'executable':
 
322
                    assert value in ('yes', 'no'), value
340
323
                    val = (value == 'yes')
341
324
                    bundle_tree.note_executable(new_path, val)
342
325
                elif name == 'target':
346
329
            return last_changed, encoding
347
330
 
348
331
        def do_patch(path, lines, encoding):
349
 
            if encoding == 'base64':
 
332
            if encoding is not None:
 
333
                assert encoding == 'base64'
350
334
                patch = base64.decodestring(''.join(lines))
351
 
            elif encoding is None:
 
335
            else:
352
336
                patch =  ''.join(lines)
353
 
            else:
354
 
                raise ValueError(encoding)
355
337
            bundle_tree.note_patch(path, patch)
356
338
 
357
339
        def renamed(kind, extra, lines):
395
377
                        ': %r' % extra)
396
378
            # This will be Unicode because of how the stream is read. Turn it
397
379
            # back into a utf8 file_id
398
 
            file_id = osutils.safe_file_id(info[1][8:], warn=False)
 
380
            file_id = osutils.safe_file_id(info[1][8:])
399
381
 
400
382
            bundle_tree.note_id(file_id, path, kind)
401
383
            # this will be overridden in extra_info if executable is specified.
417
399
            revision = get_rev_id(last_modified, path, kind)
418
400
            if lines:
419
401
                do_patch(path, lines, encoding)
420
 
 
 
402
            
421
403
        valid_actions = {
422
404
            'renamed':renamed,
423
405
            'removed':removed,
446
428
                        ' (unrecognized action): %r' % action_line)
447
429
            valid_actions[action](kind, extra, lines)
448
430
 
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
 
        """
455
 
        apply_bundle.install_bundle(target_repo, self)
456
 
        return self.target
457
 
 
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
431
 
466
432
class BundleTree(Tree):
467
 
 
468
433
    def __init__(self, base_tree, revision_id):
469
434
        self.base_tree = base_tree
470
435
        self._renamed = {} # Mapping from old_path => new_path
486
451
 
487
452
    def note_rename(self, old_path, new_path):
488
453
        """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)
 
454
        assert new_path not in self._renamed
 
455
        assert old_path not in self._renamed_r
493
456
        self._renamed[new_path] = old_path
494
457
        self._renamed_r[old_path] = new_path
495
458
 
525
488
 
526
489
    def old_path(self, new_path):
527
490
        """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)
 
491
        assert new_path[:1] not in ('\\', '/')
530
492
        old_path = self._renamed.get(new_path)
531
493
        if old_path is not None:
532
494
            return old_path
546
508
        #renamed_r
547
509
        if old_path in self._renamed_r:
548
510
            return None
549
 
        return old_path
 
511
        return old_path 
550
512
 
551
513
    def new_path(self, old_path):
552
514
        """Get the new_path (path in the target_tree) for the file at old_path
553
515
        in the base tree.
554
516
        """
555
 
        if old_path[:1] in ('\\', '/'):
556
 
            raise ValueError(old_path)
 
517
        assert old_path[:1] not in ('\\', '/')
557
518
        new_path = self._renamed_r.get(old_path)
558
519
        if new_path is not None:
559
520
            return new_path
572
533
        #renamed_r
573
534
        if new_path in self._renamed:
574
535
            return None
575
 
        return new_path
 
536
        return new_path 
576
537
 
577
538
    def path2id(self, path):
578
539
        """Return the id of the file present at path in the target tree."""
612
573
                return None
613
574
        new_path = self.id2path(file_id)
614
575
        return self.base_tree.path2id(new_path)
615
 
 
 
576
        
616
577
    def get_file(self, file_id):
617
578
        """Return a file-like object containing the new contents of the
618
579
        file given by file_id.
629
590
            patch_original = None
630
591
        file_patch = self.patches.get(self.id2path(file_id))
631
592
        if file_patch is None:
632
 
            if (patch_original is None and
 
593
            if (patch_original is None and 
633
594
                self.get_kind(file_id) == 'directory'):
634
595
                return StringIO()
635
 
            if patch_original is None:
636
 
                raise AssertionError("None: %s" % file_id)
 
596
            assert patch_original is not None, "None: %s" % file_id
637
597
            return patch_original
638
598
 
639
 
        if file_patch.startswith('\\'):
640
 
            raise ValueError(
641
 
                'Malformed patch for %s, %r' % (file_id, file_patch))
 
599
        assert not file_patch.startswith('\\'), \
 
600
            'Malformed patch for %s, %r' % (file_id, file_patch)
642
601
        return patched_file(file_patch, patch_original)
643
602
 
644
603
    def get_symlink_target(self, file_id):
664
623
        path = self.id2path(file_id)
665
624
        if path in self._last_changed:
666
625
            return self._last_changed[path]
667
 
        return self.base_tree.get_file_revision(file_id)
 
626
        return self.base_tree.inventory[file_id].revision
668
627
 
669
628
    def get_size_and_sha1(self, file_id):
670
629
        """Return the size and sha1 hash of the given file id.
691
650
        This need to be called before ever accessing self.inventory
692
651
        """
693
652
        from os.path import dirname, basename
 
653
 
 
654
        assert self.base_tree is not None
694
655
        base_inv = self.base_tree.inventory
695
656
        inv = Inventory(None, self.revision_id)
696
657
 
718
679
                ie.symlink_target = self.get_symlink_target(file_id)
719
680
            ie.revision = revision_id
720
681
 
721
 
            if kind == 'file':
 
682
            if kind in ('directory', 'symlink'):
 
683
                ie.text_size, ie.text_sha1 = None, None
 
684
            else:
722
685
                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)
 
686
            if (ie.text_size is None) and (kind == 'file'):
 
687
                raise BzrError('Got a text_size of None for file_id %r' % file_id)
726
688
            inv.add(ie)
727
689
 
728
690
        sorted_entries = self.sorted_path_id()
742
704
        for path, entry in self.inventory.iter_entries():
743
705
            yield entry.file_id
744
706
 
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
707
    def sorted_path_id(self):
763
708
        paths = []
764
709
        for result in self._new_id.iteritems():