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