~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: Vincent Ladeuil
  • Date: 2010-02-03 07:18:36 UTC
  • mto: (5008.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5009.
  • Revision ID: v.ladeuil+lp@free.fr-20100203071836-u9b86q68fr9ri5s6
Fix NEWS.

Show diffs side-by-side

added added

removed removed

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