~bzr-pqm/bzr/bzr.dev

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