~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/read_bundle.py

  • Committer: Aaron Bentley
  • Date: 2006-06-20 02:32:24 UTC
  • mto: This revision was merged to the branch mainline in revision 1802.
  • Revision ID: aaron.bentley@utoronto.ca-20060620023224-745f7801d2ef3ac5
Move BundleReader into v07 serializer

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
import os
22
22
import pprint
23
23
 
24
 
from bzrlib.bundle.common import get_header, header_str
25
24
import bzrlib.errors
26
25
from bzrlib.errors import (TestamentMismatch, BzrError, 
27
26
                           MalformedHeader, MalformedPatches, NotABundle)
424
423
            valid_actions[action](kind, extra, lines)
425
424
 
426
425
 
427
 
class BundleReader(object):
428
 
    """This class reads in a bundle from a file, and returns
429
 
    a Bundle object, which can then be applied against a tree.
430
 
    """
431
 
    def __init__(self, from_file):
432
 
        """Read in the bundle from the file.
433
 
 
434
 
        :param from_file: A file-like object (must have iterator support).
435
 
        """
436
 
        object.__init__(self)
437
 
        self.from_file = iter(from_file)
438
 
        self._next_line = None
439
 
        
440
 
        self.info = BundleInfo()
441
 
        # We put the actual inventory ids in the footer, so that the patch
442
 
        # is easier to read for humans.
443
 
        # Unfortunately, that means we need to read everything before we
444
 
        # can create a proper bundle.
445
 
        self._read()
446
 
        self._validate()
447
 
 
448
 
    def _read(self):
449
 
        self._read_header()
450
 
        while self._next_line is not None:
451
 
            self._read_revision_header()
452
 
            if self._next_line is None:
453
 
                break
454
 
            self._read_patches()
455
 
            self._read_footer()
456
 
 
457
 
    def _validate(self):
458
 
        """Make sure that the information read in makes sense
459
 
        and passes appropriate checksums.
460
 
        """
461
 
        # Fill in all the missing blanks for the revisions
462
 
        # and generate the real_revisions list.
463
 
        self.info.complete_info()
464
 
 
465
 
    def get_bundle(self, repository):
466
 
        """Return the meta information, and a Bundle tree which can
467
 
        be used to populate the local stores and working tree, respectively.
468
 
        """
469
 
        return self.info, self.revision_tree(repository, self.info.target)
470
 
 
471
 
    def revision_tree(self, repository, revision_id, base=None):
472
 
        return self.info.revision_tree(repository, revision_id, base)
473
 
 
474
 
    def _next(self):
475
 
        """yield the next line, but secretly
476
 
        keep 1 extra line for peeking.
477
 
        """
478
 
        for line in self.from_file:
479
 
            last = self._next_line
480
 
            self._next_line = line
481
 
            if last is not None:
482
 
                #mutter('yielding line: %r' % last)
483
 
                yield last
484
 
        last = self._next_line
485
 
        self._next_line = None
486
 
        #mutter('yielding line: %r' % last)
487
 
        yield last
488
 
 
489
 
    def _read_header(self):
490
 
        """Read the bzr header"""
491
 
        header = get_header()
492
 
        found = False
493
 
        for line in self._next():
494
 
            if found:
495
 
                # not all mailers will keep trailing whitespace
496
 
                if line == '#\n':
497
 
                    line = '# \n'
498
 
                if (not line.startswith('# ') or not line.endswith('\n')
499
 
                        or line[2:-1].decode('utf-8') != header[0]):
500
 
                    raise MalformedHeader('Found a header, but it'
501
 
                        ' was improperly formatted')
502
 
                header.pop(0) # We read this line.
503
 
                if not header:
504
 
                    break # We found everything.
505
 
            elif (line.startswith('#') and line.endswith('\n')):
506
 
                line = line[1:-1].strip().decode('utf-8')
507
 
                if line[:len(header_str)] == header_str:
508
 
                    if line == header[0]:
509
 
                        found = True
510
 
                    else:
511
 
                        raise MalformedHeader('Found what looks like'
512
 
                                ' a header, but did not match')
513
 
                    header.pop(0)
514
 
        else:
515
 
            raise NotABundle('Did not find an opening header')
516
 
 
517
 
    def _read_revision_header(self):
518
 
        self.info.revisions.append(RevisionInfo(None))
519
 
        for line in self._next():
520
 
            # The bzr header is terminated with a blank line
521
 
            # which does not start with '#'
522
 
            if line is None or line == '\n':
523
 
                break
524
 
            self._handle_next(line)
525
 
 
526
 
    def _read_next_entry(self, line, indent=1):
527
 
        """Read in a key-value pair
528
 
        """
529
 
        if not line.startswith('#'):
530
 
            raise MalformedHeader('Bzr header did not start with #')
531
 
        line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
532
 
        if line[:indent] == ' '*indent:
533
 
            line = line[indent:]
534
 
        if not line:
535
 
            return None, None# Ignore blank lines
536
 
 
537
 
        loc = line.find(': ')
538
 
        if loc != -1:
539
 
            key = line[:loc]
540
 
            value = line[loc+2:]
541
 
            if not value:
542
 
                value = self._read_many(indent=indent+2)
543
 
        elif line[-1:] == ':':
544
 
            key = line[:-1]
545
 
            value = self._read_many(indent=indent+2)
546
 
        else:
547
 
            raise MalformedHeader('While looking for key: value pairs,'
548
 
                    ' did not find the colon %r' % (line))
549
 
 
550
 
        key = key.replace(' ', '_')
551
 
        #mutter('found %s: %s' % (key, value))
552
 
        return key, value
553
 
 
554
 
    def _handle_next(self, line):
555
 
        if line is None:
556
 
            return
557
 
        key, value = self._read_next_entry(line, indent=1)
558
 
        mutter('_handle_next %r => %r' % (key, value))
559
 
        if key is None:
560
 
            return
561
 
 
562
 
        revision_info = self.info.revisions[-1]
563
 
        if hasattr(revision_info, key):
564
 
            if getattr(revision_info, key) is None:
565
 
                setattr(revision_info, key, value)
566
 
            else:
567
 
                raise MalformedHeader('Duplicated Key: %s' % key)
568
 
        else:
569
 
            # What do we do with a key we don't recognize
570
 
            raise MalformedHeader('Unknown Key: "%s"' % key)
571
 
    
572
 
    def _read_many(self, indent):
573
 
        """If a line ends with no entry, that means that it should be
574
 
        followed with multiple lines of values.
575
 
 
576
 
        This detects the end of the list, because it will be a line that
577
 
        does not start properly indented.
578
 
        """
579
 
        values = []
580
 
        start = '#' + (' '*indent)
581
 
 
582
 
        if self._next_line is None or self._next_line[:len(start)] != start:
583
 
            return values
584
 
 
585
 
        for line in self._next():
586
 
            values.append(line[len(start):-1].decode('utf-8'))
587
 
            if self._next_line is None or self._next_line[:len(start)] != start:
588
 
                break
589
 
        return values
590
 
 
591
 
    def _read_one_patch(self):
592
 
        """Read in one patch, return the complete patch, along with
593
 
        the next line.
594
 
 
595
 
        :return: action, lines, do_continue
596
 
        """
597
 
        #mutter('_read_one_patch: %r' % self._next_line)
598
 
        # Peek and see if there are no patches
599
 
        if self._next_line is None or self._next_line.startswith('#'):
600
 
            return None, [], False
601
 
 
602
 
        first = True
603
 
        lines = []
604
 
        for line in self._next():
605
 
            if first:
606
 
                if not line.startswith('==='):
607
 
                    raise MalformedPatches('The first line of all patches'
608
 
                        ' should be a bzr meta line "==="'
609
 
                        ': %r' % line)
610
 
                action = line[4:-1].decode('utf-8')
611
 
            elif line.startswith('... '):
612
 
                action += line[len('... '):-1].decode('utf-8')
613
 
 
614
 
            if (self._next_line is not None and 
615
 
                self._next_line.startswith('===')):
616
 
                return action, lines, True
617
 
            elif self._next_line is None or self._next_line.startswith('#'):
618
 
                return action, lines, False
619
 
 
620
 
            if first:
621
 
                first = False
622
 
            elif not line.startswith('... '):
623
 
                lines.append(line)
624
 
 
625
 
        return action, lines, False
626
 
            
627
 
    def _read_patches(self):
628
 
        do_continue = True
629
 
        revision_actions = []
630
 
        while do_continue:
631
 
            action, lines, do_continue = self._read_one_patch()
632
 
            if action is not None:
633
 
                revision_actions.append((action, lines))
634
 
        assert self.info.revisions[-1].tree_actions is None
635
 
        self.info.revisions[-1].tree_actions = revision_actions
636
 
 
637
 
    def _read_footer(self):
638
 
        """Read the rest of the meta information.
639
 
 
640
 
        :param first_line:  The previous step iterates past what it
641
 
                            can handle. That extra line is given here.
642
 
        """
643
 
        for line in self._next():
644
 
            self._handle_next(line)
645
 
            if not self._next_line.startswith('#'):
646
 
                self._next().next()
647
 
                break
648
 
            if self._next_line is None:
649
 
                break
650
 
 
651
 
 
652
426
class BundleTree(Tree):
653
427
    def __init__(self, base_tree, revision_id):
654
428
        self.base_tree = base_tree