~bzr-pqm/bzr/bzr.dev

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