~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: John Arbash Meinel
  • Date: 2009-07-06 18:59:24 UTC
  • mto: This revision was merged to the branch mainline in revision 4522.
  • Revision ID: john@arbash-meinel.com-20090706185924-qlhn1j607117lgdj
Start implementing an Annotator.add_special_text functionality.

The Python implementation supports it. Basically, it is meant to allow things
like WT and PreviewTree to insert the 'current' content into the graph, so that
we can get local modifications into the annotations.
There is also some work here to get support for texts that are already cached
in the annotator. So that we avoid extracting them, and can shortcut the
history.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007 Canonical 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
from cStringIO import StringIO
 
18
import bz2
 
19
import re
 
20
 
 
21
from bzrlib import (
 
22
    diff,
 
23
    errors,
 
24
    iterablefile,
 
25
    multiparent,
 
26
    osutils,
 
27
    pack,
 
28
    revision as _mod_revision,
 
29
    trace,
 
30
    serializer,
 
31
    )
 
32
from bzrlib.bundle import bundle_data, serializer as bundle_serializer
 
33
from bzrlib import bencode
 
34
 
 
35
 
 
36
class BundleWriter(object):
 
37
    """Writer for bundle-format files.
 
38
 
 
39
    This serves roughly the same purpose as ContainerReader, but acts as a
 
40
    layer on top of it.
 
41
 
 
42
    Provides ways of writing the specific record types supported this bundle
 
43
    format.
 
44
    """
 
45
 
 
46
    def __init__(self, fileobj):
 
47
        self._container = pack.ContainerWriter(self._write_encoded)
 
48
        self._fileobj = fileobj
 
49
        self._compressor = bz2.BZ2Compressor()
 
50
 
 
51
    def _write_encoded(self, bytes):
 
52
        """Write bzip2-encoded bytes to the file"""
 
53
        self._fileobj.write(self._compressor.compress(bytes))
 
54
 
 
55
    def begin(self):
 
56
        """Start writing the bundle"""
 
57
        self._fileobj.write(bundle_serializer._get_bundle_header(
 
58
            bundle_serializer.v4_string))
 
59
        self._fileobj.write('#\n')
 
60
        self._container.begin()
 
61
 
 
62
    def end(self):
 
63
        """Finish writing the bundle"""
 
64
        self._container.end()
 
65
        self._fileobj.write(self._compressor.flush())
 
66
 
 
67
    def add_multiparent_record(self, mp_bytes, sha1, parents, repo_kind,
 
68
                               revision_id, file_id):
 
69
        """Add a record for a multi-parent diff
 
70
 
 
71
        :mp_bytes: A multi-parent diff, as a bytestring
 
72
        :sha1: The sha1 hash of the fulltext
 
73
        :parents: a list of revision-ids of the parents
 
74
        :repo_kind: The kind of object in the repository.  May be 'file' or
 
75
            'inventory'
 
76
        :revision_id: The revision id of the mpdiff being added.
 
77
        :file_id: The file-id of the file, or None for inventories.
 
78
        """
 
79
        metadata = {'parents': parents,
 
80
                    'storage_kind': 'mpdiff',
 
81
                    'sha1': sha1}
 
82
        self._add_record(mp_bytes, metadata, repo_kind, revision_id, file_id)
 
83
 
 
84
    def add_fulltext_record(self, bytes, parents, repo_kind, revision_id):
 
85
        """Add a record for a fulltext
 
86
 
 
87
        :bytes: The fulltext, as a bytestring
 
88
        :parents: a list of revision-ids of the parents
 
89
        :repo_kind: The kind of object in the repository.  May be 'revision' or
 
90
            'signature'
 
91
        :revision_id: The revision id of the fulltext being added.
 
92
        """
 
93
        metadata = {'parents': parents,
 
94
                    'storage_kind': 'mpdiff'}
 
95
        self._add_record(bytes, {'parents': parents,
 
96
            'storage_kind': 'fulltext'}, repo_kind, revision_id, None)
 
97
 
 
98
    def add_info_record(self, **kwargs):
 
99
        """Add an info record to the bundle
 
100
 
 
101
        Any parameters may be supplied, except 'self' and 'storage_kind'.
 
102
        Values must be lists, strings, integers, dicts, or a combination.
 
103
        """
 
104
        kwargs['storage_kind'] = 'header'
 
105
        self._add_record(None, kwargs, 'info', None, None)
 
106
 
 
107
    @staticmethod
 
108
    def encode_name(content_kind, revision_id, file_id=None):
 
109
        """Encode semantic ids as a container name"""
 
110
        if content_kind not in ('revision', 'file', 'inventory', 'signature',
 
111
                'info'):
 
112
            raise ValueError(content_kind)
 
113
        if content_kind == 'file':
 
114
            if file_id is None:
 
115
                raise AssertionError()
 
116
        else:
 
117
            if file_id is not None:
 
118
                raise AssertionError()
 
119
        if content_kind == 'info':
 
120
            if revision_id is not None:
 
121
                raise AssertionError()
 
122
        elif revision_id is None:
 
123
            raise AssertionError()
 
124
        names = [n.replace('/', '//') for n in
 
125
                 (content_kind, revision_id, file_id) if n is not None]
 
126
        return '/'.join(names)
 
127
 
 
128
    def _add_record(self, bytes, metadata, repo_kind, revision_id, file_id):
 
129
        """Add a bundle record to the container.
 
130
 
 
131
        Most bundle records are recorded as header/body pairs, with the
 
132
        body being nameless.  Records with storage_kind 'header' have no
 
133
        body.
 
134
        """
 
135
        name = self.encode_name(repo_kind, revision_id, file_id)
 
136
        encoded_metadata = bencode.bencode(metadata)
 
137
        self._container.add_bytes_record(encoded_metadata, [(name, )])
 
138
        if metadata['storage_kind'] != 'header':
 
139
            self._container.add_bytes_record(bytes, [])
 
140
 
 
141
 
 
142
class BundleReader(object):
 
143
    """Reader for bundle-format files.
 
144
 
 
145
    This serves roughly the same purpose as ContainerReader, but acts as a
 
146
    layer on top of it, providing metadata, a semantic name, and a record
 
147
    body
 
148
    """
 
149
 
 
150
    def __init__(self, fileobj, stream_input=True):
 
151
        """Constructor
 
152
 
 
153
        :param fileobj: a file containing a bzip-encoded container
 
154
        :param stream_input: If True, the BundleReader stream input rather than
 
155
            reading it all into memory at once.  Reading it into memory all at
 
156
            once is (currently) faster.
 
157
        """
 
158
        line = fileobj.readline()
 
159
        if line != '\n':
 
160
            fileobj.readline()
 
161
        self.patch_lines = []
 
162
        if stream_input:
 
163
            source_file = iterablefile.IterableFile(self.iter_decode(fileobj))
 
164
        else:
 
165
            source_file = StringIO(bz2.decompress(fileobj.read()))
 
166
        self._container_file = source_file
 
167
 
 
168
    @staticmethod
 
169
    def iter_decode(fileobj):
 
170
        """Iterate through decoded fragments of the file"""
 
171
        decompressor = bz2.BZ2Decompressor()
 
172
        for line in fileobj:
 
173
            try:
 
174
                yield decompressor.decompress(line)
 
175
            except EOFError:
 
176
                return
 
177
 
 
178
    @staticmethod
 
179
    def decode_name(name):
 
180
        """Decode a name from its container form into a semantic form
 
181
 
 
182
        :retval: content_kind, revision_id, file_id
 
183
        """
 
184
        segments = re.split('(//?)', name)
 
185
        names = ['']
 
186
        for segment in segments:
 
187
            if segment == '//':
 
188
                names[-1] += '/'
 
189
            elif segment == '/':
 
190
                names.append('')
 
191
            else:
 
192
                names[-1] += segment
 
193
        content_kind = names[0]
 
194
        revision_id = None
 
195
        file_id = None
 
196
        if len(names) > 1:
 
197
            revision_id = names[1]
 
198
        if len(names) > 2:
 
199
            file_id = names[2]
 
200
        return content_kind, revision_id, file_id
 
201
 
 
202
    def iter_records(self):
 
203
        """Iterate through bundle records
 
204
 
 
205
        :return: a generator of (bytes, metadata, content_kind, revision_id,
 
206
            file_id)
 
207
        """
 
208
        iterator = pack.iter_records_from_file(self._container_file)
 
209
        for names, bytes in iterator:
 
210
            if len(names) != 1:
 
211
                raise errors.BadBundle('Record has %d names instead of 1'
 
212
                                       % len(names))
 
213
            metadata = bencode.bdecode(bytes)
 
214
            if metadata['storage_kind'] == 'header':
 
215
                bytes = None
 
216
            else:
 
217
                _unused, bytes = iterator.next()
 
218
            yield (bytes, metadata) + self.decode_name(names[0][0])
 
219
 
 
220
 
 
221
class BundleSerializerV4(bundle_serializer.BundleSerializer):
 
222
    """Implement the high-level bundle interface"""
 
223
 
 
224
    def write(self, repository, revision_ids, forced_bases, fileobj):
 
225
        """Write a bundle to a file-like object
 
226
 
 
227
        For backwards-compatibility only
 
228
        """
 
229
        write_op = BundleWriteOperation.from_old_args(repository, revision_ids,
 
230
                                                      forced_bases, fileobj)
 
231
        return write_op.do_write()
 
232
 
 
233
    def write_bundle(self, repository, target, base, fileobj):
 
234
        """Write a bundle to a file object
 
235
 
 
236
        :param repository: The repository to retrieve revision data from
 
237
        :param target: The head revision to include ancestors of
 
238
        :param base: The ancestor of the target to stop including acestors
 
239
            at.
 
240
        :param fileobj: The file-like object to write to
 
241
        """
 
242
        write_op =  BundleWriteOperation(base, target, repository, fileobj)
 
243
        return write_op.do_write()
 
244
 
 
245
    def read(self, file):
 
246
        """return a reader object for a given file"""
 
247
        bundle = BundleInfoV4(file, self)
 
248
        return bundle
 
249
 
 
250
    @staticmethod
 
251
    def get_source_serializer(info):
 
252
        """Retrieve the serializer for a given info object"""
 
253
        return serializer.format_registry.get(info['serializer'])
 
254
 
 
255
 
 
256
class BundleWriteOperation(object):
 
257
    """Perform the operation of writing revisions to a bundle"""
 
258
 
 
259
    @classmethod
 
260
    def from_old_args(cls, repository, revision_ids, forced_bases, fileobj):
 
261
        """Create a BundleWriteOperation from old-style arguments"""
 
262
        base, target = cls.get_base_target(revision_ids, forced_bases,
 
263
                                           repository)
 
264
        return BundleWriteOperation(base, target, repository, fileobj,
 
265
                                    revision_ids)
 
266
 
 
267
    def __init__(self, base, target, repository, fileobj, revision_ids=None):
 
268
        self.base = base
 
269
        self.target = target
 
270
        self.repository = repository
 
271
        bundle = BundleWriter(fileobj)
 
272
        self.bundle = bundle
 
273
        if revision_ids is not None:
 
274
            self.revision_ids = revision_ids
 
275
        else:
 
276
            graph = repository.get_graph()
 
277
            revision_ids = graph.find_unique_ancestors(target, [base])
 
278
            # Strip ghosts
 
279
            parents = graph.get_parent_map(revision_ids)
 
280
            self.revision_ids = [r for r in revision_ids if r in parents]
 
281
        self.revision_keys = set([(revid,) for revid in self.revision_ids])
 
282
 
 
283
    def do_write(self):
 
284
        """Write all data to the bundle"""
 
285
        trace.note('Bundling %d revision(s).', len(self.revision_ids))
 
286
        self.repository.lock_read()
 
287
        try:
 
288
            self.bundle.begin()
 
289
            self.write_info()
 
290
            self.write_files()
 
291
            self.write_revisions()
 
292
            self.bundle.end()
 
293
        finally:
 
294
            self.repository.unlock()
 
295
        return self.revision_ids
 
296
 
 
297
    def write_info(self):
 
298
        """Write format info"""
 
299
        serializer_format = self.repository.get_serializer_format()
 
300
        supports_rich_root = {True: 1, False: 0}[
 
301
            self.repository.supports_rich_root()]
 
302
        self.bundle.add_info_record(serializer=serializer_format,
 
303
                                    supports_rich_root=supports_rich_root)
 
304
 
 
305
    def write_files(self):
 
306
        """Write bundle records for all revisions of all files"""
 
307
        text_keys = []
 
308
        altered_fileids = self.repository.fileids_altered_by_revision_ids(
 
309
                self.revision_ids)
 
310
        for file_id, revision_ids in altered_fileids.iteritems():
 
311
            for revision_id in revision_ids:
 
312
                text_keys.append((file_id, revision_id))
 
313
        self._add_mp_records_keys('file', self.repository.texts, text_keys)
 
314
 
 
315
    def write_revisions(self):
 
316
        """Write bundle records for all revisions and signatures"""
 
317
        inv_vf = self.repository.inventories
 
318
        revision_order = [key[-1] for key in multiparent.topo_iter_keys(inv_vf,
 
319
            self.revision_keys)]
 
320
        if self.target is not None and self.target in self.revision_ids:
 
321
            revision_order.remove(self.target)
 
322
            revision_order.append(self.target)
 
323
        self._add_mp_records_keys('inventory', inv_vf, [(revid,) for revid in revision_order])
 
324
        parent_map = self.repository.get_parent_map(revision_order)
 
325
        revision_to_str = self.repository._serializer.write_revision_to_string
 
326
        revisions = self.repository.get_revisions(revision_order)
 
327
        for revision in revisions:
 
328
            revision_id = revision.revision_id
 
329
            parents = parent_map.get(revision_id, None)
 
330
            revision_text = revision_to_str(revision)
 
331
            self.bundle.add_fulltext_record(revision_text, parents,
 
332
                                       'revision', revision_id)
 
333
            try:
 
334
                self.bundle.add_fulltext_record(
 
335
                    self.repository.get_signature_text(
 
336
                    revision_id), parents, 'signature', revision_id)
 
337
            except errors.NoSuchRevision:
 
338
                pass
 
339
 
 
340
    @staticmethod
 
341
    def get_base_target(revision_ids, forced_bases, repository):
 
342
        """Determine the base and target from old-style revision ids"""
 
343
        if len(revision_ids) == 0:
 
344
            return None, None
 
345
        target = revision_ids[0]
 
346
        base = forced_bases.get(target)
 
347
        if base is None:
 
348
            parents = repository.get_revision(target).parent_ids
 
349
            if len(parents) == 0:
 
350
                base = _mod_revision.NULL_REVISION
 
351
            else:
 
352
                base = parents[0]
 
353
        return base, target
 
354
 
 
355
    def _add_mp_records_keys(self, repo_kind, vf, keys):
 
356
        """Add multi-parent diff records to a bundle"""
 
357
        ordered_keys = list(multiparent.topo_iter_keys(vf, keys))
 
358
        mpdiffs = vf.make_mpdiffs(ordered_keys)
 
359
        sha1s = vf.get_sha1s(ordered_keys)
 
360
        parent_map = vf.get_parent_map(ordered_keys)
 
361
        for mpdiff, item_key, in zip(mpdiffs, ordered_keys):
 
362
            sha1 = sha1s[item_key]
 
363
            parents = [key[-1] for key in parent_map[item_key]]
 
364
            text = ''.join(mpdiff.to_patch())
 
365
            # Infer file id records as appropriate.
 
366
            if len(item_key) == 2:
 
367
                file_id = item_key[0]
 
368
            else:
 
369
                file_id = None
 
370
            self.bundle.add_multiparent_record(text, sha1, parents, repo_kind,
 
371
                                               item_key[-1], file_id)
 
372
 
 
373
 
 
374
class BundleInfoV4(object):
 
375
 
 
376
    """Provide (most of) the BundleInfo interface"""
 
377
    def __init__(self, fileobj, serializer):
 
378
        self._fileobj = fileobj
 
379
        self._serializer = serializer
 
380
        self.__real_revisions = None
 
381
        self.__revisions = None
 
382
 
 
383
    def install(self, repository):
 
384
        return self.install_revisions(repository)
 
385
 
 
386
    def install_revisions(self, repository, stream_input=True):
 
387
        """Install this bundle's revisions into the specified repository
 
388
 
 
389
        :param target_repo: The repository to install into
 
390
        :param stream_input: If True, will stream input rather than reading it
 
391
            all into memory at once.  Reading it into memory all at once is
 
392
            (currently) faster.
 
393
        """
 
394
        repository.lock_write()
 
395
        try:
 
396
            ri = RevisionInstaller(self.get_bundle_reader(stream_input),
 
397
                                   self._serializer, repository)
 
398
            return ri.install()
 
399
        finally:
 
400
            repository.unlock()
 
401
 
 
402
    def get_merge_request(self, target_repo):
 
403
        """Provide data for performing a merge
 
404
 
 
405
        Returns suggested base, suggested target, and patch verification status
 
406
        """
 
407
        return None, self.target, 'inapplicable'
 
408
 
 
409
    def get_bundle_reader(self, stream_input=True):
 
410
        """Return a new BundleReader for the associated bundle
 
411
 
 
412
        :param stream_input: If True, the BundleReader stream input rather than
 
413
            reading it all into memory at once.  Reading it into memory all at
 
414
            once is (currently) faster.
 
415
        """
 
416
        self._fileobj.seek(0)
 
417
        return BundleReader(self._fileobj, stream_input)
 
418
 
 
419
    def _get_real_revisions(self):
 
420
        if self.__real_revisions is None:
 
421
            self.__real_revisions = []
 
422
            bundle_reader = self.get_bundle_reader()
 
423
            for bytes, metadata, repo_kind, revision_id, file_id in \
 
424
                bundle_reader.iter_records():
 
425
                if repo_kind == 'info':
 
426
                    serializer =\
 
427
                        self._serializer.get_source_serializer(metadata)
 
428
                if repo_kind == 'revision':
 
429
                    rev = serializer.read_revision_from_string(bytes)
 
430
                    self.__real_revisions.append(rev)
 
431
        return self.__real_revisions
 
432
    real_revisions = property(_get_real_revisions)
 
433
 
 
434
    def _get_revisions(self):
 
435
        if self.__revisions is None:
 
436
            self.__revisions = []
 
437
            for revision in self.real_revisions:
 
438
                self.__revisions.append(
 
439
                    bundle_data.RevisionInfo.from_revision(revision))
 
440
        return self.__revisions
 
441
 
 
442
    revisions = property(_get_revisions)
 
443
 
 
444
    def _get_target(self):
 
445
        return self.revisions[-1].revision_id
 
446
 
 
447
    target = property(_get_target)
 
448
 
 
449
 
 
450
class RevisionInstaller(object):
 
451
    """Installs revisions into a repository"""
 
452
 
 
453
    def __init__(self, container, serializer, repository):
 
454
        self._container = container
 
455
        self._serializer = serializer
 
456
        self._repository = repository
 
457
        self._info = None
 
458
 
 
459
    def install(self):
 
460
        """Perform the installation.
 
461
 
 
462
        Must be called with the Repository locked.
 
463
        """
 
464
        self._repository.start_write_group()
 
465
        try:
 
466
            result = self._install_in_write_group()
 
467
        except:
 
468
            self._repository.abort_write_group()
 
469
            raise
 
470
        self._repository.commit_write_group()
 
471
        return result
 
472
 
 
473
    def _install_in_write_group(self):
 
474
        current_file = None
 
475
        current_versionedfile = None
 
476
        pending_file_records = []
 
477
        inventory_vf = None
 
478
        pending_inventory_records = []
 
479
        added_inv = set()
 
480
        target_revision = None
 
481
        for bytes, metadata, repo_kind, revision_id, file_id in\
 
482
            self._container.iter_records():
 
483
            if repo_kind == 'info':
 
484
                if self._info is not None:
 
485
                    raise AssertionError()
 
486
                self._handle_info(metadata)
 
487
            if (pending_file_records and
 
488
                (repo_kind, file_id) != ('file', current_file)):
 
489
                # Flush the data for a single file - prevents memory
 
490
                # spiking due to buffering all files in memory.
 
491
                self._install_mp_records_keys(self._repository.texts,
 
492
                    pending_file_records)
 
493
                current_file = None
 
494
                del pending_file_records[:]
 
495
            if len(pending_inventory_records) > 0 and repo_kind != 'inventory':
 
496
                self._install_inventory_records(pending_inventory_records)
 
497
                pending_inventory_records = []
 
498
            if repo_kind == 'inventory':
 
499
                pending_inventory_records.append(((revision_id,), metadata, bytes))
 
500
            if repo_kind == 'revision':
 
501
                target_revision = revision_id
 
502
                self._install_revision(revision_id, metadata, bytes)
 
503
            if repo_kind == 'signature':
 
504
                self._install_signature(revision_id, metadata, bytes)
 
505
            if repo_kind == 'file':
 
506
                current_file = file_id
 
507
                pending_file_records.append(((file_id, revision_id), metadata, bytes))
 
508
        self._install_mp_records_keys(self._repository.texts, pending_file_records)
 
509
        return target_revision
 
510
 
 
511
    def _handle_info(self, info):
 
512
        """Extract data from an info record"""
 
513
        self._info = info
 
514
        self._source_serializer = self._serializer.get_source_serializer(info)
 
515
        if (info['supports_rich_root'] == 0 and
 
516
            self._repository.supports_rich_root()):
 
517
            self.update_root = True
 
518
        else:
 
519
            self.update_root = False
 
520
 
 
521
    def _install_mp_records(self, versionedfile, records):
 
522
        if len(records) == 0:
 
523
            return
 
524
        d_func = multiparent.MultiParent.from_patch
 
525
        vf_records = [(r, m['parents'], m['sha1'], d_func(t)) for r, m, t in
 
526
                      records if r not in versionedfile]
 
527
        versionedfile.add_mpdiffs(vf_records)
 
528
 
 
529
    def _install_mp_records_keys(self, versionedfile, records):
 
530
        d_func = multiparent.MultiParent.from_patch
 
531
        vf_records = []
 
532
        for key, meta, text in records:
 
533
            # Adapt to tuple interface: A length two key is a file_id,
 
534
            # revision_id pair, a length 1 key is a
 
535
            # revision/signature/inventory. We need to do this because
 
536
            # the metadata extraction from the bundle has not yet been updated
 
537
            # to use the consistent tuple interface itself.
 
538
            if len(key) == 2:
 
539
                prefix = key[:1]
 
540
            else:
 
541
                prefix = ()
 
542
            parents = [prefix + (parent,) for parent in meta['parents']]
 
543
            vf_records.append((key, parents, meta['sha1'], d_func(text)))
 
544
        versionedfile.add_mpdiffs(vf_records)
 
545
 
 
546
    def _install_inventory_records(self, records):
 
547
        if self._info['serializer'] == self._repository._serializer.format_num:
 
548
            return self._install_mp_records_keys(self._repository.inventories,
 
549
                records)
 
550
        for key, metadata, bytes in records:
 
551
            revision_id = key[-1]
 
552
            parent_ids = metadata['parents']
 
553
            parents = [self._repository.get_inventory(p)
 
554
                       for p in parent_ids]
 
555
            p_texts = [self._source_serializer.write_inventory_to_string(p)
 
556
                       for p in parents]
 
557
            target_lines = multiparent.MultiParent.from_patch(bytes).to_lines(
 
558
                p_texts)
 
559
            sha1 = osutils.sha_strings(target_lines)
 
560
            if sha1 != metadata['sha1']:
 
561
                raise errors.BadBundle("Can't convert to target format")
 
562
            target_inv = self._source_serializer.read_inventory_from_string(
 
563
                ''.join(target_lines))
 
564
            self._handle_root(target_inv, parent_ids)
 
565
            try:
 
566
                self._repository.add_inventory(revision_id, target_inv,
 
567
                                               parent_ids)
 
568
            except errors.UnsupportedInventoryKind:
 
569
                raise errors.IncompatibleRevision(repr(self._repository))
 
570
 
 
571
    def _handle_root(self, target_inv, parent_ids):
 
572
        revision_id = target_inv.revision_id
 
573
        if self.update_root:
 
574
            text_key = (target_inv.root.file_id, revision_id)
 
575
            parent_keys = [(target_inv.root.file_id, parent) for
 
576
                parent in parent_ids]
 
577
            self._repository.texts.add_lines(text_key, parent_keys, [])
 
578
        elif not self._repository.supports_rich_root():
 
579
            if target_inv.root.revision != revision_id:
 
580
                raise errors.IncompatibleRevision(repr(self._repository))
 
581
 
 
582
    def _install_revision(self, revision_id, metadata, text):
 
583
        if self._repository.has_revision(revision_id):
 
584
            return
 
585
        revision = self._source_serializer.read_revision_from_string(text)
 
586
        self._repository.add_revision(revision.revision_id, revision)
 
587
 
 
588
    def _install_signature(self, revision_id, metadata, text):
 
589
        transaction = self._repository.get_transaction()
 
590
        if self._repository.has_signature_for_revision_id(revision_id):
 
591
            return
 
592
        self._repository.add_signature_text(revision_id, text)