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