~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: Martin Pool
  • Date: 2007-04-04 06:17:31 UTC
  • mto: This revision was merged to the branch mainline in revision 2397.
  • Revision ID: mbp@sourcefrog.net-20070404061731-tt2xrzllqhbodn83
Contents of TODO file moved into bug tracker

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
    )
28
27
import bzrlib.errors
29
28
from bzrlib.bundle import apply_bundle
30
 
from bzrlib.errors import (TestamentMismatch, BzrError,
 
29
from bzrlib.errors import (TestamentMismatch, BzrError, 
31
30
                           MalformedHeader, MalformedPatches, NotABundle)
32
31
from bzrlib.inventory import (Inventory, InventoryEntry,
33
32
                              InventoryDirectory, InventoryFile,
77
76
        if self.properties:
78
77
            for property in self.properties:
79
78
                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:]
 
79
                assert key_end is not None
 
80
                key = property[:key_end].encode('utf-8')
 
81
                value = property[key_end+2:].encode('utf-8')
88
82
                rev.properties[key] = value
89
83
 
90
84
        return rev
91
85
 
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
86
 
106
87
class BundleInfo(object):
107
88
    """This contains the meta information. Stuff that allows you to
108
89
    recreate the revision or inventory XML.
109
90
    """
110
 
    def __init__(self, bundle_format=None):
111
 
        self.bundle_format = None
 
91
    def __init__(self):
112
92
        self.committer = None
113
93
        self.date = None
114
94
        self.message = None
125
105
        self.timestamp = None
126
106
        self.timezone = None
127
107
 
128
 
        # Have we checked the repository yet?
129
 
        self._validated_revisions_against_repo = False
130
 
 
131
108
    def __str__(self):
132
109
        return pprint.pformat(self.__dict__)
133
110
 
159
136
    def get_base(self, revision):
160
137
        revision_info = self.get_revision_info(revision.revision_id)
161
138
        if revision_info.base_id is not None:
162
 
            return revision_info.base_id
 
139
            if revision_info.base_id == NULL_REVISION:
 
140
                return None
 
141
            else:
 
142
                return revision_info.base_id
163
143
        if len(revision.parent_ids) == 0:
164
144
            # There is no base listed, and
165
145
            # the lowest revision doesn't have a parent
166
146
            # so this is probably against the empty tree
167
 
            # and thus base truly is NULL_REVISION
168
 
            return NULL_REVISION
 
147
            # and thus base truly is None
 
148
            return None
169
149
        else:
170
150
            return revision.parent_ids[-1]
171
151
 
192
172
        raise KeyError(revision_id)
193
173
 
194
174
    def revision_tree(self, repository, revision_id, base=None):
 
175
        revision_id = osutils.safe_revision_id(revision_id)
195
176
        revision = self.get_revision(revision_id)
196
177
        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)
 
178
        assert base != revision_id
 
179
        self._validate_references_from_repository(repository)
201
180
        revision_info = self.get_revision_info(revision_id)
202
181
        inventory_revision_id = revision_id
203
 
        bundle_tree = BundleTree(repository.revision_tree(base),
 
182
        bundle_tree = BundleTree(repository.revision_tree(base), 
204
183
                                  inventory_revision_id)
205
184
        self._update_tree(bundle_tree, revision_id)
206
185
 
239
218
        for rev_info in self.revisions:
240
219
            checked[rev_info.revision_id] = True
241
220
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
242
 
 
 
221
                
243
222
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
244
223
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
245
224
 
247
226
        missing = {}
248
227
        for revision_id, sha1 in rev_to_sha.iteritems():
249
228
            if repository.has_revision(revision_id):
250
 
                testament = StrictTestament.from_revision(repository,
 
229
                testament = StrictTestament.from_revision(repository, 
251
230
                                                          revision_id)
252
231
                local_sha1 = self._testament_sha1_from_revision(repository,
253
232
                                                                revision_id)
254
233
                if sha1 != local_sha1:
255
 
                    raise BzrError('sha1 mismatch. For revision id {%s}'
 
234
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
256
235
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
257
236
                else:
258
237
                    count += 1
259
238
            elif revision_id not in checked:
260
239
                missing[revision_id] = sha1
261
240
 
 
241
        for inv_id, sha1 in inv_to_sha.iteritems():
 
242
            if repository.has_revision(inv_id):
 
243
                # Note: branch.get_inventory_sha1() just returns the value that
 
244
                # is stored in the revision text, and that value may be out
 
245
                # of date. This is bogus, because that means we aren't
 
246
                # validating the actual text, just that we wrote and read the
 
247
                # string. But for now, what the hell.
 
248
                local_sha1 = repository.get_inventory_sha1(inv_id)
 
249
                if sha1 != local_sha1:
 
250
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
 
251
                                   'local: %s, bundle: %s' % 
 
252
                                   (inv_id, local_sha1, sha1))
 
253
                else:
 
254
                    count += 1
 
255
 
262
256
        if len(missing) > 0:
263
257
            # I don't know if this is an error yet
264
258
            warning('Not all revision hashes could be validated.'
265
259
                    ' Unable validate %d hashes' % len(missing))
266
260
        mutter('Verified %d sha hashes for the bundle.' % count)
267
 
        self._validated_revisions_against_repo = True
268
261
 
269
262
    def _validate_inventory(self, inv, revision_id):
270
263
        """At this point we should have generated the BundleTree,
271
264
        so build up an inventory, and make sure the hashes match.
272
265
        """
 
266
 
 
267
        assert inv is not None
 
268
 
273
269
        # Now we should have a complete inventory entry.
274
270
        s = serializer_v5.write_inventory_to_string(inv)
275
271
        sha1 = sha_string(s)
276
272
        # Target revision is the last entry in the real_revisions list
277
273
        rev = self.get_revision(revision_id)
278
 
        if rev.revision_id != revision_id:
279
 
            raise AssertionError()
 
274
        assert rev.revision_id == revision_id
280
275
        if sha1 != rev.inventory_sha1:
281
276
            open(',,bogus-inv', 'wb').write(s)
282
277
            warning('Inventory sha hash mismatch for revision %s. %s'
287
282
 
288
283
        # This is a mapping from each revision id to it's sha hash
289
284
        rev_to_sha1 = {}
290
 
 
 
285
        
291
286
        rev = self.get_revision(revision_id)
292
287
        rev_info = self.get_revision_info(revision_id)
293
 
        if not (rev.revision_id == rev_info.revision_id):
294
 
            raise AssertionError()
295
 
        if not (rev.revision_id == revision_id):
296
 
            raise AssertionError()
 
288
        assert rev.revision_id == rev_info.revision_id
 
289
        assert rev.revision_id == revision_id
297
290
        sha1 = self._testament_sha1(rev, inventory)
298
291
        if sha1 != rev_info.sha1:
299
292
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
331
324
                if name == 'last-changed':
332
325
                    last_changed = value
333
326
                elif name == 'executable':
 
327
                    assert value in ('yes', 'no'), value
334
328
                    val = (value == 'yes')
335
329
                    bundle_tree.note_executable(new_path, val)
336
330
                elif name == 'target':
340
334
            return last_changed, encoding
341
335
 
342
336
        def do_patch(path, lines, encoding):
343
 
            if encoding == 'base64':
 
337
            if encoding is not None:
 
338
                assert encoding == 'base64'
344
339
                patch = base64.decodestring(''.join(lines))
345
 
            elif encoding is None:
 
340
            else:
346
341
                patch =  ''.join(lines)
347
 
            else:
348
 
                raise ValueError(encoding)
349
342
            bundle_tree.note_patch(path, patch)
350
343
 
351
344
        def renamed(kind, extra, lines):
411
404
            revision = get_rev_id(last_modified, path, kind)
412
405
            if lines:
413
406
                do_patch(path, lines, encoding)
414
 
 
 
407
            
415
408
        valid_actions = {
416
409
            'renamed':renamed,
417
410
            'removed':removed,
440
433
                        ' (unrecognized action): %r' % action_line)
441
434
            valid_actions[action](kind, extra, lines)
442
435
 
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
 
        """
 
436
    def install_revisions(self, target_repo):
 
437
        """Install revisions and return the target revision"""
449
438
        apply_bundle.install_bundle(target_repo, self)
450
439
        return self.target
451
440
 
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
 
 
459
441
 
460
442
class BundleTree(Tree):
461
443
    def __init__(self, base_tree, revision_id):
479
461
 
480
462
    def note_rename(self, old_path, new_path):
481
463
        """A file/directory has been renamed from old_path => new_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)
 
464
        assert new_path not in self._renamed
 
465
        assert old_path not in self._renamed_r
486
466
        self._renamed[new_path] = old_path
487
467
        self._renamed_r[old_path] = new_path
488
468
 
518
498
 
519
499
    def old_path(self, new_path):
520
500
        """Get the old_path (path in the base_tree) for the file at new_path"""
521
 
        if new_path[:1] in ('\\', '/'):
522
 
            raise ValueError(new_path)
 
501
        assert new_path[:1] not in ('\\', '/')
523
502
        old_path = self._renamed.get(new_path)
524
503
        if old_path is not None:
525
504
            return old_path
539
518
        #renamed_r
540
519
        if old_path in self._renamed_r:
541
520
            return None
542
 
        return old_path
 
521
        return old_path 
543
522
 
544
523
    def new_path(self, old_path):
545
524
        """Get the new_path (path in the target_tree) for the file at old_path
546
525
        in the base tree.
547
526
        """
548
 
        if old_path[:1] in ('\\', '/'):
549
 
            raise ValueError(old_path)
 
527
        assert old_path[:1] not in ('\\', '/')
550
528
        new_path = self._renamed_r.get(old_path)
551
529
        if new_path is not None:
552
530
            return new_path
565
543
        #renamed_r
566
544
        if new_path in self._renamed:
567
545
            return None
568
 
        return new_path
 
546
        return new_path 
569
547
 
570
548
    def path2id(self, path):
571
549
        """Return the id of the file present at path in the target tree."""
605
583
                return None
606
584
        new_path = self.id2path(file_id)
607
585
        return self.base_tree.path2id(new_path)
608
 
 
 
586
        
609
587
    def get_file(self, file_id):
610
588
        """Return a file-like object containing the new contents of the
611
589
        file given by file_id.
622
600
            patch_original = None
623
601
        file_patch = self.patches.get(self.id2path(file_id))
624
602
        if file_patch is None:
625
 
            if (patch_original is None and
 
603
            if (patch_original is None and 
626
604
                self.get_kind(file_id) == 'directory'):
627
605
                return StringIO()
628
 
            if patch_original is None:
629
 
                raise AssertionError("None: %s" % file_id)
 
606
            assert patch_original is not None, "None: %s" % file_id
630
607
            return patch_original
631
608
 
632
 
        if file_patch.startswith('\\'):
633
 
            raise ValueError(
634
 
                'Malformed patch for %s, %r' % (file_id, file_patch))
 
609
        assert not file_patch.startswith('\\'), \
 
610
            'Malformed patch for %s, %r' % (file_id, file_patch)
635
611
        return patched_file(file_patch, patch_original)
636
612
 
637
613
    def get_symlink_target(self, file_id):
684
660
        This need to be called before ever accessing self.inventory
685
661
        """
686
662
        from os.path import dirname, basename
 
663
 
 
664
        assert self.base_tree is not None
687
665
        base_inv = self.base_tree.inventory
688
666
        inv = Inventory(None, self.revision_id)
689
667