~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-01-24 07:12:09 UTC
  • mto: This revision was merged to the branch mainline in revision 2244.
  • Revision ID: mbp@sourcefrog.net-20070124071209-yqiths20n6wxqaqr
Change RepositoryFormat to use a Registry rather than ad-hoc dictionary

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
 
21
21
import os
22
22
import pprint
23
23
 
24
 
from bzrlib import (
25
 
    osutils,
26
 
    timestamp,
27
 
    )
28
24
import bzrlib.errors
29
 
from bzrlib.bundle import apply_bundle
30
 
from bzrlib.errors import (TestamentMismatch, BzrError,
 
25
from bzrlib.errors import (TestamentMismatch, BzrError, 
31
26
                           MalformedHeader, MalformedPatches, NotABundle)
32
27
from bzrlib.inventory import (Inventory, InventoryEntry,
33
28
                              InventoryDirectory, InventoryFile,
77
72
        if self.properties:
78
73
            for property in self.properties:
79
74
                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:]
 
75
                assert key_end is not None
 
76
                key = property[:key_end].encode('utf-8')
 
77
                value = property[key_end+2:].encode('utf-8')
88
78
                rev.properties[key] = value
89
79
 
90
80
        return rev
91
81
 
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
82
 
106
83
class BundleInfo(object):
107
84
    """This contains the meta information. Stuff that allows you to
108
85
    recreate the revision or inventory XML.
109
86
    """
110
 
    def __init__(self, bundle_format=None):
111
 
        self.bundle_format = None
 
87
    def __init__(self):
112
88
        self.committer = None
113
89
        self.date = None
114
90
        self.message = None
125
101
        self.timestamp = None
126
102
        self.timezone = None
127
103
 
128
 
        # Have we checked the repository yet?
129
 
        self._validated_revisions_against_repo = False
130
 
 
131
104
    def __str__(self):
132
105
        return pprint.pformat(self.__dict__)
133
106
 
136
109
        split up, based on the assumptions that can be made
137
110
        when information is missing.
138
111
        """
139
 
        from bzrlib.timestamp import unpack_highres_date
 
112
        from bzrlib.bundle.serializer import unpack_highres_date
140
113
        # Put in all of the guessable information.
141
114
        if not self.timestamp and self.date:
142
115
            self.timestamp, self.timezone = unpack_highres_date(self.date)
159
132
    def get_base(self, revision):
160
133
        revision_info = self.get_revision_info(revision.revision_id)
161
134
        if revision_info.base_id is not None:
162
 
            return revision_info.base_id
 
135
            if revision_info.base_id == NULL_REVISION:
 
136
                return None
 
137
            else:
 
138
                return revision_info.base_id
163
139
        if len(revision.parent_ids) == 0:
164
140
            # There is no base listed, and
165
141
            # the lowest revision doesn't have a parent
166
142
            # so this is probably against the empty tree
167
 
            # and thus base truly is NULL_REVISION
168
 
            return NULL_REVISION
 
143
            # and thus base truly is None
 
144
            return None
169
145
        else:
170
146
            return revision.parent_ids[-1]
171
147
 
194
170
    def revision_tree(self, repository, revision_id, base=None):
195
171
        revision = self.get_revision(revision_id)
196
172
        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)
 
173
        assert base != revision_id
 
174
        self._validate_references_from_repository(repository)
201
175
        revision_info = self.get_revision_info(revision_id)
202
176
        inventory_revision_id = revision_id
203
 
        bundle_tree = BundleTree(repository.revision_tree(base),
 
177
        bundle_tree = BundleTree(repository.revision_tree(base), 
204
178
                                  inventory_revision_id)
205
179
        self._update_tree(bundle_tree, revision_id)
206
180
 
239
213
        for rev_info in self.revisions:
240
214
            checked[rev_info.revision_id] = True
241
215
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
242
 
 
 
216
                
243
217
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
244
218
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
245
219
 
247
221
        missing = {}
248
222
        for revision_id, sha1 in rev_to_sha.iteritems():
249
223
            if repository.has_revision(revision_id):
250
 
                testament = StrictTestament.from_revision(repository,
 
224
                testament = StrictTestament.from_revision(repository, 
251
225
                                                          revision_id)
252
226
                local_sha1 = self._testament_sha1_from_revision(repository,
253
227
                                                                revision_id)
254
228
                if sha1 != local_sha1:
255
 
                    raise BzrError('sha1 mismatch. For revision id {%s}'
 
229
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
256
230
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
257
231
                else:
258
232
                    count += 1
259
233
            elif revision_id not in checked:
260
234
                missing[revision_id] = sha1
261
235
 
 
236
        for inv_id, sha1 in inv_to_sha.iteritems():
 
237
            if repository.has_revision(inv_id):
 
238
                # Note: branch.get_inventory_sha1() just returns the value that
 
239
                # is stored in the revision text, and that value may be out
 
240
                # of date. This is bogus, because that means we aren't
 
241
                # validating the actual text, just that we wrote and read the
 
242
                # string. But for now, what the hell.
 
243
                local_sha1 = repository.get_inventory_sha1(inv_id)
 
244
                if sha1 != local_sha1:
 
245
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
 
246
                                   'local: %s, bundle: %s' % 
 
247
                                   (inv_id, local_sha1, sha1))
 
248
                else:
 
249
                    count += 1
 
250
 
262
251
        if len(missing) > 0:
263
252
            # I don't know if this is an error yet
264
253
            warning('Not all revision hashes could be validated.'
265
254
                    ' Unable validate %d hashes' % len(missing))
266
255
        mutter('Verified %d sha hashes for the bundle.' % count)
267
 
        self._validated_revisions_against_repo = True
268
256
 
269
257
    def _validate_inventory(self, inv, revision_id):
270
258
        """At this point we should have generated the BundleTree,
271
259
        so build up an inventory, and make sure the hashes match.
272
260
        """
 
261
 
 
262
        assert inv is not None
 
263
 
273
264
        # Now we should have a complete inventory entry.
274
265
        s = serializer_v5.write_inventory_to_string(inv)
275
266
        sha1 = sha_string(s)
276
267
        # Target revision is the last entry in the real_revisions list
277
268
        rev = self.get_revision(revision_id)
278
 
        if rev.revision_id != revision_id:
279
 
            raise AssertionError()
 
269
        assert rev.revision_id == revision_id
280
270
        if sha1 != rev.inventory_sha1:
281
271
            open(',,bogus-inv', 'wb').write(s)
282
272
            warning('Inventory sha hash mismatch for revision %s. %s'
287
277
 
288
278
        # This is a mapping from each revision id to it's sha hash
289
279
        rev_to_sha1 = {}
290
 
 
 
280
        
291
281
        rev = self.get_revision(revision_id)
292
282
        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()
 
283
        assert rev.revision_id == rev_info.revision_id
 
284
        assert rev.revision_id == revision_id
297
285
        sha1 = self._testament_sha1(rev, inventory)
298
286
        if sha1 != rev_info.sha1:
299
287
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
311
299
 
312
300
        def get_rev_id(last_changed, path, kind):
313
301
            if last_changed is not None:
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
                changed_revision_id = last_changed.decode('utf-8')
318
303
            else:
319
304
                changed_revision_id = revision_id
320
305
            bundle_tree.note_last_changed(path, changed_revision_id)
331
316
                if name == 'last-changed':
332
317
                    last_changed = value
333
318
                elif name == 'executable':
 
319
                    assert value in ('yes', 'no'), value
334
320
                    val = (value == 'yes')
335
321
                    bundle_tree.note_executable(new_path, val)
336
322
                elif name == 'target':
340
326
            return last_changed, encoding
341
327
 
342
328
        def do_patch(path, lines, encoding):
343
 
            if encoding == 'base64':
 
329
            if encoding is not None:
 
330
                assert encoding == 'base64'
344
331
                patch = base64.decodestring(''.join(lines))
345
 
            elif encoding is None:
 
332
            else:
346
333
                patch =  ''.join(lines)
347
 
            else:
348
 
                raise ValueError(encoding)
349
334
            bundle_tree.note_patch(path, patch)
350
335
 
351
336
        def renamed(kind, extra, lines):
387
372
            if not info[1].startswith('file-id:'):
388
373
                raise BzrError('The file-id should follow the path for an add'
389
374
                        ': %r' % extra)
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
            file_id = info[1][8:]
393
376
 
394
377
            bundle_tree.note_id(file_id, path, kind)
395
378
            # this will be overridden in extra_info if executable is specified.
411
394
            revision = get_rev_id(last_modified, path, kind)
412
395
            if lines:
413
396
                do_patch(path, lines, encoding)
414
 
 
 
397
            
415
398
        valid_actions = {
416
399
            'renamed':renamed,
417
400
            'removed':removed,
440
423
                        ' (unrecognized action): %r' % action_line)
441
424
            valid_actions[action](kind, extra, lines)
442
425
 
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
 
 
459
426
 
460
427
class BundleTree(Tree):
461
428
    def __init__(self, base_tree, revision_id):
479
446
 
480
447
    def note_rename(self, old_path, new_path):
481
448
        """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)
 
449
        assert new_path not in self._renamed
 
450
        assert old_path not in self._renamed_r
486
451
        self._renamed[new_path] = old_path
487
452
        self._renamed_r[old_path] = new_path
488
453
 
518
483
 
519
484
    def old_path(self, new_path):
520
485
        """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)
 
486
        assert new_path[:1] not in ('\\', '/')
523
487
        old_path = self._renamed.get(new_path)
524
488
        if old_path is not None:
525
489
            return old_path
539
503
        #renamed_r
540
504
        if old_path in self._renamed_r:
541
505
            return None
542
 
        return old_path
 
506
        return old_path 
543
507
 
544
508
    def new_path(self, old_path):
545
509
        """Get the new_path (path in the target_tree) for the file at old_path
546
510
        in the base tree.
547
511
        """
548
 
        if old_path[:1] in ('\\', '/'):
549
 
            raise ValueError(old_path)
 
512
        assert old_path[:1] not in ('\\', '/')
550
513
        new_path = self._renamed_r.get(old_path)
551
514
        if new_path is not None:
552
515
            return new_path
565
528
        #renamed_r
566
529
        if new_path in self._renamed:
567
530
            return None
568
 
        return new_path
 
531
        return new_path 
569
532
 
570
533
    def path2id(self, path):
571
534
        """Return the id of the file present at path in the target tree."""
605
568
                return None
606
569
        new_path = self.id2path(file_id)
607
570
        return self.base_tree.path2id(new_path)
608
 
 
 
571
        
609
572
    def get_file(self, file_id):
610
573
        """Return a file-like object containing the new contents of the
611
574
        file given by file_id.
622
585
            patch_original = None
623
586
        file_patch = self.patches.get(self.id2path(file_id))
624
587
        if file_patch is None:
625
 
            if (patch_original is None and
 
588
            if (patch_original is None and 
626
589
                self.get_kind(file_id) == 'directory'):
627
590
                return StringIO()
628
 
            if patch_original is None:
629
 
                raise AssertionError("None: %s" % file_id)
 
591
            assert patch_original is not None, "None: %s" % file_id
630
592
            return patch_original
631
593
 
632
 
        if file_patch.startswith('\\'):
633
 
            raise ValueError(
634
 
                'Malformed patch for %s, %r' % (file_id, file_patch))
 
594
        assert not file_patch.startswith('\\'), \
 
595
            'Malformed patch for %s, %r' % (file_id, file_patch)
635
596
        return patched_file(file_patch, patch_original)
636
597
 
637
598
    def get_symlink_target(self, file_id):
684
645
        This need to be called before ever accessing self.inventory
685
646
        """
686
647
        from os.path import dirname, basename
 
648
 
 
649
        assert self.base_tree is not None
687
650
        base_inv = self.base_tree.inventory
688
651
        inv = Inventory(None, self.revision_id)
689
652