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