~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/serializer/v08.py

  • Committer: Robey Pointer
  • Date: 2006-07-01 19:03:33 UTC
  • mfrom: (1829 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1830.
  • Revision ID: robey@lag.net-20060701190333-f58465aec4bd3412
merge from bzr.dev

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
25
25
                                      unpack_highres_date,
26
26
                                     )
27
27
from bzrlib.bundle.serializer import binary_diff
 
28
from bzrlib.bundle.bundle_data import (RevisionInfo, BundleInfo, BundleTree)
28
29
from bzrlib.delta import compare_trees
29
30
from bzrlib.diff import internal_diff
30
31
import bzrlib.errors as errors
35
36
import bzrlib.ui
36
37
from bzrlib.testament import StrictTestament
37
38
from bzrlib.textfile import text_file
 
39
from bzrlib.trace import mutter
38
40
 
39
41
bool_text = {True: 'yes', False: 'no'}
40
42
 
84
86
        to_file.write(text_line+'\n')
85
87
 
86
88
 
87
 
class BundleSerializerV07(BundleSerializer):
 
89
class BundleSerializerV08(BundleSerializer):
88
90
    def read(self, f):
89
91
        """Read the rest of the bundles from the supplied file.
90
92
 
91
93
        :param f: The file to read from
92
94
        :return: A list of bundles
93
95
        """
94
 
        assert self.version == '0.7'
95
 
        # The first line of the header should have been read
96
 
        raise NotImplementedError
 
96
        return BundleReader(f).info
97
97
 
98
98
    def write(self, source, revision_ids, forced_bases, f):
99
99
        """Write the bundless to the supplied files.
123
123
        """Write the header for the changes"""
124
124
        f = self.to_file
125
125
        f.write(BUNDLE_HEADER)
126
 
        f.write('0.7\n')
 
126
        f.write('0.8\n')
127
127
        f.write('#\n')
128
128
 
129
129
    def _write(self, key, value, indent=1):
303
303
                                             new_tree.id2path(ie.file_id)])
304
304
                action.add_property('last-changed', ie.revision)
305
305
                action.write(self.to_file)
 
306
 
 
307
 
 
308
class BundleReader(object):
 
309
    """This class reads in a bundle from a file, and returns
 
310
    a Bundle object, which can then be applied against a tree.
 
311
    """
 
312
    def __init__(self, from_file):
 
313
        """Read in the bundle from the file.
 
314
 
 
315
        :param from_file: A file-like object (must have iterator support).
 
316
        """
 
317
        object.__init__(self)
 
318
        self.from_file = iter(from_file)
 
319
        self._next_line = None
 
320
        
 
321
        self.info = BundleInfo()
 
322
        # We put the actual inventory ids in the footer, so that the patch
 
323
        # is easier to read for humans.
 
324
        # Unfortunately, that means we need to read everything before we
 
325
        # can create a proper bundle.
 
326
        self._read()
 
327
        self._validate()
 
328
 
 
329
    def _read(self):
 
330
        self._next().next()
 
331
        while self._next_line is not None:
 
332
            if not self._read_revision_header():
 
333
                break
 
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 _next(self):
 
348
        """yield the next line, but secretly
 
349
        keep 1 extra line for peeking.
 
350
        """
 
351
        for line in self.from_file:
 
352
            last = self._next_line
 
353
            self._next_line = line
 
354
            if last is not None:
 
355
                #mutter('yielding line: %r' % last)
 
356
                yield last
 
357
        last = self._next_line
 
358
        self._next_line = None
 
359
        #mutter('yielding line: %r' % last)
 
360
        yield last
 
361
 
 
362
    def _read_revision_header(self):
 
363
        found_something = False
 
364
        self.info.revisions.append(RevisionInfo(None))
 
365
        for line in self._next():
 
366
            # The bzr header is terminated with a blank line
 
367
            # which does not start with '#'
 
368
            if line is None or line == '\n':
 
369
                break
 
370
            found_something = True
 
371
            self._handle_next(line)
 
372
        if not found_something:
 
373
            # Nothing was there, so remove the added revision
 
374
            self.info.revisions.pop()
 
375
        return found_something
 
376
 
 
377
    def _read_next_entry(self, line, indent=1):
 
378
        """Read in a key-value pair
 
379
        """
 
380
        if not line.startswith('#'):
 
381
            raise MalformedHeader('Bzr header did not start with #')
 
382
        line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
 
383
        if line[:indent] == ' '*indent:
 
384
            line = line[indent:]
 
385
        if not line:
 
386
            return None, None# Ignore blank lines
 
387
 
 
388
        loc = line.find(': ')
 
389
        if loc != -1:
 
390
            key = line[:loc]
 
391
            value = line[loc+2:]
 
392
            if not value:
 
393
                value = self._read_many(indent=indent+2)
 
394
        elif line[-1:] == ':':
 
395
            key = line[:-1]
 
396
            value = self._read_many(indent=indent+2)
 
397
        else:
 
398
            raise MalformedHeader('While looking for key: value pairs,'
 
399
                    ' did not find the colon %r' % (line))
 
400
 
 
401
        key = key.replace(' ', '_')
 
402
        #mutter('found %s: %s' % (key, value))
 
403
        return key, value
 
404
 
 
405
    def _handle_next(self, line):
 
406
        if line is None:
 
407
            return
 
408
        key, value = self._read_next_entry(line, indent=1)
 
409
        mutter('_handle_next %r => %r' % (key, value))
 
410
        if key is None:
 
411
            return
 
412
 
 
413
        revision_info = self.info.revisions[-1]
 
414
        if hasattr(revision_info, key):
 
415
            if getattr(revision_info, key) is None:
 
416
                setattr(revision_info, key, value)
 
417
            else:
 
418
                raise MalformedHeader('Duplicated Key: %s' % key)
 
419
        else:
 
420
            # What do we do with a key we don't recognize
 
421
            raise MalformedHeader('Unknown Key: "%s"' % key)
 
422
    
 
423
    def _read_many(self, indent):
 
424
        """If a line ends with no entry, that means that it should be
 
425
        followed with multiple lines of values.
 
426
 
 
427
        This detects the end of the list, because it will be a line that
 
428
        does not start properly indented.
 
429
        """
 
430
        values = []
 
431
        start = '#' + (' '*indent)
 
432
 
 
433
        if self._next_line is None or self._next_line[:len(start)] != start:
 
434
            return values
 
435
 
 
436
        for line in self._next():
 
437
            values.append(line[len(start):-1].decode('utf-8'))
 
438
            if self._next_line is None or self._next_line[:len(start)] != start:
 
439
                break
 
440
        return values
 
441
 
 
442
    def _read_one_patch(self):
 
443
        """Read in one patch, return the complete patch, along with
 
444
        the next line.
 
445
 
 
446
        :return: action, lines, do_continue
 
447
        """
 
448
        #mutter('_read_one_patch: %r' % self._next_line)
 
449
        # Peek and see if there are no patches
 
450
        if self._next_line is None or self._next_line.startswith('#'):
 
451
            return None, [], False
 
452
 
 
453
        first = True
 
454
        lines = []
 
455
        for line in self._next():
 
456
            if first:
 
457
                if not line.startswith('==='):
 
458
                    raise MalformedPatches('The first line of all patches'
 
459
                        ' should be a bzr meta line "==="'
 
460
                        ': %r' % line)
 
461
                action = line[4:-1].decode('utf-8')
 
462
            elif line.startswith('... '):
 
463
                action += line[len('... '):-1].decode('utf-8')
 
464
 
 
465
            if (self._next_line is not None and 
 
466
                self._next_line.startswith('===')):
 
467
                return action, lines, True
 
468
            elif self._next_line is None or self._next_line.startswith('#'):
 
469
                return action, lines, False
 
470
 
 
471
            if first:
 
472
                first = False
 
473
            elif not line.startswith('... '):
 
474
                lines.append(line)
 
475
 
 
476
        return action, lines, False
 
477
            
 
478
    def _read_patches(self):
 
479
        do_continue = True
 
480
        revision_actions = []
 
481
        while do_continue:
 
482
            action, lines, do_continue = self._read_one_patch()
 
483
            if action is not None:
 
484
                revision_actions.append((action, lines))
 
485
        assert self.info.revisions[-1].tree_actions is None
 
486
        self.info.revisions[-1].tree_actions = revision_actions
 
487
 
 
488
    def _read_footer(self):
 
489
        """Read the rest of the meta information.
 
490
 
 
491
        :param first_line:  The previous step iterates past what it
 
492
                            can handle. That extra line is given here.
 
493
        """
 
494
        for line in self._next():
 
495
            self._handle_next(line)
 
496
            if not self._next_line.startswith('#'):
 
497
                self._next().next()
 
498
                break
 
499
            if self._next_line is None:
 
500
                break