/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
1927.1.3 by John Arbash Meinel
Get 10% savings by just fixing a bug
150
        # Optimize for the case of revisions in order
151
        last_rev_id = None
152
        last_rev_tree = None
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
153
1927.1.3 by John Arbash Meinel
Get 10% savings by just fixing a bug
154
        i_max = len(self.revision_ids) 
155
        for i, rev_id in enumerate(self.revision_ids):
1185.82.92 by Aaron Bentley
Add progress bar for changeset generation
156
            pb.update("Generating revsion data", i, i_max)
1927.1.3 by John Arbash Meinel
Get 10% savings by just fixing a bug
157
            rev = self.source.get_revision(rev_id)
158
            if rev_id == last_rev_id:
159
                rev_tree = last_rev_tree
160
            else:
161
                rev_tree = self.source.revision_tree(rev_id)
162
            if rev_id in self.forced_bases:
1185.82.74 by Aaron Bentley
Allow custom base for any revision
163
                explicit_base = True
1927.1.3 by John Arbash Meinel
Get 10% savings by just fixing a bug
164
                base_id = self.forced_bases[rev_id]
1185.82.74 by Aaron Bentley
Allow custom base for any revision
165
                if base_id is None:
166
                    base_id = NULL_REVISION
1185.82.72 by Aaron Bentley
Always use leftmost base for changesets
167
            else:
1185.82.74 by Aaron Bentley
Allow custom base for any revision
168
                explicit_base = False
169
                if rev.parent_ids:
170
                    base_id = rev.parent_ids[-1]
171
                else:
172
                    base_id = NULL_REVISION
1927.1.3 by John Arbash Meinel
Get 10% savings by just fixing a bug
173
174
            if base_id == last_rev_id:
175
                base_tree = last_rev_tree
176
            else:
177
                base_tree = self.source.revision_tree(base_id)
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
178
            force_binary = (i != 0)
1927.1.3 by John Arbash Meinel
Get 10% savings by just fixing a bug
179
            self._write_revision(rev, rev_tree, base_id, base_tree, 
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
180
                                 explicit_base, force_binary)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
181
1927.1.3 by John Arbash Meinel
Get 10% savings by just fixing a bug
182
            last_rev_id = base_id
183
            last_rev_tree = base_tree
184
185
    def _write_revision(self, rev, rev_tree, base_rev, base_tree, 
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
186
                        explicit_base, force_binary):
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
187
        """Write out the information for a revision."""
188
        def w(key, value):
189
            self._write(key, value, indent=1)
190
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
191
        w('message', rev.message.split('\n'))
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
192
        w('committer', rev.committer)
193
        w('date', format_highres_date(rev.timestamp, rev.timezone))
194
        self.to_file.write('\n')
195
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
196
        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
197
1185.82.79 by Aaron Bentley
Move message to top, revision-id to footer
198
        w('revision id', rev.revision_id)
1185.82.121 by Aaron Bentley
Move calculation of Testament sha1s to Testament
199
        w('sha1', StrictTestament.from_revision(self.source, 
200
                                                rev.revision_id).as_sha1())
1185.82.29 by Aaron Bentley
Got merge test working
201
        w('inventory sha1', rev.inventory_sha1)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
202
        if rev.parent_ids:
1185.82.28 by Aaron Bentley
Got parent_id handling working
203
            w('parent ids', rev.parent_ids)
1185.82.74 by Aaron Bentley
Allow custom base for any revision
204
        if explicit_base:
205
            w('base id', base_rev)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
206
        if rev.properties:
207
            self._write('properties', None, indent=1)
208
            for name, value in rev.properties.items():
209
                self._write(name, value, indent=3)
210
        
211
        # Add an extra blank space at the end
212
        self.to_file.write('\n')
213
1185.82.101 by Aaron Bentley
Start using a standard action writer
214
    def _write_action(self, name, parameters, properties=None):
215
        if properties is None:
216
            properties = []
217
        p_texts = ['%s:%s' % v for v in properties]
218
        self.to_file.write('=== ')
219
        self.to_file.write(' '.join([name]+parameters).encode('utf-8'))
220
        self.to_file.write(' // '.join(p_texts).encode('utf-8'))
221
        self.to_file.write('\n')
222
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
223
    def _write_delta(self, new_tree, old_tree, default_revision_id, 
224
                     force_binary):
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
225
        """Write out the changes between the trees."""
226
        DEVNULL = '/dev/null'
227
        old_label = ''
228
        new_label = ''
229
1731.1.33 by Aaron Bentley
Revert no-special-root changes
230
        def bundle_path(path):
231
            if path != '':
232
                return path
233
            else:
234
                return '.'
235
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
236
        def do_diff(file_id, old_path, new_path, action, force_binary):
1185.82.96 by Aaron Bentley
Got first binary test passing
237
            def tree_lines(tree, require_text=False):
238
                if file_id in tree:
239
                    tree_file = tree.get_file(file_id)
240
                    if require_text is True:
241
                        tree_file = text_file(tree_file)
242
                    return tree_file.readlines()
243
                else:
244
                    return []
245
246
            try:
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
247
                if force_binary:
248
                    raise errors.BinaryFile()
1185.82.96 by Aaron Bentley
Got first binary test passing
249
                old_lines = tree_lines(old_tree, require_text=True)
250
                new_lines = tree_lines(new_tree, require_text=True)
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
251
                action.write(self.to_file)
1185.82.96 by Aaron Bentley
Got first binary test passing
252
                internal_diff(old_path, old_lines, new_path, new_lines, 
253
                              self.to_file)
254
            except errors.BinaryFile:
255
                old_lines = tree_lines(old_tree, require_text=False)
256
                new_lines = tree_lines(new_tree, require_text=False)
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
257
                action.add_property('encoding', 'base64')
258
                action.write(self.to_file)
1185.82.96 by Aaron Bentley
Got first binary test passing
259
                binary_diff(old_path, old_lines, new_path, new_lines, 
260
                            self.to_file)
261
1185.82.104 by Aaron Bentley
Refactored action writing
262
        def finish_action(action, file_id, kind, meta_modified, text_modified,
263
                          old_path, new_path):
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
264
            entry = new_tree.inventory[file_id]
1731.1.33 by Aaron Bentley
Revert no-special-root changes
265
            if (entry.revision != default_revision_id and 
266
                entry.revision is not None):
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
267
                action.add_property('last-changed', entry.revision)
1185.82.104 by Aaron Bentley
Refactored action writing
268
            if meta_modified:
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
269
                action.add_bool_property('executable', entry.executable)
1185.82.104 by Aaron Bentley
Refactored action writing
270
            if text_modified and kind == "symlink":
1185.82.105 by Aaron Bentley
Removed two-liner nested functions
271
                action.add_property('target', entry.symlink_target)
1185.82.104 by Aaron Bentley
Refactored action writing
272
            if text_modified and kind == "file":
1185.84.3 by Aaron Bentley
Hide diffs for old revisions in bundles
273
                do_diff(file_id, old_path, new_path, action, force_binary)
1185.82.104 by Aaron Bentley
Refactored action writing
274
            else:
275
                action.write(self.to_file)
276
1731.1.33 by Aaron Bentley
Revert no-special-root changes
277
        delta = new_tree.changes_from(old_tree, want_unchanged=True, 
278
                                      include_root=True)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
279
        for path, file_id, kind in delta.removed:
1185.82.103 by Aaron Bentley
Serialize all actions through the action object
280
            action = Action('removed', [kind, path]).write(self.to_file)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
281
282
        for path, file_id, kind in delta.added:
1731.1.33 by Aaron Bentley
Revert no-special-root changes
283
            action = Action('added', [kind, bundle_path(path)], 
284
                            [('file-id', file_id)])
1185.82.119 by Aaron Bentley
Default execute bit to no for new files, directories, symlinks
285
            meta_modified = (kind=='file' and 
286
                             new_tree.is_executable(file_id))
287
            finish_action(action, file_id, kind, meta_modified, True,
1185.82.104 by Aaron Bentley
Refactored action writing
288
                          DEVNULL, path)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
289
290
        for (old_path, new_path, file_id, kind,
291
             text_modified, meta_modified) in delta.renamed:
1731.1.33 by Aaron Bentley
Revert no-special-root changes
292
            action = Action('renamed', [kind, bundle_path(old_path)], 
293
                            [(bundle_path(new_path),)])
1185.82.104 by Aaron Bentley
Refactored action writing
294
            finish_action(action, file_id, kind, meta_modified, text_modified,
295
                          old_path, new_path)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
296
297
        for (path, file_id, kind,
298
             text_modified, meta_modified) in delta.modified:
1731.1.33 by Aaron Bentley
Revert no-special-root changes
299
            action = Action('modified', [kind, bundle_path(path)])
1185.82.104 by Aaron Bentley
Refactored action writing
300
            finish_action(action, file_id, kind, meta_modified, text_modified,
301
                          path, path)
1185.82.117 by Aaron Bentley
Handle last-modified changes on their own
302
303
        for path, file_id, kind in delta.unchanged:
304
            ie = new_tree.inventory[file_id]
305
            new_rev = getattr(ie, 'revision', None)
306
            if new_rev is None:
307
                continue
308
            old_rev = getattr(old_tree.inventory[ie.file_id], 'revision', None)
309
            if new_rev != old_rev:
1731.1.33 by Aaron Bentley
Revert no-special-root changes
310
                action = Action('modified', 
311
                    [ie.kind, bundle_path(new_tree.id2path(ie.file_id))])
312
                if ie.revision is not None:
313
                    action.add_property('last-changed', ie.revision)
1185.82.117 by Aaron Bentley
Handle last-modified changes on their own
314
                action.write(self.to_file)
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
315
316
317
class BundleReader(object):
318
    """This class reads in a bundle from a file, and returns
319
    a Bundle object, which can then be applied against a tree.
320
    """
321
    def __init__(self, from_file):
322
        """Read in the bundle from the file.
323
324
        :param from_file: A file-like object (must have iterator support).
325
        """
326
        object.__init__(self)
327
        self.from_file = iter(from_file)
328
        self._next_line = None
329
        
1910.2.1 by Aaron Bentley
Ensure root entry always has a revision
330
        self.info = BundleInfo08()
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
331
        # We put the actual inventory ids in the footer, so that the patch
332
        # is easier to read for humans.
333
        # Unfortunately, that means we need to read everything before we
334
        # can create a proper bundle.
335
        self._read()
336
        self._validate()
337
338
    def _read(self):
339
        self._next().next()
340
        while self._next_line is not None:
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
341
            if not self._read_revision_header():
342
                break
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
343
            if self._next_line is None:
344
                break
345
            self._read_patches()
346
            self._read_footer()
347
348
    def _validate(self):
349
        """Make sure that the information read in makes sense
350
        and passes appropriate checksums.
351
        """
352
        # Fill in all the missing blanks for the revisions
353
        # and generate the real_revisions list.
354
        self.info.complete_info()
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_revision_header(self):
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
372
        found_something = False
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
373
        self.info.revisions.append(RevisionInfo(None))
374
        for line in self._next():
375
            # The bzr header is terminated with a blank line
376
            # which does not start with '#'
377
            if line is None or line == '\n':
378
                break
1793.3.16 by John Arbash Meinel
Add tests to ensure that we gracefully handle opening and trailing non-bundle text.
379
            if not line.startswith('#'):
380
                continue
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
381
            found_something = True
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
382
            self._handle_next(line)
1793.3.4 by John Arbash Meinel
[merge] bzr.dev 1804 and fix conflicts.
383
        if not found_something:
384
            # Nothing was there, so remove the added revision
385
            self.info.revisions.pop()
386
        return found_something
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
387
388
    def _read_next_entry(self, line, indent=1):
389
        """Read in a key-value pair
390
        """
391
        if not line.startswith('#'):
1793.3.15 by John Arbash Meinel
Raise the right errors
392
            raise errors.MalformedHeader('Bzr header did not start with #')
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
393
        line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
394
        if line[:indent] == ' '*indent:
395
            line = line[indent:]
396
        if not line:
397
            return None, None# Ignore blank lines
398
399
        loc = line.find(': ')
400
        if loc != -1:
401
            key = line[:loc]
402
            value = line[loc+2:]
403
            if not value:
404
                value = self._read_many(indent=indent+2)
405
        elif line[-1:] == ':':
406
            key = line[:-1]
407
            value = self._read_many(indent=indent+2)
408
        else:
1793.3.15 by John Arbash Meinel
Raise the right errors
409
            raise errors.MalformedHeader('While looking for key: value pairs,'
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
410
                    ' did not find the colon %r' % (line))
411
412
        key = key.replace(' ', '_')
413
        #mutter('found %s: %s' % (key, value))
414
        return key, value
415
416
    def _handle_next(self, line):
417
        if line is None:
418
            return
419
        key, value = self._read_next_entry(line, indent=1)
420
        mutter('_handle_next %r => %r' % (key, value))
421
        if key is None:
422
            return
423
424
        revision_info = self.info.revisions[-1]
1963.2.4 by Robey Pointer
remove usage of hasattr
425
        if key in revision_info.__dict__:
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
426
            if getattr(revision_info, key) is None:
427
                setattr(revision_info, key, value)
428
            else:
1793.3.15 by John Arbash Meinel
Raise the right errors
429
                raise errors.MalformedHeader('Duplicated Key: %s' % key)
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
430
        else:
431
            # What do we do with a key we don't recognize
1793.3.15 by John Arbash Meinel
Raise the right errors
432
            raise errors.MalformedHeader('Unknown Key: "%s"' % key)
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
433
    
434
    def _read_many(self, indent):
435
        """If a line ends with no entry, that means that it should be
436
        followed with multiple lines of values.
437
438
        This detects the end of the list, because it will be a line that
439
        does not start properly indented.
440
        """
441
        values = []
442
        start = '#' + (' '*indent)
443
444
        if self._next_line is None or self._next_line[:len(start)] != start:
445
            return values
446
447
        for line in self._next():
448
            values.append(line[len(start):-1].decode('utf-8'))
449
            if self._next_line is None or self._next_line[:len(start)] != start:
450
                break
451
        return values
452
453
    def _read_one_patch(self):
454
        """Read in one patch, return the complete patch, along with
455
        the next line.
456
457
        :return: action, lines, do_continue
458
        """
459
        #mutter('_read_one_patch: %r' % self._next_line)
460
        # Peek and see if there are no patches
461
        if self._next_line is None or self._next_line.startswith('#'):
462
            return None, [], False
463
464
        first = True
465
        lines = []
466
        for line in self._next():
467
            if first:
468
                if not line.startswith('==='):
1793.3.15 by John Arbash Meinel
Raise the right errors
469
                    raise errors.MalformedPatches('The first line of all patches'
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
470
                        ' should be a bzr meta line "==="'
471
                        ': %r' % line)
472
                action = line[4:-1].decode('utf-8')
473
            elif line.startswith('... '):
474
                action += line[len('... '):-1].decode('utf-8')
475
476
            if (self._next_line is not None and 
477
                self._next_line.startswith('===')):
478
                return action, lines, True
479
            elif self._next_line is None or self._next_line.startswith('#'):
480
                return action, lines, False
481
482
            if first:
483
                first = False
484
            elif not line.startswith('... '):
485
                lines.append(line)
486
487
        return action, lines, False
488
            
489
    def _read_patches(self):
490
        do_continue = True
491
        revision_actions = []
492
        while do_continue:
493
            action, lines, do_continue = self._read_one_patch()
494
            if action is not None:
495
                revision_actions.append((action, lines))
496
        assert self.info.revisions[-1].tree_actions is None
497
        self.info.revisions[-1].tree_actions = revision_actions
498
499
    def _read_footer(self):
500
        """Read the rest of the meta information.
501
502
        :param first_line:  The previous step iterates past what it
503
                            can handle. That extra line is given here.
504
        """
505
        for line in self._next():
506
            self._handle_next(line)
1793.3.14 by John Arbash Meinel
Actually fix the bug with missing trailing newline bug #49182
507
            if self._next_line is None:
508
                break
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
509
            if not self._next_line.startswith('#'):
1793.3.14 by John Arbash Meinel
Actually fix the bug with missing trailing newline bug #49182
510
                # Consume the trailing \n and stop processing
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
511
                self._next().next()
512
                break
1910.2.1 by Aaron Bentley
Ensure root entry always has a revision
513
1910.2.5 by Aaron Bentley
Fix whitespace
514
1910.2.1 by Aaron Bentley
Ensure root entry always has a revision
515
class BundleInfo08(BundleInfo):
516
    def _update_tree(self, bundle_tree, revision_id):
517
        bundle_tree.note_last_changed('', revision_id)
518
        BundleInfo._update_tree(self, bundle_tree, revision_id)