~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/serializer/v07.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-09 04:51:05 UTC
  • Revision ID: mbp@sourcefrog.net-20050309045105-d02cd410a115da2c
import all docs from arch

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# (C) 2005 Canonical Development Ltd
2
 
 
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.
7
 
 
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.
12
 
 
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
 
 
17
 
"""Serializer factory for reading and writing bundles.
18
 
"""
19
 
 
20
 
import os
21
 
 
22
 
from bzrlib.bundle.serializer import (BundleSerializer, 
23
 
                                      BUNDLE_HEADER, 
24
 
                                      format_highres_date,
25
 
                                      unpack_highres_date,
26
 
                                     )
27
 
from bzrlib.bundle.serializer import binary_diff
28
 
from bzrlib.delta import compare_trees
29
 
from bzrlib.diff import internal_diff
30
 
import bzrlib.errors as errors
31
 
from bzrlib.osutils import pathjoin
32
 
from bzrlib.progress import DummyProgress
33
 
from bzrlib.revision import NULL_REVISION
34
 
from bzrlib.rio import RioWriter, read_stanzas
35
 
import bzrlib.ui
36
 
from bzrlib.testament import StrictTestament
37
 
from bzrlib.textfile import text_file
38
 
 
39
 
bool_text = {True: 'yes', False: 'no'}
40
 
 
41
 
 
42
 
class Action(object):
43
 
    """Represent an action"""
44
 
 
45
 
    def __init__(self, name, parameters=None, properties=None):
46
 
        self.name = name
47
 
        if parameters is None:
48
 
            self.parameters = []
49
 
        else:
50
 
            self.parameters = parameters
51
 
        if properties is None:
52
 
            self.properties = []
53
 
        else:
54
 
            self.properties = properties
55
 
 
56
 
    def add_property(self, name, value):
57
 
        """Add a property to the action"""
58
 
        self.properties.append((name, value))
59
 
 
60
 
    def add_bool_property(self, name, value):
61
 
        """Add a boolean property to the action"""
62
 
        self.add_property(name, bool_text[value])
63
 
 
64
 
    def write(self, to_file):
65
 
        """Write action as to a file"""
66
 
        p_texts = [' '.join([self.name]+self.parameters)]
67
 
        for prop in self.properties:
68
 
            if len(prop) == 1:
69
 
                p_texts.append(prop[0])
70
 
            else:
71
 
                try:
72
 
                    p_texts.append('%s:%s' % prop)
73
 
                except:
74
 
                    raise repr(prop)
75
 
        text = ['=== ']
76
 
        text.append(' // '.join(p_texts))
77
 
        text_line = ''.join(text).encode('utf-8')
78
 
        available = 79
79
 
        while len(text_line) > available:
80
 
            to_file.write(text_line[:available])
81
 
            text_line = text_line[available:]
82
 
            to_file.write('\n... ')
83
 
            available = 79 - len('... ')
84
 
        to_file.write(text_line+'\n')
85
 
 
86
 
 
87
 
class BundleSerializerV07(BundleSerializer):
88
 
    def read(self, f):
89
 
        """Read the rest of the bundles from the supplied file.
90
 
 
91
 
        :param f: The file to read from
92
 
        :return: A list of bundles
93
 
        """
94
 
        assert self.version == '0.7'
95
 
        # The first line of the header should have been read
96
 
        raise NotImplementedError
97
 
 
98
 
    def write(self, source, revision_ids, forced_bases, f):
99
 
        """Write the bundless to the supplied files.
100
 
 
101
 
        :param source: A source for revision information
102
 
        :param revision_ids: The list of revision ids to serialize
103
 
        :param forced_bases: A dict of revision -> base that overrides default
104
 
        :param f: The file to output to
105
 
        """
106
 
        self.source = source
107
 
        self.revision_ids = revision_ids
108
 
        self.forced_bases = forced_bases
109
 
        self.to_file = f
110
 
        source.lock_read()
111
 
        try:
112
 
            self._write_main_header()
113
 
            pb = DummyProgress()
114
 
            try:
115
 
                self._write_revisions(pb)
116
 
            finally:
117
 
                pass
118
 
                #pb.finished()
119
 
        finally:
120
 
            source.unlock()
121
 
 
122
 
    def _write_main_header(self):
123
 
        """Write the header for the changes"""
124
 
        f = self.to_file
125
 
        f.write(BUNDLE_HEADER)
126
 
        f.write('0.7\n')
127
 
        f.write('#\n')
128
 
 
129
 
    def _write(self, key, value, indent=1):
130
 
        """Write out meta information, with proper indenting, etc"""
131
 
        assert indent > 0, 'indentation must be greater than 0'
132
 
        f = self.to_file
133
 
        f.write('#' + (' ' * indent))
134
 
        f.write(key.encode('utf-8'))
135
 
        if not value:
136
 
            f.write(':\n')
137
 
        elif isinstance(value, basestring):
138
 
            f.write(': ')
139
 
            f.write(value.encode('utf-8'))
140
 
            f.write('\n')
141
 
        else:
142
 
            f.write(':\n')
143
 
            for entry in value:
144
 
                f.write('#' + (' ' * (indent+2)))
145
 
                f.write(entry.encode('utf-8'))
146
 
                f.write('\n')
147
 
 
148
 
    def _write_revisions(self, pb):
149
 
        """Write the information for all of the revisions."""
150
 
 
151
 
        # Optimize for the case of revisions in order
152
 
        last_rev_id = None
153
 
        last_rev_tree = None
154
 
 
155
 
        i_max = len(self.revision_ids) 
156
 
        for i, rev_id in enumerate(self.revision_ids):
157
 
            pb.update("Generating revsion data", i, i_max)
158
 
            rev = self.source.get_revision(rev_id)
159
 
            if rev_id == last_rev_id:
160
 
                rev_tree = last_rev_tree
161
 
            else:
162
 
                base_tree = self.source.revision_tree(rev_id)
163
 
            rev_tree = self.source.revision_tree(rev_id)
164
 
            if rev_id in self.forced_bases:
165
 
                explicit_base = True
166
 
                base_id = self.forced_bases[rev_id]
167
 
                if base_id is None:
168
 
                    base_id = NULL_REVISION
169
 
            else:
170
 
                explicit_base = False
171
 
                if rev.parent_ids:
172
 
                    base_id = rev.parent_ids[-1]
173
 
                else:
174
 
                    base_id = NULL_REVISION
175
 
 
176
 
            if base_id == last_rev_id:
177
 
                base_tree = last_rev_tree
178
 
            else:
179
 
                base_tree = self.source.revision_tree(base_id)
180
 
            force_binary = (i != 0)
181
 
            self._write_revision(rev, rev_tree, base_id, base_tree, 
182
 
                                 explicit_base, force_binary)
183
 
 
184
 
            last_rev_id = base_id
185
 
            last_rev_tree = base_tree
186
 
 
187
 
    def _write_revision(self, rev, rev_tree, base_rev, base_tree, 
188
 
                        explicit_base, force_binary):
189
 
        """Write out the information for a revision."""
190
 
        def w(key, value):
191
 
            self._write(key, value, indent=1)
192
 
 
193
 
        w('message', rev.message.split('\n'))
194
 
        w('committer', rev.committer)
195
 
        w('date', format_highres_date(rev.timestamp, rev.timezone))
196
 
        self.to_file.write('\n')
197
 
 
198
 
        self._write_delta(rev_tree, base_tree, rev.revision_id, force_binary)
199
 
 
200
 
        w('revision id', rev.revision_id)
201
 
        w('sha1', StrictTestament.from_revision(self.source, 
202
 
                                                rev.revision_id).as_sha1())
203
 
        w('inventory sha1', rev.inventory_sha1)
204
 
        if rev.parent_ids:
205
 
            w('parent ids', rev.parent_ids)
206
 
        if explicit_base:
207
 
            w('base id', base_rev)
208
 
        if rev.properties:
209
 
            self._write('properties', None, indent=1)
210
 
            for name, value in rev.properties.items():
211
 
                self._write(name, value, indent=3)
212
 
        
213
 
        # Add an extra blank space at the end
214
 
        self.to_file.write('\n')
215
 
 
216
 
    def _write_action(self, name, parameters, properties=None):
217
 
        if properties is None:
218
 
            properties = []
219
 
        p_texts = ['%s:%s' % v for v in properties]
220
 
        self.to_file.write('=== ')
221
 
        self.to_file.write(' '.join([name]+parameters).encode('utf-8'))
222
 
        self.to_file.write(' // '.join(p_texts).encode('utf-8'))
223
 
        self.to_file.write('\n')
224
 
 
225
 
    def _write_delta(self, new_tree, old_tree, default_revision_id, 
226
 
                     force_binary):
227
 
        """Write out the changes between the trees."""
228
 
        DEVNULL = '/dev/null'
229
 
        old_label = ''
230
 
        new_label = ''
231
 
 
232
 
        def do_diff(file_id, old_path, new_path, action, force_binary):
233
 
            def tree_lines(tree, require_text=False):
234
 
                if file_id in tree:
235
 
                    tree_file = tree.get_file(file_id)
236
 
                    if require_text is True:
237
 
                        tree_file = text_file(tree_file)
238
 
                    return tree_file.readlines()
239
 
                else:
240
 
                    return []
241
 
 
242
 
            try:
243
 
                if force_binary:
244
 
                    raise errors.BinaryFile()
245
 
                old_lines = tree_lines(old_tree, require_text=True)
246
 
                new_lines = tree_lines(new_tree, require_text=True)
247
 
                action.write(self.to_file)
248
 
                internal_diff(old_path, old_lines, new_path, new_lines, 
249
 
                              self.to_file)
250
 
            except errors.BinaryFile:
251
 
                old_lines = tree_lines(old_tree, require_text=False)
252
 
                new_lines = tree_lines(new_tree, require_text=False)
253
 
                action.add_property('encoding', 'base64')
254
 
                action.write(self.to_file)
255
 
                binary_diff(old_path, old_lines, new_path, new_lines, 
256
 
                            self.to_file)
257
 
 
258
 
        def finish_action(action, file_id, kind, meta_modified, text_modified,
259
 
                          old_path, new_path):
260
 
            entry = new_tree.inventory[file_id]
261
 
            if entry.revision != default_revision_id:
262
 
                action.add_property('last-changed', entry.revision)
263
 
            if meta_modified:
264
 
                action.add_bool_property('executable', entry.executable)
265
 
            if text_modified and kind == "symlink":
266
 
                action.add_property('target', entry.symlink_target)
267
 
            if text_modified and kind == "file":
268
 
                do_diff(file_id, old_path, new_path, action, force_binary)
269
 
            else:
270
 
                action.write(self.to_file)
271
 
 
272
 
        delta = compare_trees(old_tree, new_tree, want_unchanged=True)
273
 
        for path, file_id, kind in delta.removed:
274
 
            action = Action('removed', [kind, path]).write(self.to_file)
275
 
 
276
 
        for path, file_id, kind in delta.added:
277
 
            action = Action('added', [kind, path], [('file-id', file_id)])
278
 
            meta_modified = (kind=='file' and 
279
 
                             new_tree.is_executable(file_id))
280
 
            finish_action(action, file_id, kind, meta_modified, True,
281
 
                          DEVNULL, path)
282
 
 
283
 
        for (old_path, new_path, file_id, kind,
284
 
             text_modified, meta_modified) in delta.renamed:
285
 
            action = Action('renamed', [kind, old_path], [(new_path,)])
286
 
            finish_action(action, file_id, kind, meta_modified, text_modified,
287
 
                          old_path, new_path)
288
 
 
289
 
        for (path, file_id, kind,
290
 
             text_modified, meta_modified) in delta.modified:
291
 
            action = Action('modified', [kind, path])
292
 
            finish_action(action, file_id, kind, meta_modified, text_modified,
293
 
                          path, path)
294
 
 
295
 
        for path, file_id, kind in delta.unchanged:
296
 
            ie = new_tree.inventory[file_id]
297
 
            new_rev = getattr(ie, 'revision', None)
298
 
            if new_rev is None:
299
 
                continue
300
 
            old_rev = getattr(old_tree.inventory[ie.file_id], 'revision', None)
301
 
            if new_rev != old_rev:
302
 
                action = Action('modified', [ie.kind, 
303
 
                                             new_tree.id2path(ie.file_id)])
304
 
                action.add_property('last-changed', ie.revision)
305
 
                action.write(self.to_file)