~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-04-09 20:23:07 UTC
  • mfrom: (4265.1.4 bbc-merge)
  • Revision ID: pqm@pqm.ubuntu.com-20090409202307-n0depb16qepoe21o
(jam) Change _fetch_uses_deltas = False for CHK repos until we can
        write a better fix.

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