/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
1
# (C) 2005 Canonical Development Ltd
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
2
#
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
7
#
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
12
#
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
17
"""Serializer factory for reading and writing bundles.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
18
"""
19
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
20
import os
1185.82.78 by Aaron Bentley
Cleanups
21
1793.3.15 by John Arbash Meinel
Raise the right errors
22
from bzrlib import errors
23
from bzrlib.bundle.serializer import (BundleSerializer,
24
                                      BUNDLE_HEADER,
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
25
                                      format_highres_date,
26
                                      unpack_highres_date,
27
                                     )
28
from bzrlib.bundle.serializer import binary_diff
1793.2.3 by Aaron Bentley
Rename read_bundle.py to bundle_data.py
29
from bzrlib.bundle.bundle_data import (RevisionInfo, BundleInfo, BundleTree)
1185.82.78 by Aaron Bentley
Cleanups
30
from bzrlib.delta import compare_trees
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
31
from bzrlib.diff import internal_diff
1185.82.78 by Aaron Bentley
Cleanups
32
from bzrlib.osutils import pathjoin
1185.82.111 by Aaron Bentley
Remove progress indicator from cset command for now
33
from bzrlib.progress import DummyProgress
1185.82.78 by Aaron Bentley
Cleanups
34
from bzrlib.revision import NULL_REVISION
1185.82.7 by John Arbash Meinel
Adding patches.py into bzrlib, including the tests into the test suite.
35
from bzrlib.rio import RioWriter, read_stanzas
1185.82.92 by Aaron Bentley
Add progress bar for changeset generation
36
import bzrlib.ui
1185.82.121 by Aaron Bentley
Move calculation of Testament sha1s to Testament
37
from bzrlib.testament import StrictTestament
1185.82.96 by Aaron Bentley
Got first binary test passing
38
from bzrlib.textfile import text_file
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
39
from bzrlib.trace import mutter
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
40
1185.82.65 by Aaron Bentley
Factored out boolean text stuff
41
bool_text = {True: 'yes', False: 'no'}
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
42
1185.82.102 by Aaron Bentley
Start abstracting action line writing
43
44
class Action(object):
45
    """Represent an action"""
46
47
    def __init__(self, name, parameters=None, properties=None):
48
        self.name = name
49
        if parameters is None:
50
            self.parameters = []
51
        else:
52
            self.parameters = parameters
53
        if properties is None:
54
            self.properties = []
55
        else:
56
            self.properties = properties
57
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
58
    def add_property(self, name, value):
59
        """Add a property to the action"""
60
        self.properties.append((name, value))
61
62
    def add_bool_property(self, name, value):
63
        """Add a boolean property to the action"""
64
        self.add_property(name, bool_text[value])
65
1185.82.102 by Aaron Bentley
Start abstracting action line writing
66
    def write(self, to_file):
67
        """Write action as to a file"""
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
68
        p_texts = [' '.join([self.name]+self.parameters)]
69
        for prop in self.properties:
70
            if len(prop) == 1:
71
                p_texts.append(prop[0])
72
            else:
73
                try:
74
                    p_texts.append('%s:%s' % prop)
75
                except:
76
                    raise repr(prop)
1185.82.102 by Aaron Bentley
Start abstracting action line writing
77
        text = ['=== ']
78
        text.append(' // '.join(p_texts))
1185.82.106 by Aaron Bentley
Use elipsis to continue long meta lines
79
        text_line = ''.join(text).encode('utf-8')
80
        available = 79
81
        while len(text_line) > available:
82
            to_file.write(text_line[:available])
83
            text_line = text_line[available:]
84
            to_file.write('\n... ')
85
            available = 79 - len('... ')
86
        to_file.write(text_line+'\n')
1185.82.102 by Aaron Bentley
Start abstracting action line writing
87
88
1551.7.3 by Aaron Bentley
Fix strict testaments, as_sha1
89
class BundleSerializerV08(BundleSerializer):
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
90
    def read(self, f):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
91
        """Read the rest of the bundles from the supplied file.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
92
93
        :param f: The file to read from
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
94
        :return: A list of bundles
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
95
        """
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
96
        return BundleReader(f).info
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
97
1185.82.74 by Aaron Bentley
Allow custom base for any revision
98
    def write(self, source, revision_ids, forced_bases, f):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
99
        """Write the bundless to the supplied files.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
100
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
101
        :param source: A source for revision information
102
        :param revision_ids: The list of revision ids to serialize
1185.82.74 by Aaron Bentley
Allow custom base for any revision
103
        :param forced_bases: A dict of revision -> base that overrides default
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
104
        :param f: The file to output to
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
105
        """
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
106
        self.source = source
107
        self.revision_ids = revision_ids
1185.82.74 by Aaron Bentley
Allow custom base for any revision
108
        self.forced_bases = forced_bases
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
109
        self.to_file = f
110
        source.lock_read()
111
        try:
112
            self._write_main_header()
1185.82.111 by Aaron Bentley
Remove progress indicator from cset command for now
113
            pb = DummyProgress()
1185.82.92 by Aaron Bentley
Add progress bar for changeset generation
114
            try:
115
                self._write_revisions(pb)
116
            finally:
1185.82.111 by Aaron Bentley
Remove progress indicator from cset command for now
117
                pass
118
                #pb.finished()
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
119
        finally:
120
            source.unlock()
121
122
    def _write_main_header(self):
123
        """Write the header for the changes"""
124
        f = self.to_file
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
125
        f.write(BUNDLE_HEADER)
1551.7.3 by Aaron Bentley
Fix strict testaments, as_sha1
126
        f.write('0.8\n')
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
127
        f.write('#\n')
128
129
    def _write(self, key, value, indent=1):
130
        """Write out meta information, with proper indenting, etc"""
131
        assert indent > 0, 'indentation must be greater than 0'
132
        f = self.to_file
133
        f.write('#' + (' ' * indent))
134
        f.write(key.encode('utf-8'))
135
        if not value:
136
            f.write(':\n')
1185.82.28 by Aaron Bentley
Got parent_id handling working
137
        elif isinstance(value, basestring):
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
138
            f.write(': ')
139
            f.write(value.encode('utf-8'))
140
            f.write('\n')
141
        else:
142
            f.write(':\n')
1185.82.28 by Aaron Bentley
Got parent_id handling working
143
            for entry in value:
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
144
                f.write('#' + (' ' * (indent+2)))
1185.82.32 by Aaron Bentley
Got all tests passing using serializer
145
                f.write(entry.encode('utf-8'))
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
146
                f.write('\n')
147
1185.82.92 by Aaron Bentley
Add progress bar for changeset generation
148
    def _write_revisions(self, pb):
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
149
        """Write the information for all of the revisions."""
150
151
        # Optimize for the case of revisions in order
152
        last_rev_id = None
153
        last_rev_tree = None
154
1185.82.92 by Aaron Bentley
Add progress bar for changeset generation
155
        i_max = len(self.revision_ids) 
156
        for i, rev_id in enumerate(self.revision_ids):
157
            pb.update("Generating revsion data", i, i_max)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
158
            rev = self.source.get_revision(rev_id)
1185.82.91 by Aaron Bentley
Updated the write_changeset optimization
159
            if rev_id == last_rev_id:
160
                rev_tree = last_rev_tree
161
            else:
162
                base_tree = self.source.revision_tree(rev_id)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
163
            rev_tree = self.source.revision_tree(rev_id)
1185.82.74 by Aaron Bentley
Allow custom base for any revision
164
            if rev_id in self.forced_bases:
165
                explicit_base = True
166
                base_id = self.forced_bases[rev_id]
167
                if base_id is None:
168
                    base_id = NULL_REVISION
1185.82.72 by Aaron Bentley
Always use leftmost base for changesets
169
            else:
1185.82.74 by Aaron Bentley
Allow custom base for any revision
170
                explicit_base = False
171
                if rev.parent_ids:
172
                    base_id = rev.parent_ids[-1]
173
                else:
174
                    base_id = NULL_REVISION
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
175
176
            if base_id == last_rev_id:
177
                base_tree = last_rev_tree
178
            else:
179
                base_tree = self.source.revision_tree(base_id)
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
180
            force_binary = (i != 0)
1185.82.74 by Aaron Bentley
Allow custom base for any revision
181
            self._write_revision(rev, rev_tree, base_id, base_tree, 
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
182
                                 explicit_base, force_binary)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
183
1185.82.91 by Aaron Bentley
Updated the write_changeset optimization
184
            last_rev_id = base_id
185
            last_rev_tree = base_tree
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
186
1185.82.74 by Aaron Bentley
Allow custom base for any revision
187
    def _write_revision(self, rev, rev_tree, base_rev, base_tree, 
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
188
                        explicit_base, force_binary):
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
189
        """Write out the information for a revision."""
190
        def w(key, value):
191
            self._write(key, value, indent=1)
192
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
193
        w('message', rev.message.split('\n'))
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
194
        w('committer', rev.committer)
195
        w('date', format_highres_date(rev.timestamp, rev.timezone))
196
        self.to_file.write('\n')
197
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
198
        self._write_delta(rev_tree, base_tree, rev.revision_id, force_binary)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
199
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
200
        w('revision id', rev.revision_id)
1185.82.121 by Aaron Bentley
Move calculation of Testament sha1s to Testament
201
        w('sha1', StrictTestament.from_revision(self.source, 
202
                                                rev.revision_id).as_sha1())
1185.82.29 by Aaron Bentley
Got merge test working
203
        w('inventory sha1', rev.inventory_sha1)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
204
        if rev.parent_ids:
1185.82.28 by Aaron Bentley
Got parent_id handling working
205
            w('parent ids', rev.parent_ids)
1185.82.74 by Aaron Bentley
Allow custom base for any revision
206
        if explicit_base:
207
            w('base id', base_rev)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
208
        if rev.properties:
209
            self._write('properties', None, indent=1)
210
            for name, value in rev.properties.items():
211
                self._write(name, value, indent=3)
212
        
213
        # Add an extra blank space at the end
214
        self.to_file.write('\n')
215
1185.82.101 by Aaron Bentley
Start using a standard action writer
216
    def _write_action(self, name, parameters, properties=None):
217
        if properties is None:
218
            properties = []
219
        p_texts = ['%s:%s' % v for v in properties]
220
        self.to_file.write('=== ')
221
        self.to_file.write(' '.join([name]+parameters).encode('utf-8'))
222
        self.to_file.write(' // '.join(p_texts).encode('utf-8'))
223
        self.to_file.write('\n')
224
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
225
    def _write_delta(self, new_tree, old_tree, default_revision_id, 
226
                     force_binary):
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
227
        """Write out the changes between the trees."""
228
        DEVNULL = '/dev/null'
229
        old_label = ''
230
        new_label = ''
231
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
232
        def do_diff(file_id, old_path, new_path, action, force_binary):
1185.82.96 by Aaron Bentley
Got first binary test passing
233
            def tree_lines(tree, require_text=False):
234
                if file_id in tree:
235
                    tree_file = tree.get_file(file_id)
236
                    if require_text is True:
237
                        tree_file = text_file(tree_file)
238
                    return tree_file.readlines()
239
                else:
240
                    return []
241
242
            try:
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
243
                if force_binary:
244
                    raise errors.BinaryFile()
1185.82.96 by Aaron Bentley
Got first binary test passing
245
                old_lines = tree_lines(old_tree, require_text=True)
246
                new_lines = tree_lines(new_tree, require_text=True)
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
247
                action.write(self.to_file)
1185.82.96 by Aaron Bentley
Got first binary test passing
248
                internal_diff(old_path, old_lines, new_path, new_lines, 
249
                              self.to_file)
250
            except errors.BinaryFile:
251
                old_lines = tree_lines(old_tree, require_text=False)
252
                new_lines = tree_lines(new_tree, require_text=False)
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
253
                action.add_property('encoding', 'base64')
254
                action.write(self.to_file)
1185.82.96 by Aaron Bentley
Got first binary test passing
255
                binary_diff(old_path, old_lines, new_path, new_lines, 
256
                            self.to_file)
257
1185.82.104 by Aaron Bentley
Refactored action writing
258
        def finish_action(action, file_id, kind, meta_modified, text_modified,
259
                          old_path, new_path):
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
260
            entry = new_tree.inventory[file_id]
261
            if entry.revision != default_revision_id:
262
                action.add_property('last-changed', entry.revision)
1185.82.104 by Aaron Bentley
Refactored action writing
263
            if meta_modified:
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
264
                action.add_bool_property('executable', entry.executable)
1185.82.104 by Aaron Bentley
Refactored action writing
265
            if text_modified and kind == "symlink":
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
266
                action.add_property('target', entry.symlink_target)
1185.82.104 by Aaron Bentley
Refactored action writing
267
            if text_modified and kind == "file":
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
268
                do_diff(file_id, old_path, new_path, action, force_binary)
1185.82.104 by Aaron Bentley
Refactored action writing
269
            else:
270
                action.write(self.to_file)
271
1185.82.117 by Aaron Bentley
Handle last-modified changes on their own
272
        delta = compare_trees(old_tree, new_tree, want_unchanged=True)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
273
        for path, file_id, kind in delta.removed:
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
274
            action = Action('removed', [kind, path]).write(self.to_file)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
275
276
        for path, file_id, kind in delta.added:
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
277
            action = Action('added', [kind, path], [('file-id', file_id)])
1185.82.119 by Aaron Bentley
Default execute bit to no for new files, directories, symlinks
278
            meta_modified = (kind=='file' and 
279
                             new_tree.is_executable(file_id))
280
            finish_action(action, file_id, kind, meta_modified, True,
1185.82.104 by Aaron Bentley
Refactored action writing
281
                          DEVNULL, path)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
282
283
        for (old_path, new_path, file_id, kind,
284
             text_modified, meta_modified) in delta.renamed:
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
285
            action = Action('renamed', [kind, old_path], [(new_path,)])
1185.82.104 by Aaron Bentley
Refactored action writing
286
            finish_action(action, file_id, kind, meta_modified, text_modified,
287
                          old_path, new_path)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
288
289
        for (path, file_id, kind,
290
             text_modified, meta_modified) in delta.modified:
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
291
            action = Action('modified', [kind, path])
1185.82.104 by Aaron Bentley
Refactored action writing
292
            finish_action(action, file_id, kind, meta_modified, text_modified,
293
                          path, path)
1185.82.117 by Aaron Bentley
Handle last-modified changes on their own
294
295
        for path, file_id, kind in delta.unchanged:
296
            ie = new_tree.inventory[file_id]
297
            new_rev = getattr(ie, 'revision', None)
298
            if new_rev is None:
299
                continue
300
            old_rev = getattr(old_tree.inventory[ie.file_id], 'revision', None)
301
            if new_rev != old_rev:
302
                action = Action('modified', [ie.kind, 
303
                                             new_tree.id2path(ie.file_id)])
304
                action.add_property('last-changed', ie.revision)
305
                action.write(self.to_file)
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
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:
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
332
            if not self._read_revision_header():
333
                break
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
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):
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
363
        found_something = False
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
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
1793.3.16 by John Arbash Meinel
Add tests to ensure that we gracefully handle opening and trailing non-bundle text.
370
            if not line.startswith('#'):
371
                continue
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
372
            found_something = True
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
373
            self._handle_next(line)
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
374
        if not found_something:
375
            # Nothing was there, so remove the added revision
376
            self.info.revisions.pop()
377
        return found_something
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
378
379
    def _read_next_entry(self, line, indent=1):
380
        """Read in a key-value pair
381
        """
382
        if not line.startswith('#'):
1793.3.15 by John Arbash Meinel
Raise the right errors
383
            raise errors.MalformedHeader('Bzr header did not start with #')
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
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:
1793.3.15 by John Arbash Meinel
Raise the right errors
400
            raise errors.MalformedHeader('While looking for key: value pairs,'
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
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:
1793.3.15 by John Arbash Meinel
Raise the right errors
420
                raise errors.MalformedHeader('Duplicated Key: %s' % key)
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
421
        else:
422
            # What do we do with a key we don't recognize
1793.3.15 by John Arbash Meinel
Raise the right errors
423
            raise errors.MalformedHeader('Unknown Key: "%s"' % key)
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
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('==='):
1793.3.15 by John Arbash Meinel
Raise the right errors
460
                    raise errors.MalformedPatches('The first line of all patches'
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
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)
1793.3.14 by John Arbash Meinel
Actually fix the bug with missing trailing newline bug #49182
498
            if self._next_line is None:
499
                break
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
500
            if not self._next_line.startswith('#'):
1793.3.14 by John Arbash Meinel
Actually fix the bug with missing trailing newline bug #49182
501
                # Consume the trailing \n and stop processing
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
502
                self._next().next()
503
                break