/brz/remove-bazaar

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