~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/serializer/v07.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:
1
1
# (C) 2005 Canonical Development 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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
19
 
20
20
import os
21
21
 
 
22
from bzrlib.bundle.common import get_header, header_str
22
23
from bzrlib.bundle.serializer import (BundleSerializer, 
23
24
                                      BUNDLE_HEADER, 
24
25
                                      format_highres_date,
25
26
                                      unpack_highres_date,
26
27
                                     )
27
28
from bzrlib.bundle.serializer import binary_diff
 
29
from bzrlib.bundle.read_bundle import (RevisionInfo, BundleInfo, BundleTree)
28
30
from bzrlib.delta import compare_trees
29
31
from bzrlib.diff import internal_diff
30
32
import bzrlib.errors as errors
35
37
import bzrlib.ui
36
38
from bzrlib.testament import StrictTestament
37
39
from bzrlib.textfile import text_file
 
40
from bzrlib.trace import mutter
38
41
 
39
42
bool_text = {True: 'yes', False: 'no'}
40
43
 
91
94
        :param f: The file to read from
92
95
        :return: A list of bundles
93
96
        """
94
 
        assert self.version == '0.7'
95
 
        # The first line of the header should have been read
96
 
        raise NotImplementedError
 
97
        return BundleReader(f).info
97
98
 
98
99
    def write(self, source, revision_ids, forced_bases, f):
99
100
        """Write the bundless to the supplied files.
303
304
                                             new_tree.id2path(ie.file_id)])
304
305
                action.add_property('last-changed', ie.revision)
305
306
                action.write(self.to_file)
 
307
 
 
308
 
 
309
class BundleReader(object):
 
310
    """This class reads in a bundle from a file, and returns
 
311
    a Bundle object, which can then be applied against a tree.
 
312
    """
 
313
    def __init__(self, from_file):
 
314
        """Read in the bundle from the file.
 
315
 
 
316
        :param from_file: A file-like object (must have iterator support).
 
317
        """
 
318
        object.__init__(self)
 
319
        self.from_file = iter(from_file)
 
320
        self._next_line = None
 
321
        
 
322
        self.info = BundleInfo()
 
323
        # We put the actual inventory ids in the footer, so that the patch
 
324
        # is easier to read for humans.
 
325
        # Unfortunately, that means we need to read everything before we
 
326
        # can create a proper bundle.
 
327
        self._read()
 
328
        self._validate()
 
329
 
 
330
    def _read(self):
 
331
        self._next().next()
 
332
        while self._next_line is not None:
 
333
            self._read_revision_header()
 
334
            if self._next_line is None:
 
335
                break
 
336
            self._read_patches()
 
337
            self._read_footer()
 
338
 
 
339
    def _validate(self):
 
340
        """Make sure that the information read in makes sense
 
341
        and passes appropriate checksums.
 
342
        """
 
343
        # Fill in all the missing blanks for the revisions
 
344
        # and generate the real_revisions list.
 
345
        self.info.complete_info()
 
346
 
 
347
    def get_bundle(self, repository):
 
348
        """Return the meta information, and a Bundle tree which can
 
349
        be used to populate the local stores and working tree, respectively.
 
350
        """
 
351
        return self.info, self.revision_tree(repository, self.info.target)
 
352
 
 
353
    def revision_tree(self, repository, revision_id, base=None):
 
354
        return self.info.revision_tree(repository, revision_id, base)
 
355
 
 
356
    def _next(self):
 
357
        """yield the next line, but secretly
 
358
        keep 1 extra line for peeking.
 
359
        """
 
360
        for line in self.from_file:
 
361
            last = self._next_line
 
362
            self._next_line = line
 
363
            if last is not None:
 
364
                #mutter('yielding line: %r' % last)
 
365
                yield last
 
366
        last = self._next_line
 
367
        self._next_line = None
 
368
        #mutter('yielding line: %r' % last)
 
369
        yield last
 
370
 
 
371
    def _read_header(self):
 
372
        """Read the bzr header"""
 
373
        header = get_header()
 
374
        found = False
 
375
        for line in self._next():
 
376
            if found:
 
377
                # not all mailers will keep trailing whitespace
 
378
                if line == '#\n':
 
379
                    line = '# \n'
 
380
                if (not line.startswith('# ') or not line.endswith('\n')
 
381
                        or line[2:-1].decode('utf-8') != header[0]):
 
382
                    raise MalformedHeader('Found a header, but it'
 
383
                        ' was improperly formatted')
 
384
                header.pop(0) # We read this line.
 
385
                if not header:
 
386
                    break # We found everything.
 
387
            elif (line.startswith('#') and line.endswith('\n')):
 
388
                line = line[1:-1].strip().decode('utf-8')
 
389
                if line[:len(header_str)] == header_str:
 
390
                    if line == header[0]:
 
391
                        found = True
 
392
                    else:
 
393
                        raise MalformedHeader('Found what looks like'
 
394
                                ' a header, but did not match')
 
395
                    header.pop(0)
 
396
        else:
 
397
            raise NotABundle('Did not find an opening header')
 
398
 
 
399
    def _read_revision_header(self):
 
400
        self.info.revisions.append(RevisionInfo(None))
 
401
        for line in self._next():
 
402
            # The bzr header is terminated with a blank line
 
403
            # which does not start with '#'
 
404
            if line is None or line == '\n':
 
405
                break
 
406
            self._handle_next(line)
 
407
 
 
408
    def _read_next_entry(self, line, indent=1):
 
409
        """Read in a key-value pair
 
410
        """
 
411
        if not line.startswith('#'):
 
412
            raise MalformedHeader('Bzr header did not start with #')
 
413
        line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
 
414
        if line[:indent] == ' '*indent:
 
415
            line = line[indent:]
 
416
        if not line:
 
417
            return None, None# Ignore blank lines
 
418
 
 
419
        loc = line.find(': ')
 
420
        if loc != -1:
 
421
            key = line[:loc]
 
422
            value = line[loc+2:]
 
423
            if not value:
 
424
                value = self._read_many(indent=indent+2)
 
425
        elif line[-1:] == ':':
 
426
            key = line[:-1]
 
427
            value = self._read_many(indent=indent+2)
 
428
        else:
 
429
            raise MalformedHeader('While looking for key: value pairs,'
 
430
                    ' did not find the colon %r' % (line))
 
431
 
 
432
        key = key.replace(' ', '_')
 
433
        #mutter('found %s: %s' % (key, value))
 
434
        return key, value
 
435
 
 
436
    def _handle_next(self, line):
 
437
        if line is None:
 
438
            return
 
439
        key, value = self._read_next_entry(line, indent=1)
 
440
        mutter('_handle_next %r => %r' % (key, value))
 
441
        if key is None:
 
442
            return
 
443
 
 
444
        revision_info = self.info.revisions[-1]
 
445
        if hasattr(revision_info, key):
 
446
            if getattr(revision_info, key) is None:
 
447
                setattr(revision_info, key, value)
 
448
            else:
 
449
                raise MalformedHeader('Duplicated Key: %s' % key)
 
450
        else:
 
451
            # What do we do with a key we don't recognize
 
452
            raise MalformedHeader('Unknown Key: "%s"' % key)
 
453
    
 
454
    def _read_many(self, indent):
 
455
        """If a line ends with no entry, that means that it should be
 
456
        followed with multiple lines of values.
 
457
 
 
458
        This detects the end of the list, because it will be a line that
 
459
        does not start properly indented.
 
460
        """
 
461
        values = []
 
462
        start = '#' + (' '*indent)
 
463
 
 
464
        if self._next_line is None or self._next_line[:len(start)] != start:
 
465
            return values
 
466
 
 
467
        for line in self._next():
 
468
            values.append(line[len(start):-1].decode('utf-8'))
 
469
            if self._next_line is None or self._next_line[:len(start)] != start:
 
470
                break
 
471
        return values
 
472
 
 
473
    def _read_one_patch(self):
 
474
        """Read in one patch, return the complete patch, along with
 
475
        the next line.
 
476
 
 
477
        :return: action, lines, do_continue
 
478
        """
 
479
        #mutter('_read_one_patch: %r' % self._next_line)
 
480
        # Peek and see if there are no patches
 
481
        if self._next_line is None or self._next_line.startswith('#'):
 
482
            return None, [], False
 
483
 
 
484
        first = True
 
485
        lines = []
 
486
        for line in self._next():
 
487
            if first:
 
488
                if not line.startswith('==='):
 
489
                    raise MalformedPatches('The first line of all patches'
 
490
                        ' should be a bzr meta line "==="'
 
491
                        ': %r' % line)
 
492
                action = line[4:-1].decode('utf-8')
 
493
            elif line.startswith('... '):
 
494
                action += line[len('... '):-1].decode('utf-8')
 
495
 
 
496
            if (self._next_line is not None and 
 
497
                self._next_line.startswith('===')):
 
498
                return action, lines, True
 
499
            elif self._next_line is None or self._next_line.startswith('#'):
 
500
                return action, lines, False
 
501
 
 
502
            if first:
 
503
                first = False
 
504
            elif not line.startswith('... '):
 
505
                lines.append(line)
 
506
 
 
507
        return action, lines, False
 
508
            
 
509
    def _read_patches(self):
 
510
        do_continue = True
 
511
        revision_actions = []
 
512
        while do_continue:
 
513
            action, lines, do_continue = self._read_one_patch()
 
514
            if action is not None:
 
515
                revision_actions.append((action, lines))
 
516
        assert self.info.revisions[-1].tree_actions is None
 
517
        self.info.revisions[-1].tree_actions = revision_actions
 
518
 
 
519
    def _read_footer(self):
 
520
        """Read the rest of the meta information.
 
521
 
 
522
        :param first_line:  The previous step iterates past what it
 
523
                            can handle. That extra line is given here.
 
524
        """
 
525
        for line in self._next():
 
526
            self._handle_next(line)
 
527
            if not self._next_line.startswith('#'):
 
528
                self._next().next()
 
529
                break
 
530
            if self._next_line is None:
 
531
                break