~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/repository.py

  • Committer: Ian Clatworthy
  • Date: 2010-02-19 03:02:07 UTC
  • mto: (4797.23.1 integration-2.1)
  • mto: This revision was merged to the branch mainline in revision 5055.
  • Revision ID: ian.clatworthy@canonical.com-20100219030207-zpbzx021zavx4sqt
What's New in 2.1 - a summary of changes since 2.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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
30
30
    graph,
31
31
    osutils,
32
32
    pack,
 
33
    ui,
 
34
    versionedfile,
33
35
    )
34
36
from bzrlib.bzrdir import BzrDir
35
37
from bzrlib.smart.request import (
39
41
    )
40
42
from bzrlib.repository import _strip_NULL_ghosts, network_format_registry
41
43
from bzrlib import revision as _mod_revision
42
 
from bzrlib.versionedfile import NetworkRecordStream, record_to_fulltext_bytes
 
44
from bzrlib.versionedfile import (
 
45
    NetworkRecordStream,
 
46
    record_to_fulltext_bytes,
 
47
    )
43
48
 
44
49
 
45
50
class SmartServerRepositoryRequest(SmartServerRequest):
414
419
            repository.
415
420
        """
416
421
        self._to_format = network_format_registry.get(to_network_name)
 
422
        if self._should_fake_unknown():
 
423
            return FailedSmartServerResponse(
 
424
                ('UnknownMethod', 'Repository.get_stream'))
417
425
        return None # Signal that we want a body.
418
426
 
 
427
    def _should_fake_unknown(self):
 
428
        """Return True if we should return UnknownMethod to the client.
 
429
        
 
430
        This is a workaround for bugs in pre-1.19 clients that claim to
 
431
        support receiving streams of CHK repositories.  The pre-1.19 client
 
432
        expects inventory records to be serialized in the format defined by
 
433
        to_network_name, but in pre-1.19 (at least) that format definition
 
434
        tries to use the xml5 serializer, which does not correctly handle
 
435
        rich-roots.  After 1.19 the client can also accept inventory-deltas
 
436
        (which avoids this issue), and those clients will use the
 
437
        Repository.get_stream_1.19 verb instead of this one.
 
438
        So: if this repository is CHK, and the to_format doesn't match,
 
439
        we should just fake an UnknownSmartMethod error so that the client
 
440
        will fallback to VFS, rather than sending it a stream we know it
 
441
        cannot handle.
 
442
        """
 
443
        from_format = self._repository._format
 
444
        to_format = self._to_format
 
445
        if not from_format.supports_chks:
 
446
            # Source not CHK: that's ok
 
447
            return False
 
448
        if (to_format.supports_chks and
 
449
            from_format.repository_class is to_format.repository_class and
 
450
            from_format._serializer == to_format._serializer):
 
451
            # Source is CHK, but target matches: that's ok
 
452
            # (e.g. 2a->2a, or CHK2->2a)
 
453
            return False
 
454
        # Source is CHK, and target is not CHK or incompatible CHK.  We can't
 
455
        # generate a compatible stream.
 
456
        return True
 
457
 
419
458
    def do_body(self, body_bytes):
420
459
        repository = self._repository
421
460
        repository.lock_read()
451
490
            repository.unlock()
452
491
 
453
492
 
 
493
class SmartServerRepositoryGetStream_1_19(SmartServerRepositoryGetStream):
 
494
 
 
495
    def _should_fake_unknown(self):
 
496
        """Returns False; we don't need to workaround bugs in 1.19+ clients."""
 
497
        return False
 
498
 
 
499
 
454
500
def _stream_to_byte_stream(stream, src_format):
455
501
    """Convert a record stream to a self delimited byte stream."""
456
502
    pack_writer = pack.ContainerSerialiser()
457
503
    yield pack_writer.begin()
458
504
    yield pack_writer.bytes_record(src_format.network_name(), '')
459
505
    for substream_type, substream in stream:
 
506
        if substream_type == 'inventory-deltas':
 
507
            # This doesn't feel like the ideal place to issue this warning;
 
508
            # however we don't want to do it in the Repository that's
 
509
            # generating the stream, because that might be on the server.
 
510
            # Instead we try to observe it as the stream goes by.
 
511
            ui.ui_factory.warn_cross_format_fetch(src_format,
 
512
                '(remote)')
460
513
        for record in substream:
461
514
            if record.storage_kind in ('chunked', 'fulltext'):
462
515
                serialised = record_to_fulltext_bytes(record)
 
516
            elif record.storage_kind == 'inventory-delta':
 
517
                serialised = record_to_inventory_delta_bytes(record)
463
518
            elif record.storage_kind == 'absent':
464
519
                raise ValueError("Absent factory for %s" % (record.key,))
465
520
            else:
472
527
    yield pack_writer.end()
473
528
 
474
529
 
 
530
class _ByteStreamDecoder(object):
 
531
    """Helper for _byte_stream_to_stream.
 
532
 
 
533
    The expected usage of this class is via the function _byte_stream_to_stream
 
534
    which creates a _ByteStreamDecoder, pops off the stream format and then
 
535
    yields the output of record_stream(), the main entry point to
 
536
    _ByteStreamDecoder.
 
537
 
 
538
    Broadly this class has to unwrap two layers of iterators:
 
539
    (type, substream)
 
540
    (substream details)
 
541
 
 
542
    This is complicated by wishing to return type, iterator_for_type, but
 
543
    getting the data for iterator_for_type when we find out type: we can't
 
544
    simply pass a generator down to the NetworkRecordStream parser, instead
 
545
    we have a little local state to seed each NetworkRecordStream instance,
 
546
    and gather the type that we'll be yielding.
 
547
 
 
548
    :ivar byte_stream: The byte stream being decoded.
 
549
    :ivar stream_decoder: A pack parser used to decode the bytestream
 
550
    :ivar current_type: The current type, used to join adjacent records of the
 
551
        same type into a single stream.
 
552
    :ivar first_bytes: The first bytes to give the next NetworkRecordStream.
 
553
    """
 
554
 
 
555
    def __init__(self, byte_stream):
 
556
        """Create a _ByteStreamDecoder."""
 
557
        self.stream_decoder = pack.ContainerPushParser()
 
558
        self.current_type = None
 
559
        self.first_bytes = None
 
560
        self.byte_stream = byte_stream
 
561
 
 
562
    def iter_stream_decoder(self):
 
563
        """Iterate the contents of the pack from stream_decoder."""
 
564
        # dequeue pending items
 
565
        for record in self.stream_decoder.read_pending_records():
 
566
            yield record
 
567
        # Pull bytes of the wire, decode them to records, yield those records.
 
568
        for bytes in self.byte_stream:
 
569
            self.stream_decoder.accept_bytes(bytes)
 
570
            for record in self.stream_decoder.read_pending_records():
 
571
                yield record
 
572
 
 
573
    def iter_substream_bytes(self):
 
574
        if self.first_bytes is not None:
 
575
            yield self.first_bytes
 
576
            # If we run out of pack records, single the outer layer to stop.
 
577
            self.first_bytes = None
 
578
        for record in self.iter_pack_records:
 
579
            record_names, record_bytes = record
 
580
            record_name, = record_names
 
581
            substream_type = record_name[0]
 
582
            if substream_type != self.current_type:
 
583
                # end of a substream, seed the next substream.
 
584
                self.current_type = substream_type
 
585
                self.first_bytes = record_bytes
 
586
                return
 
587
            yield record_bytes
 
588
 
 
589
    def record_stream(self):
 
590
        """Yield substream_type, substream from the byte stream."""
 
591
        self.seed_state()
 
592
        # Make and consume sub generators, one per substream type:
 
593
        while self.first_bytes is not None:
 
594
            substream = NetworkRecordStream(self.iter_substream_bytes())
 
595
            # after substream is fully consumed, self.current_type is set to
 
596
            # the next type, and self.first_bytes is set to the matching bytes.
 
597
            yield self.current_type, substream.read()
 
598
 
 
599
    def seed_state(self):
 
600
        """Prepare the _ByteStreamDecoder to decode from the pack stream."""
 
601
        # Set a single generator we can use to get data from the pack stream.
 
602
        self.iter_pack_records = self.iter_stream_decoder()
 
603
        # Seed the very first subiterator with content; after this each one
 
604
        # seeds the next.
 
605
        list(self.iter_substream_bytes())
 
606
 
 
607
 
475
608
def _byte_stream_to_stream(byte_stream):
476
609
    """Convert a byte stream into a format and a stream.
477
610
 
478
611
    :param byte_stream: A bytes iterator, as output by _stream_to_byte_stream.
479
612
    :return: (RepositoryFormat, stream_generator)
480
613
    """
481
 
    stream_decoder = pack.ContainerPushParser()
482
 
    def record_stream():
483
 
        """Closure to return the substreams."""
484
 
        # May have fully parsed records already.
485
 
        for record in stream_decoder.read_pending_records():
486
 
            record_names, record_bytes = record
487
 
            record_name, = record_names
488
 
            substream_type = record_name[0]
489
 
            substream = NetworkRecordStream([record_bytes])
490
 
            yield substream_type, substream.read()
491
 
        for bytes in byte_stream:
492
 
            stream_decoder.accept_bytes(bytes)
493
 
            for record in stream_decoder.read_pending_records():
494
 
                record_names, record_bytes = record
495
 
                record_name, = record_names
496
 
                substream_type = record_name[0]
497
 
                substream = NetworkRecordStream([record_bytes])
498
 
                yield substream_type, substream.read()
 
614
    decoder = _ByteStreamDecoder(byte_stream)
499
615
    for bytes in byte_stream:
500
 
        stream_decoder.accept_bytes(bytes)
501
 
        for record in stream_decoder.read_pending_records(max=1):
 
616
        decoder.stream_decoder.accept_bytes(bytes)
 
617
        for record in decoder.stream_decoder.read_pending_records(max=1):
502
618
            record_names, src_format_name = record
503
619
            src_format = network_format_registry.get(src_format_name)
504
 
            return src_format, record_stream()
 
620
            return src_format, decoder.record_stream()
505
621
 
506
622
 
507
623
class SmartServerRepositoryUnlock(SmartServerRepositoryRequest):
650
766
            return SuccessfulSmartServerResponse(('ok', ))
651
767
 
652
768
 
 
769
class SmartServerRepositoryInsertStream_1_19(SmartServerRepositoryInsertStreamLocked):
 
770
    """Insert a record stream from a RemoteSink into a repository.
 
771
 
 
772
    Same as SmartServerRepositoryInsertStreamLocked, except:
 
773
     - the lock token argument is optional
 
774
     - servers that implement this verb accept 'inventory-delta' records in the
 
775
       stream.
 
776
 
 
777
    New in 1.19.
 
778
    """
 
779
 
 
780
    def do_repository_request(self, repository, resume_tokens, lock_token=None):
 
781
        """StreamSink.insert_stream for a remote repository."""
 
782
        SmartServerRepositoryInsertStreamLocked.do_repository_request(
 
783
            self, repository, resume_tokens, lock_token)
 
784
 
 
785
 
653
786
class SmartServerRepositoryInsertStream(SmartServerRepositoryInsertStreamLocked):
654
787
    """Insert a record stream from a RemoteSink into an unlocked repository.
655
788