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