~bzr-pqm/bzr/bzr.dev

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