~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Robert Collins
  • Date: 2006-08-08 23:19:29 UTC
  • mfrom: (1884 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1912.
  • Revision ID: robertc@robertcollins.net-20060808231929-4e3e298190214b3a
current status

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.serializer import (BundleSerializer, 
23
 
                                      BUNDLE_HEADER, 
 
22
from bzrlib import errors
 
23
from bzrlib.bundle.serializer import (BundleSerializer,
 
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.bundle_data import (RevisionInfo, BundleInfo, BundleTree)
28
30
from bzrlib.delta import compare_trees
29
31
from bzrlib.diff import internal_diff
30
 
import bzrlib.errors as errors
31
32
from bzrlib.osutils import pathjoin
32
33
from bzrlib.progress import DummyProgress
33
34
from bzrlib.revision import NULL_REVISION
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):
177
177
                base_tree = last_rev_tree
178
178
            else:
179
179
                base_tree = self.source.revision_tree(base_id)
180
 
 
 
180
            force_binary = (i != 0)
181
181
            self._write_revision(rev, rev_tree, base_id, base_tree, 
182
 
                                 explicit_base)
 
182
                                 explicit_base, force_binary)
183
183
 
184
184
            last_rev_id = base_id
185
185
            last_rev_tree = base_tree
186
186
 
187
187
    def _write_revision(self, rev, rev_tree, base_rev, base_tree, 
188
 
                        explicit_base):
 
188
                        explicit_base, force_binary):
189
189
        """Write out the information for a revision."""
190
190
        def w(key, value):
191
191
            self._write(key, value, indent=1)
195
195
        w('date', format_highres_date(rev.timestamp, rev.timezone))
196
196
        self.to_file.write('\n')
197
197
 
198
 
        self._write_delta(rev_tree, base_tree, rev.revision_id)
 
198
        self._write_delta(rev_tree, base_tree, rev.revision_id, force_binary)
199
199
 
200
200
        w('revision id', rev.revision_id)
201
201
        w('sha1', StrictTestament.from_revision(self.source, 
222
222
        self.to_file.write(' // '.join(p_texts).encode('utf-8'))
223
223
        self.to_file.write('\n')
224
224
 
225
 
    def _write_delta(self, new_tree, old_tree, default_revision_id):
 
225
    def _write_delta(self, new_tree, old_tree, default_revision_id, 
 
226
                     force_binary):
226
227
        """Write out the changes between the trees."""
227
228
        DEVNULL = '/dev/null'
228
229
        old_label = ''
229
230
        new_label = ''
230
231
 
231
 
        def do_diff(file_id, old_path, new_path, action):
 
232
        def do_diff(file_id, old_path, new_path, action, force_binary):
232
233
            def tree_lines(tree, require_text=False):
233
234
                if file_id in tree:
234
235
                    tree_file = tree.get_file(file_id)
239
240
                    return []
240
241
 
241
242
            try:
 
243
                if force_binary:
 
244
                    raise errors.BinaryFile()
242
245
                old_lines = tree_lines(old_tree, require_text=True)
243
246
                new_lines = tree_lines(new_tree, require_text=True)
244
247
                action.write(self.to_file)
262
265
            if text_modified and kind == "symlink":
263
266
                action.add_property('target', entry.symlink_target)
264
267
            if text_modified and kind == "file":
265
 
                do_diff(file_id, old_path, new_path, action)
 
268
                do_diff(file_id, old_path, new_path, action, force_binary)
266
269
            else:
267
270
                action.write(self.to_file)
268
271
 
300
303
                                             new_tree.id2path(ie.file_id)])
301
304
                action.add_property('last-changed', ie.revision)
302
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
            if not line.startswith('#'):
 
371
                continue
 
372
            found_something = True
 
373
            self._handle_next(line)
 
374
        if not found_something:
 
375
            # Nothing was there, so remove the added revision
 
376
            self.info.revisions.pop()
 
377
        return found_something
 
378
 
 
379
    def _read_next_entry(self, line, indent=1):
 
380
        """Read in a key-value pair
 
381
        """
 
382
        if not line.startswith('#'):
 
383
            raise errors.MalformedHeader('Bzr header did not start with #')
 
384
        line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
 
385
        if line[:indent] == ' '*indent:
 
386
            line = line[indent:]
 
387
        if not line:
 
388
            return None, None# Ignore blank lines
 
389
 
 
390
        loc = line.find(': ')
 
391
        if loc != -1:
 
392
            key = line[:loc]
 
393
            value = line[loc+2:]
 
394
            if not value:
 
395
                value = self._read_many(indent=indent+2)
 
396
        elif line[-1:] == ':':
 
397
            key = line[:-1]
 
398
            value = self._read_many(indent=indent+2)
 
399
        else:
 
400
            raise errors.MalformedHeader('While looking for key: value pairs,'
 
401
                    ' did not find the colon %r' % (line))
 
402
 
 
403
        key = key.replace(' ', '_')
 
404
        #mutter('found %s: %s' % (key, value))
 
405
        return key, value
 
406
 
 
407
    def _handle_next(self, line):
 
408
        if line is None:
 
409
            return
 
410
        key, value = self._read_next_entry(line, indent=1)
 
411
        mutter('_handle_next %r => %r' % (key, value))
 
412
        if key is None:
 
413
            return
 
414
 
 
415
        revision_info = self.info.revisions[-1]
 
416
        if hasattr(revision_info, key):
 
417
            if getattr(revision_info, key) is None:
 
418
                setattr(revision_info, key, value)
 
419
            else:
 
420
                raise errors.MalformedHeader('Duplicated Key: %s' % key)
 
421
        else:
 
422
            # What do we do with a key we don't recognize
 
423
            raise errors.MalformedHeader('Unknown Key: "%s"' % key)
 
424
    
 
425
    def _read_many(self, indent):
 
426
        """If a line ends with no entry, that means that it should be
 
427
        followed with multiple lines of values.
 
428
 
 
429
        This detects the end of the list, because it will be a line that
 
430
        does not start properly indented.
 
431
        """
 
432
        values = []
 
433
        start = '#' + (' '*indent)
 
434
 
 
435
        if self._next_line is None or self._next_line[:len(start)] != start:
 
436
            return values
 
437
 
 
438
        for line in self._next():
 
439
            values.append(line[len(start):-1].decode('utf-8'))
 
440
            if self._next_line is None or self._next_line[:len(start)] != start:
 
441
                break
 
442
        return values
 
443
 
 
444
    def _read_one_patch(self):
 
445
        """Read in one patch, return the complete patch, along with
 
446
        the next line.
 
447
 
 
448
        :return: action, lines, do_continue
 
449
        """
 
450
        #mutter('_read_one_patch: %r' % self._next_line)
 
451
        # Peek and see if there are no patches
 
452
        if self._next_line is None or self._next_line.startswith('#'):
 
453
            return None, [], False
 
454
 
 
455
        first = True
 
456
        lines = []
 
457
        for line in self._next():
 
458
            if first:
 
459
                if not line.startswith('==='):
 
460
                    raise errors.MalformedPatches('The first line of all patches'
 
461
                        ' should be a bzr meta line "==="'
 
462
                        ': %r' % line)
 
463
                action = line[4:-1].decode('utf-8')
 
464
            elif line.startswith('... '):
 
465
                action += line[len('... '):-1].decode('utf-8')
 
466
 
 
467
            if (self._next_line is not None and 
 
468
                self._next_line.startswith('===')):
 
469
                return action, lines, True
 
470
            elif self._next_line is None or self._next_line.startswith('#'):
 
471
                return action, lines, False
 
472
 
 
473
            if first:
 
474
                first = False
 
475
            elif not line.startswith('... '):
 
476
                lines.append(line)
 
477
 
 
478
        return action, lines, False
 
479
            
 
480
    def _read_patches(self):
 
481
        do_continue = True
 
482
        revision_actions = []
 
483
        while do_continue:
 
484
            action, lines, do_continue = self._read_one_patch()
 
485
            if action is not None:
 
486
                revision_actions.append((action, lines))
 
487
        assert self.info.revisions[-1].tree_actions is None
 
488
        self.info.revisions[-1].tree_actions = revision_actions
 
489
 
 
490
    def _read_footer(self):
 
491
        """Read the rest of the meta information.
 
492
 
 
493
        :param first_line:  The previous step iterates past what it
 
494
                            can handle. That extra line is given here.
 
495
        """
 
496
        for line in self._next():
 
497
            self._handle_next(line)
 
498
            if self._next_line is None:
 
499
                break
 
500
            if not self._next_line.startswith('#'):
 
501
                # Consume the trailing \n and stop processing
 
502
                self._next().next()
 
503
                break