/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.4 by John Arbash Meinel
Created output format, slightly simplified code
30
from bzrlib.diff import internal_diff
1185.82.78 by Aaron Bentley
Cleanups
31
from bzrlib.osutils import pathjoin
1185.82.111 by Aaron Bentley
Remove progress indicator from cset command for now
32
from bzrlib.progress import DummyProgress
1185.82.78 by Aaron Bentley
Cleanups
33
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.
34
from bzrlib.rio import RioWriter, read_stanzas
1185.82.92 by Aaron Bentley
Add progress bar for changeset generation
35
import bzrlib.ui
1185.82.121 by Aaron Bentley
Move calculation of Testament sha1s to Testament
36
from bzrlib.testament import StrictTestament
1185.82.96 by Aaron Bentley
Got first binary test passing
37
from bzrlib.textfile import text_file
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
38
from bzrlib.trace import mutter
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
39
1185.82.65 by Aaron Bentley
Factored out boolean text stuff
40
bool_text = {True: 'yes', False: 'no'}
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
41
1185.82.102 by Aaron Bentley
Start abstracting action line writing
42
43
class Action(object):
44
    """Represent an action"""
45
46
    def __init__(self, name, parameters=None, properties=None):
47
        self.name = name
48
        if parameters is None:
49
            self.parameters = []
50
        else:
51
            self.parameters = parameters
52
        if properties is None:
53
            self.properties = []
54
        else:
55
            self.properties = properties
56
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
57
    def add_property(self, name, value):
58
        """Add a property to the action"""
59
        self.properties.append((name, value))
60
61
    def add_bool_property(self, name, value):
62
        """Add a boolean property to the action"""
63
        self.add_property(name, bool_text[value])
64
1185.82.102 by Aaron Bentley
Start abstracting action line writing
65
    def write(self, to_file):
66
        """Write action as to a file"""
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
67
        p_texts = [' '.join([self.name]+self.parameters)]
68
        for prop in self.properties:
69
            if len(prop) == 1:
70
                p_texts.append(prop[0])
71
            else:
72
                try:
73
                    p_texts.append('%s:%s' % prop)
74
                except:
75
                    raise repr(prop)
1185.82.102 by Aaron Bentley
Start abstracting action line writing
76
        text = ['=== ']
77
        text.append(' // '.join(p_texts))
1185.82.106 by Aaron Bentley
Use elipsis to continue long meta lines
78
        text_line = ''.join(text).encode('utf-8')
79
        available = 79
80
        while len(text_line) > available:
81
            to_file.write(text_line[:available])
82
            text_line = text_line[available:]
83
            to_file.write('\n... ')
84
            available = 79 - len('... ')
85
        to_file.write(text_line+'\n')
1185.82.102 by Aaron Bentley
Start abstracting action line writing
86
87
1551.7.3 by Aaron Bentley
Fix strict testaments, as_sha1
88
class BundleSerializerV08(BundleSerializer):
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
89
    def read(self, f):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
90
        """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.
91
92
        :param f: The file to read from
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
93
        :return: A list of bundles
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
94
        """
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
95
        return BundleReader(f).info
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
96
1185.82.74 by Aaron Bentley
Allow custom base for any revision
97
    def write(self, source, revision_ids, forced_bases, f):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
98
        """Write the bundless to the supplied files.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
99
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
100
        :param source: A source for revision information
101
        :param revision_ids: The list of revision ids to serialize
1185.82.74 by Aaron Bentley
Allow custom base for any revision
102
        :param forced_bases: A dict of revision -> base that overrides default
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
103
        :param f: The file to output to
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
104
        """
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
105
        self.source = source
106
        self.revision_ids = revision_ids
1185.82.74 by Aaron Bentley
Allow custom base for any revision
107
        self.forced_bases = forced_bases
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
108
        self.to_file = f
109
        source.lock_read()
110
        try:
111
            self._write_main_header()
1185.82.111 by Aaron Bentley
Remove progress indicator from cset command for now
112
            pb = DummyProgress()
1185.82.92 by Aaron Bentley
Add progress bar for changeset generation
113
            try:
114
                self._write_revisions(pb)
115
            finally:
1185.82.111 by Aaron Bentley
Remove progress indicator from cset command for now
116
                pass
117
                #pb.finished()
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
118
        finally:
119
            source.unlock()
120
121
    def _write_main_header(self):
122
        """Write the header for the changes"""
123
        f = self.to_file
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
124
        f.write(BUNDLE_HEADER)
1551.7.3 by Aaron Bentley
Fix strict testaments, as_sha1
125
        f.write('0.8\n')
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
126
        f.write('#\n')
127
128
    def _write(self, key, value, indent=1):
129
        """Write out meta information, with proper indenting, etc"""
130
        assert indent > 0, 'indentation must be greater than 0'
131
        f = self.to_file
132
        f.write('#' + (' ' * indent))
133
        f.write(key.encode('utf-8'))
134
        if not value:
135
            f.write(':\n')
1185.82.28 by Aaron Bentley
Got parent_id handling working
136
        elif isinstance(value, basestring):
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
137
            f.write(': ')
138
            f.write(value.encode('utf-8'))
139
            f.write('\n')
140
        else:
141
            f.write(':\n')
1185.82.28 by Aaron Bentley
Got parent_id handling working
142
            for entry in value:
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
143
                f.write('#' + (' ' * (indent+2)))
1185.82.32 by Aaron Bentley
Got all tests passing using serializer
144
                f.write(entry.encode('utf-8'))
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
145
                f.write('\n')
146
1185.82.92 by Aaron Bentley
Add progress bar for changeset generation
147
    def _write_revisions(self, pb):
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
148
        """Write the information for all of the revisions."""
149
150
        # Optimize for the case of revisions in order
151
        last_rev_id = None
152
        last_rev_tree = None
153
1185.82.92 by Aaron Bentley
Add progress bar for changeset generation
154
        i_max = len(self.revision_ids) 
155
        for i, rev_id in enumerate(self.revision_ids):
156
            pb.update("Generating revsion data", i, i_max)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
157
            rev = self.source.get_revision(rev_id)
1185.82.91 by Aaron Bentley
Updated the write_changeset optimization
158
            if rev_id == last_rev_id:
159
                rev_tree = last_rev_tree
160
            else:
161
                base_tree = self.source.revision_tree(rev_id)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
162
            rev_tree = self.source.revision_tree(rev_id)
1185.82.74 by Aaron Bentley
Allow custom base for any revision
163
            if rev_id in self.forced_bases:
164
                explicit_base = True
165
                base_id = self.forced_bases[rev_id]
166
                if base_id is None:
167
                    base_id = NULL_REVISION
1185.82.72 by Aaron Bentley
Always use leftmost base for changesets
168
            else:
1185.82.74 by Aaron Bentley
Allow custom base for any revision
169
                explicit_base = False
170
                if rev.parent_ids:
171
                    base_id = rev.parent_ids[-1]
172
                else:
173
                    base_id = NULL_REVISION
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
174
175
            if base_id == last_rev_id:
176
                base_tree = last_rev_tree
177
            else:
178
                base_tree = self.source.revision_tree(base_id)
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
179
            force_binary = (i != 0)
1185.82.74 by Aaron Bentley
Allow custom base for any revision
180
            self._write_revision(rev, rev_tree, base_id, base_tree, 
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
181
                                 explicit_base, force_binary)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
182
1185.82.91 by Aaron Bentley
Updated the write_changeset optimization
183
            last_rev_id = base_id
184
            last_rev_tree = base_tree
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
185
1185.82.74 by Aaron Bentley
Allow custom base for any revision
186
    def _write_revision(self, rev, rev_tree, base_rev, base_tree, 
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
187
                        explicit_base, force_binary):
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
188
        """Write out the information for a revision."""
189
        def w(key, value):
190
            self._write(key, value, indent=1)
191
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
192
        w('message', rev.message.split('\n'))
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
193
        w('committer', rev.committer)
194
        w('date', format_highres_date(rev.timestamp, rev.timezone))
195
        self.to_file.write('\n')
196
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
197
        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
198
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
199
        w('revision id', rev.revision_id)
1185.82.121 by Aaron Bentley
Move calculation of Testament sha1s to Testament
200
        w('sha1', StrictTestament.from_revision(self.source, 
201
                                                rev.revision_id).as_sha1())
1185.82.29 by Aaron Bentley
Got merge test working
202
        w('inventory sha1', rev.inventory_sha1)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
203
        if rev.parent_ids:
1185.82.28 by Aaron Bentley
Got parent_id handling working
204
            w('parent ids', rev.parent_ids)
1185.82.74 by Aaron Bentley
Allow custom base for any revision
205
        if explicit_base:
206
            w('base id', base_rev)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
207
        if rev.properties:
208
            self._write('properties', None, indent=1)
209
            for name, value in rev.properties.items():
210
                self._write(name, value, indent=3)
211
        
212
        # Add an extra blank space at the end
213
        self.to_file.write('\n')
214
1185.82.101 by Aaron Bentley
Start using a standard action writer
215
    def _write_action(self, name, parameters, properties=None):
216
        if properties is None:
217
            properties = []
218
        p_texts = ['%s:%s' % v for v in properties]
219
        self.to_file.write('=== ')
220
        self.to_file.write(' '.join([name]+parameters).encode('utf-8'))
221
        self.to_file.write(' // '.join(p_texts).encode('utf-8'))
222
        self.to_file.write('\n')
223
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
224
    def _write_delta(self, new_tree, old_tree, default_revision_id, 
225
                     force_binary):
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
226
        """Write out the changes between the trees."""
227
        DEVNULL = '/dev/null'
228
        old_label = ''
229
        new_label = ''
230
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
231
        def do_diff(file_id, old_path, new_path, action, force_binary):
1185.82.96 by Aaron Bentley
Got first binary test passing
232
            def tree_lines(tree, require_text=False):
233
                if file_id in tree:
234
                    tree_file = tree.get_file(file_id)
235
                    if require_text is True:
236
                        tree_file = text_file(tree_file)
237
                    return tree_file.readlines()
238
                else:
239
                    return []
240
241
            try:
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
242
                if force_binary:
243
                    raise errors.BinaryFile()
1185.82.96 by Aaron Bentley
Got first binary test passing
244
                old_lines = tree_lines(old_tree, require_text=True)
245
                new_lines = tree_lines(new_tree, require_text=True)
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
246
                action.write(self.to_file)
1185.82.96 by Aaron Bentley
Got first binary test passing
247
                internal_diff(old_path, old_lines, new_path, new_lines, 
248
                              self.to_file)
249
            except errors.BinaryFile:
250
                old_lines = tree_lines(old_tree, require_text=False)
251
                new_lines = tree_lines(new_tree, require_text=False)
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
252
                action.add_property('encoding', 'base64')
253
                action.write(self.to_file)
1185.82.96 by Aaron Bentley
Got first binary test passing
254
                binary_diff(old_path, old_lines, new_path, new_lines, 
255
                            self.to_file)
256
1185.82.104 by Aaron Bentley
Refactored action writing
257
        def finish_action(action, file_id, kind, meta_modified, text_modified,
258
                          old_path, new_path):
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
259
            entry = new_tree.inventory[file_id]
1907.1.1 by Aaron Bentley
Unshelved all changes except those related to removing RootEntry
260
            if entry.revision != default_revision_id:
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
261
                action.add_property('last-changed', entry.revision)
1185.82.104 by Aaron Bentley
Refactored action writing
262
            if meta_modified:
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
263
                action.add_bool_property('executable', entry.executable)
1185.82.104 by Aaron Bentley
Refactored action writing
264
            if text_modified and kind == "symlink":
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
265
                action.add_property('target', entry.symlink_target)
1185.82.104 by Aaron Bentley
Refactored action writing
266
            if text_modified and kind == "file":
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
267
                do_diff(file_id, old_path, new_path, action, force_binary)
1185.82.104 by Aaron Bentley
Refactored action writing
268
            else:
269
                action.write(self.to_file)
270
1907.1.1 by Aaron Bentley
Unshelved all changes except those related to removing RootEntry
271
        delta = new_tree.changes_from(old_tree, want_unchanged=True)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
272
        for path, file_id, kind in delta.removed:
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
273
            action = Action('removed', [kind, path]).write(self.to_file)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
274
275
        for path, file_id, kind in delta.added:
1907.1.1 by Aaron Bentley
Unshelved all changes except those related to removing RootEntry
276
            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
277
            meta_modified = (kind=='file' and 
278
                             new_tree.is_executable(file_id))
279
            finish_action(action, file_id, kind, meta_modified, True,
1185.82.104 by Aaron Bentley
Refactored action writing
280
                          DEVNULL, path)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
281
282
        for (old_path, new_path, file_id, kind,
283
             text_modified, meta_modified) in delta.renamed:
1907.1.1 by Aaron Bentley
Unshelved all changes except those related to removing RootEntry
284
            action = Action('renamed', [kind, old_path], [(new_path,)])
1185.82.104 by Aaron Bentley
Refactored action writing
285
            finish_action(action, file_id, kind, meta_modified, text_modified,
286
                          old_path, new_path)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
287
288
        for (path, file_id, kind,
289
             text_modified, meta_modified) in delta.modified:
1907.1.1 by Aaron Bentley
Unshelved all changes except those related to removing RootEntry
290
            action = Action('modified', [kind, path])
1185.82.104 by Aaron Bentley
Refactored action writing
291
            finish_action(action, file_id, kind, meta_modified, text_modified,
292
                          path, path)
1185.82.117 by Aaron Bentley
Handle last-modified changes on their own
293
294
        for path, file_id, kind in delta.unchanged:
295
            ie = new_tree.inventory[file_id]
296
            new_rev = getattr(ie, 'revision', None)
297
            if new_rev is None:
298
                continue
299
            old_rev = getattr(old_tree.inventory[ie.file_id], 'revision', None)
300
            if new_rev != old_rev:
1907.1.1 by Aaron Bentley
Unshelved all changes except those related to removing RootEntry
301
                action = Action('modified', [ie.kind, 
302
                                             new_tree.id2path(ie.file_id)])
303
                action.add_property('last-changed', ie.revision)
1185.82.117 by Aaron Bentley
Handle last-modified changes on their own
304
                action.write(self.to_file)
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
305
306
307
class BundleReader(object):
308
    """This class reads in a bundle from a file, and returns
309
    a Bundle object, which can then be applied against a tree.
310
    """
311
    def __init__(self, from_file):
312
        """Read in the bundle from the file.
313
314
        :param from_file: A file-like object (must have iterator support).
315
        """
316
        object.__init__(self)
317
        self.from_file = iter(from_file)
318
        self._next_line = None
319
        
1910.2.1 by Aaron Bentley
Ensure root entry always has a revision
320
        self.info = BundleInfo08()
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
321
        # We put the actual inventory ids in the footer, so that the patch
322
        # is easier to read for humans.
323
        # Unfortunately, that means we need to read everything before we
324
        # can create a proper bundle.
325
        self._read()
326
        self._validate()
327
328
    def _read(self):
329
        self._next().next()
330
        while self._next_line is not None:
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
331
            if not self._read_revision_header():
332
                break
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
333
            if self._next_line is None:
334
                break
335
            self._read_patches()
336
            self._read_footer()
337
338
    def _validate(self):
339
        """Make sure that the information read in makes sense
340
        and passes appropriate checksums.
341
        """
342
        # Fill in all the missing blanks for the revisions
343
        # and generate the real_revisions list.
344
        self.info.complete_info()
345
346
    def _next(self):
347
        """yield the next line, but secretly
348
        keep 1 extra line for peeking.
349
        """
350
        for line in self.from_file:
351
            last = self._next_line
352
            self._next_line = line
353
            if last is not None:
354
                #mutter('yielding line: %r' % last)
355
                yield last
356
        last = self._next_line
357
        self._next_line = None
358
        #mutter('yielding line: %r' % last)
359
        yield last
360
361
    def _read_revision_header(self):
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
362
        found_something = False
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
363
        self.info.revisions.append(RevisionInfo(None))
364
        for line in self._next():
365
            # The bzr header is terminated with a blank line
366
            # which does not start with '#'
367
            if line is None or line == '\n':
368
                break
1793.3.16 by John Arbash Meinel
Add tests to ensure that we gracefully handle opening and trailing non-bundle text.
369
            if not line.startswith('#'):
370
                continue
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
371
            found_something = True
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
372
            self._handle_next(line)
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
373
        if not found_something:
374
            # Nothing was there, so remove the added revision
375
            self.info.revisions.pop()
376
        return found_something
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
377
378
    def _read_next_entry(self, line, indent=1):
379
        """Read in a key-value pair
380
        """
381
        if not line.startswith('#'):
1793.3.15 by John Arbash Meinel
Raise the right errors
382
            raise errors.MalformedHeader('Bzr header did not start with #')
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
383
        line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
384
        if line[:indent] == ' '*indent:
385
            line = line[indent:]
386
        if not line:
387
            return None, None# Ignore blank lines
388
389
        loc = line.find(': ')
390
        if loc != -1:
391
            key = line[:loc]
392
            value = line[loc+2:]
393
            if not value:
394
                value = self._read_many(indent=indent+2)
395
        elif line[-1:] == ':':
396
            key = line[:-1]
397
            value = self._read_many(indent=indent+2)
398
        else:
1793.3.15 by John Arbash Meinel
Raise the right errors
399
            raise errors.MalformedHeader('While looking for key: value pairs,'
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
400
                    ' did not find the colon %r' % (line))
401
402
        key = key.replace(' ', '_')
403
        #mutter('found %s: %s' % (key, value))
404
        return key, value
405
406
    def _handle_next(self, line):
407
        if line is None:
408
            return
409
        key, value = self._read_next_entry(line, indent=1)
410
        mutter('_handle_next %r => %r' % (key, value))
411
        if key is None:
412
            return
413
414
        revision_info = self.info.revisions[-1]
415
        if hasattr(revision_info, key):
416
            if getattr(revision_info, key) is None:
417
                setattr(revision_info, key, value)
418
            else:
1793.3.15 by John Arbash Meinel
Raise the right errors
419
                raise errors.MalformedHeader('Duplicated Key: %s' % key)
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
420
        else:
421
            # What do we do with a key we don't recognize
1793.3.15 by John Arbash Meinel
Raise the right errors
422
            raise errors.MalformedHeader('Unknown Key: "%s"' % key)
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
423
    
424
    def _read_many(self, indent):
425
        """If a line ends with no entry, that means that it should be
426
        followed with multiple lines of values.
427
428
        This detects the end of the list, because it will be a line that
429
        does not start properly indented.
430
        """
431
        values = []
432
        start = '#' + (' '*indent)
433
434
        if self._next_line is None or self._next_line[:len(start)] != start:
435
            return values
436
437
        for line in self._next():
438
            values.append(line[len(start):-1].decode('utf-8'))
439
            if self._next_line is None or self._next_line[:len(start)] != start:
440
                break
441
        return values
442
443
    def _read_one_patch(self):
444
        """Read in one patch, return the complete patch, along with
445
        the next line.
446
447
        :return: action, lines, do_continue
448
        """
449
        #mutter('_read_one_patch: %r' % self._next_line)
450
        # Peek and see if there are no patches
451
        if self._next_line is None or self._next_line.startswith('#'):
452
            return None, [], False
453
454
        first = True
455
        lines = []
456
        for line in self._next():
457
            if first:
458
                if not line.startswith('==='):
1793.3.15 by John Arbash Meinel
Raise the right errors
459
                    raise errors.MalformedPatches('The first line of all patches'
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
460
                        ' should be a bzr meta line "==="'
461
                        ': %r' % line)
462
                action = line[4:-1].decode('utf-8')
463
            elif line.startswith('... '):
464
                action += line[len('... '):-1].decode('utf-8')
465
466
            if (self._next_line is not None and 
467
                self._next_line.startswith('===')):
468
                return action, lines, True
469
            elif self._next_line is None or self._next_line.startswith('#'):
470
                return action, lines, False
471
472
            if first:
473
                first = False
474
            elif not line.startswith('... '):
475
                lines.append(line)
476
477
        return action, lines, False
478
            
479
    def _read_patches(self):
480
        do_continue = True
481
        revision_actions = []
482
        while do_continue:
483
            action, lines, do_continue = self._read_one_patch()
484
            if action is not None:
485
                revision_actions.append((action, lines))
486
        assert self.info.revisions[-1].tree_actions is None
487
        self.info.revisions[-1].tree_actions = revision_actions
488
489
    def _read_footer(self):
490
        """Read the rest of the meta information.
491
492
        :param first_line:  The previous step iterates past what it
493
                            can handle. That extra line is given here.
494
        """
495
        for line in self._next():
496
            self._handle_next(line)
1793.3.14 by John Arbash Meinel
Actually fix the bug with missing trailing newline bug #49182
497
            if self._next_line is None:
498
                break
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
499
            if not self._next_line.startswith('#'):
1793.3.14 by John Arbash Meinel
Actually fix the bug with missing trailing newline bug #49182
500
                # Consume the trailing \n and stop processing
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
501
                self._next().next()
502
                break
1910.2.1 by Aaron Bentley
Ensure root entry always has a revision
503
1910.2.5 by Aaron Bentley
Fix whitespace
504
1910.2.1 by Aaron Bentley
Ensure root entry always has a revision
505
class BundleInfo08(BundleInfo):
506
    def _update_tree(self, bundle_tree, revision_id):
507
        bundle_tree.note_last_changed('', revision_id)
508
        BundleInfo._update_tree(self, bundle_tree, revision_id)