~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Vincent Ladeuil
  • Date: 2010-02-03 07:18:36 UTC
  • mto: (5008.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5009.
  • Revision ID: v.ladeuil+lp@free.fr-20100203071836-u9b86q68fr9ri5s6
Fix NEWS.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# (C) 2005 Canonical Development Ltd
 
1
# Copyright (C) 2005, 2006, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Serializer factory for reading and writing bundles.
18
18
"""
19
19
 
20
20
import os
21
21
 
22
 
from bzrlib import errors
 
22
from bzrlib import (
 
23
    errors,
 
24
    ui,
 
25
    )
23
26
from bzrlib.bundle.serializer import (BundleSerializer,
24
 
                                      BUNDLE_HEADER,
25
 
                                      format_highres_date,
26
 
                                      unpack_highres_date,
 
27
                                      _get_bundle_header,
27
28
                                     )
28
29
from bzrlib.bundle.serializer import binary_diff
29
30
from bzrlib.bundle.bundle_data import (RevisionInfo, BundleInfo, BundleTree)
30
31
from bzrlib.diff import internal_diff
31
32
from bzrlib.osutils import pathjoin
32
 
from bzrlib.progress import DummyProgress
33
33
from bzrlib.revision import NULL_REVISION
34
 
from bzrlib.rio import RioWriter, read_stanzas
35
 
import bzrlib.ui
36
34
from bzrlib.testament import StrictTestament
 
35
from bzrlib.timestamp import (
 
36
    format_highres_date,
 
37
    unpack_highres_date,
 
38
)
37
39
from bzrlib.textfile import text_file
38
40
from bzrlib.trace import mutter
39
41
 
54
56
        else:
55
57
            self.properties = properties
56
58
 
 
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
 
57
63
    def add_property(self, name, value):
58
64
        """Add a property to the action"""
59
65
        self.properties.append((name, value))
94
100
        """
95
101
        return BundleReader(f).info
96
102
 
 
103
    def check_compatible(self):
 
104
        if self.source.supports_rich_root():
 
105
            raise errors.IncompatibleBundleFormat('0.8', repr(self.source))
 
106
 
97
107
    def write(self, source, revision_ids, forced_bases, f):
98
108
        """Write the bundless to the supplied files.
99
109
 
106
116
        self.revision_ids = revision_ids
107
117
        self.forced_bases = forced_bases
108
118
        self.to_file = f
 
119
        self.check_compatible()
109
120
        source.lock_read()
110
121
        try:
111
122
            self._write_main_header()
112
 
            pb = DummyProgress()
 
123
            pb = ui.ui_factory.nested_progress_bar()
113
124
            try:
114
125
                self._write_revisions(pb)
115
126
            finally:
116
 
                pass
117
 
                #pb.finished()
 
127
                pb.finished()
118
128
        finally:
119
129
            source.unlock()
120
130
 
 
131
    def write_bundle(self, repository, target, base, fileobj):
 
132
        return self._write_bundle(repository, target, base, fileobj)
 
133
 
121
134
    def _write_main_header(self):
122
135
        """Write the header for the changes"""
123
136
        f = self.to_file
124
 
        f.write(BUNDLE_HEADER)
125
 
        f.write('0.8\n')
 
137
        f.write(_get_bundle_header('0.8'))
126
138
        f.write('#\n')
127
139
 
128
 
    def _write(self, key, value, indent=1):
129
 
        """Write out meta information, with proper indenting, etc"""
130
 
        assert indent > 0, 'indentation must be greater than 0'
 
140
    def _write(self, key, value, indent=1, trailing_space_when_empty=False):
 
141
        """Write out meta information, with proper indenting, etc.
 
142
 
 
143
        :param trailing_space_when_empty: To work around a bug in earlier
 
144
            bundle readers, when writing an empty property, we use "prop: \n"
 
145
            rather than writing "prop:\n".
 
146
            If this parameter is True, and value is the empty string, we will
 
147
            write an extra space.
 
148
        """
 
149
        if indent < 1:
 
150
            raise ValueError('indentation must be greater than 0')
131
151
        f = self.to_file
132
152
        f.write('#' + (' ' * indent))
133
153
        f.write(key.encode('utf-8'))
134
154
        if not value:
135
 
            f.write(':\n')
136
 
        elif isinstance(value, basestring):
 
155
            if trailing_space_when_empty and value == '':
 
156
                f.write(': \n')
 
157
            else:
 
158
                f.write(':\n')
 
159
        elif isinstance(value, str):
 
160
            f.write(': ')
 
161
            f.write(value)
 
162
            f.write('\n')
 
163
        elif isinstance(value, unicode):
137
164
            f.write(': ')
138
165
            f.write(value.encode('utf-8'))
139
166
            f.write('\n')
141
168
            f.write(':\n')
142
169
            for entry in value:
143
170
                f.write('#' + (' ' * (indent+2)))
144
 
                f.write(entry.encode('utf-8'))
 
171
                if isinstance(entry, str):
 
172
                    f.write(entry)
 
173
                else:
 
174
                    f.write(entry.encode('utf-8'))
145
175
                f.write('\n')
146
176
 
147
177
    def _write_revisions(self, pb):
151
181
        last_rev_id = None
152
182
        last_rev_tree = None
153
183
 
154
 
        i_max = len(self.revision_ids) 
 
184
        i_max = len(self.revision_ids)
155
185
        for i, rev_id in enumerate(self.revision_ids):
156
 
            pb.update("Generating revsion data", i, i_max)
 
186
            pb.update("Generating revision data", i, i_max)
157
187
            rev = self.source.get_revision(rev_id)
158
188
            if rev_id == last_rev_id:
159
189
                rev_tree = last_rev_tree
160
190
            else:
161
 
                base_tree = self.source.revision_tree(rev_id)
162
 
            rev_tree = self.source.revision_tree(rev_id)
 
191
                rev_tree = self.source.revision_tree(rev_id)
163
192
            if rev_id in self.forced_bases:
164
193
                explicit_base = True
165
194
                base_id = self.forced_bases[rev_id]
177
206
            else:
178
207
                base_tree = self.source.revision_tree(base_id)
179
208
            force_binary = (i != 0)
180
 
            self._write_revision(rev, rev_tree, base_id, base_tree, 
 
209
            self._write_revision(rev, rev_tree, base_id, base_tree,
181
210
                                 explicit_base, force_binary)
182
211
 
183
212
            last_rev_id = base_id
184
213
            last_rev_tree = base_tree
185
214
 
186
 
    def _write_revision(self, rev, rev_tree, base_rev, base_tree, 
 
215
    def _testament_sha1(self, revision_id):
 
216
        return StrictTestament.from_revision(self.source,
 
217
                                             revision_id).as_sha1()
 
218
 
 
219
    def _write_revision(self, rev, rev_tree, base_rev, base_tree,
187
220
                        explicit_base, force_binary):
188
221
        """Write out the information for a revision."""
189
222
        def w(key, value):
197
230
        self._write_delta(rev_tree, base_tree, rev.revision_id, force_binary)
198
231
 
199
232
        w('revision id', rev.revision_id)
200
 
        w('sha1', StrictTestament.from_revision(self.source, 
201
 
                                                rev.revision_id).as_sha1())
 
233
        w('sha1', self._testament_sha1(rev.revision_id))
202
234
        w('inventory sha1', rev.inventory_sha1)
203
235
        if rev.parent_ids:
204
236
            w('parent ids', rev.parent_ids)
206
238
            w('base id', base_rev)
207
239
        if rev.properties:
208
240
            self._write('properties', None, indent=1)
209
 
            for name, value in rev.properties.items():
210
 
                self._write(name, value, indent=3)
211
 
        
 
241
            for name, value in sorted(rev.properties.items()):
 
242
                self._write(name, value, indent=3,
 
243
                            trailing_space_when_empty=True)
 
244
 
212
245
        # Add an extra blank space at the end
213
246
        self.to_file.write('\n')
214
247
 
221
254
        self.to_file.write(' // '.join(p_texts).encode('utf-8'))
222
255
        self.to_file.write('\n')
223
256
 
224
 
    def _write_delta(self, new_tree, old_tree, default_revision_id, 
 
257
    def _write_delta(self, new_tree, old_tree, default_revision_id,
225
258
                     force_binary):
226
259
        """Write out the changes between the trees."""
227
260
        DEVNULL = '/dev/null'
244
277
                old_lines = tree_lines(old_tree, require_text=True)
245
278
                new_lines = tree_lines(new_tree, require_text=True)
246
279
                action.write(self.to_file)
247
 
                internal_diff(old_path, old_lines, new_path, new_lines, 
 
280
                internal_diff(old_path, old_lines, new_path, new_lines,
248
281
                              self.to_file)
249
282
            except errors.BinaryFile:
250
283
                old_lines = tree_lines(old_tree, require_text=False)
251
284
                new_lines = tree_lines(new_tree, require_text=False)
252
285
                action.add_property('encoding', 'base64')
253
286
                action.write(self.to_file)
254
 
                binary_diff(old_path, old_lines, new_path, new_lines, 
 
287
                binary_diff(old_path, old_lines, new_path, new_lines,
255
288
                            self.to_file)
256
289
 
257
290
        def finish_action(action, file_id, kind, meta_modified, text_modified,
258
291
                          old_path, new_path):
259
292
            entry = new_tree.inventory[file_id]
260
293
            if entry.revision != default_revision_id:
261
 
                action.add_property('last-changed', entry.revision)
 
294
                action.add_utf8_property('last-changed', entry.revision)
262
295
            if meta_modified:
263
296
                action.add_bool_property('executable', entry.executable)
264
297
            if text_modified and kind == "symlink":
268
301
            else:
269
302
                action.write(self.to_file)
270
303
 
271
 
        delta = new_tree.changes_from(old_tree, want_unchanged=True)
 
304
        delta = new_tree.changes_from(old_tree, want_unchanged=True,
 
305
                                      include_root=True)
272
306
        for path, file_id, kind in delta.removed:
273
307
            action = Action('removed', [kind, path]).write(self.to_file)
274
308
 
275
309
        for path, file_id, kind in delta.added:
276
310
            action = Action('added', [kind, path], [('file-id', file_id)])
277
 
            meta_modified = (kind=='file' and 
 
311
            meta_modified = (kind=='file' and
278
312
                             new_tree.is_executable(file_id))
279
313
            finish_action(action, file_id, kind, meta_modified, True,
280
314
                          DEVNULL, path)
298
332
                continue
299
333
            old_rev = getattr(old_tree.inventory[ie.file_id], 'revision', None)
300
334
            if new_rev != old_rev:
301
 
                action = Action('modified', [ie.kind, 
 
335
                action = Action('modified', [ie.kind,
302
336
                                             new_tree.id2path(ie.file_id)])
303
 
                action.add_property('last-changed', ie.revision)
 
337
                action.add_utf8_property('last-changed', ie.revision)
304
338
                action.write(self.to_file)
305
339
 
306
340
 
316
350
        object.__init__(self)
317
351
        self.from_file = iter(from_file)
318
352
        self._next_line = None
319
 
        
320
 
        self.info = BundleInfo()
 
353
 
 
354
        self.info = self._get_info()
321
355
        # We put the actual inventory ids in the footer, so that the patch
322
356
        # is easier to read for humans.
323
357
        # Unfortunately, that means we need to read everything before we
325
359
        self._read()
326
360
        self._validate()
327
361
 
 
362
    def _get_info(self):
 
363
        return BundleInfo08()
 
364
 
328
365
    def _read(self):
329
366
        self._next().next()
330
367
        while self._next_line is not None:
412
449
            return
413
450
 
414
451
        revision_info = self.info.revisions[-1]
415
 
        if hasattr(revision_info, key):
 
452
        if key in revision_info.__dict__:
416
453
            if getattr(revision_info, key) is None:
 
454
                if key in ('file_id', 'revision_id', 'base_id'):
 
455
                    value = value.encode('utf8')
 
456
                elif key in ('parent_ids'):
 
457
                    value = [v.encode('utf8') for v in value]
417
458
                setattr(revision_info, key, value)
418
459
            else:
419
460
                raise errors.MalformedHeader('Duplicated Key: %s' % key)
420
461
        else:
421
462
            # What do we do with a key we don't recognize
422
463
            raise errors.MalformedHeader('Unknown Key: "%s"' % key)
423
 
    
 
464
 
424
465
    def _read_many(self, indent):
425
466
        """If a line ends with no entry, that means that it should be
426
467
        followed with multiple lines of values.
463
504
            elif line.startswith('... '):
464
505
                action += line[len('... '):-1].decode('utf-8')
465
506
 
466
 
            if (self._next_line is not None and 
 
507
            if (self._next_line is not None and
467
508
                self._next_line.startswith('===')):
468
509
                return action, lines, True
469
510
            elif self._next_line is None or self._next_line.startswith('#'):
475
516
                lines.append(line)
476
517
 
477
518
        return action, lines, False
478
 
            
 
519
 
479
520
    def _read_patches(self):
480
521
        do_continue = True
481
522
        revision_actions = []
483
524
            action, lines, do_continue = self._read_one_patch()
484
525
            if action is not None:
485
526
                revision_actions.append((action, lines))
486
 
        assert self.info.revisions[-1].tree_actions is None
 
527
        if self.info.revisions[-1].tree_actions is not None:
 
528
            raise AssertionError()
487
529
        self.info.revisions[-1].tree_actions = revision_actions
488
530
 
489
531
    def _read_footer(self):
500
542
                # Consume the trailing \n and stop processing
501
543
                self._next().next()
502
544
                break
 
545
 
 
546
class BundleInfo08(BundleInfo):
 
547
 
 
548
    def _update_tree(self, bundle_tree, revision_id):
 
549
        bundle_tree.note_last_changed('', revision_id)
 
550
        BundleInfo._update_tree(self, bundle_tree, revision_id)
 
551
 
 
552
    def _testament_sha1_from_revision(self, repository, revision_id):
 
553
        testament = StrictTestament.from_revision(repository, revision_id)
 
554
        return testament.as_sha1()
 
555
 
 
556
    def _testament_sha1(self, revision, inventory):
 
557
        return StrictTestament(revision, inventory).as_sha1()