/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

  • Committer: mernst at mit
  • Date: 2008-10-16 10:57:16 UTC
  • mto: This revision was merged to the branch mainline in revision 3799.
  • Revision ID: mernst@csail.mit.edu-20081016105716-v8x8n5t2pf7f6uds
Improved documentation of stacked and lightweight branches

These patches improve the User Guide's documentation of stacked and
lightweight branches.

Section "1.2.6 Putting the concepts together" should mention stacked
branches and the difference between them and lightweight branches.  It
should also contain links to further details of the common scenarios.

Section "5.3.4 Getting a lightweight checkout" should mention stacked
branches as an option, and should link to all the options, not just some of
them.  It should also clarify that lightweight only applies to checkouts,
not to arbitrary branches.

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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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
    xml_serializer,
 
31
    )
 
32
from bzrlib.bundle import bundle_data, serializer
 
33
from bzrlib.util 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(serializer._get_bundle_header(
 
58
            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(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 xml_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
        self.base_ancestry = set(repository.get_ancestry(base,
 
274
                                                         topo_sorted=False))
 
275
        if revision_ids is not None:
 
276
            self.revision_ids = revision_ids
 
277
        else:
 
278
            revision_ids = set(repository.get_ancestry(target,
 
279
                                                       topo_sorted=False))
 
280
            self.revision_ids = revision_ids.difference(self.base_ancestry)
 
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
        self.repository.lock_read()
 
286
        try:
 
287
            self.bundle.begin()
 
288
            self.write_info()
 
289
            self.write_files()
 
290
            self.write_revisions()
 
291
            self.bundle.end()
 
292
        finally:
 
293
            self.repository.unlock()
 
294
        return self.revision_ids
 
295
 
 
296
    def write_info(self):
 
297
        """Write format info"""
 
298
        serializer_format = self.repository.get_serializer_format()
 
299
        supports_rich_root = {True: 1, False: 0}[
 
300
            self.repository.supports_rich_root()]
 
301
        self.bundle.add_info_record(serializer=serializer_format,
 
302
                                    supports_rich_root=supports_rich_root)
 
303
 
 
304
    def write_files(self):
 
305
        """Write bundle records for all revisions of all files"""
 
306
        text_keys = []
 
307
        altered_fileids = self.repository.fileids_altered_by_revision_ids(
 
308
                self.revision_ids)
 
309
        for file_id, revision_ids in altered_fileids.iteritems():
 
310
            for revision_id in revision_ids:
 
311
                text_keys.append((file_id, revision_id))
 
312
        self._add_mp_records_keys('file', self.repository.texts, text_keys)
 
313
 
 
314
    def write_revisions(self):
 
315
        """Write bundle records for all revisions and signatures"""
 
316
        inv_vf = self.repository.inventories
 
317
        revision_order = [key[-1] for key in multiparent.topo_iter_keys(inv_vf,
 
318
            self.revision_keys)]
 
319
        if self.target is not None and self.target in self.revision_ids:
 
320
            revision_order.remove(self.target)
 
321
            revision_order.append(self.target)
 
322
        self._add_mp_records_keys('inventory', inv_vf, [(revid,) for revid in revision_order])
 
323
        parent_map = self.repository.get_parent_map(revision_order)
 
324
        for revision_id in revision_order:
 
325
            parents = parent_map.get(revision_id, None)
 
326
            revision_text = self.repository.get_revision_xml(revision_id)
 
327
            self.bundle.add_fulltext_record(revision_text, parents,
 
328
                                       'revision', revision_id)
 
329
            try:
 
330
                self.bundle.add_fulltext_record(
 
331
                    self.repository.get_signature_text(
 
332
                    revision_id), parents, 'signature', revision_id)
 
333
            except errors.NoSuchRevision:
 
334
                pass
 
335
 
 
336
    @staticmethod
 
337
    def get_base_target(revision_ids, forced_bases, repository):
 
338
        """Determine the base and target from old-style revision ids"""
 
339
        if len(revision_ids) == 0:
 
340
            return None, None
 
341
        target = revision_ids[0]
 
342
        base = forced_bases.get(target)
 
343
        if base is None:
 
344
            parents = repository.get_revision(target).parent_ids
 
345
            if len(parents) == 0:
 
346
                base = _mod_revision.NULL_REVISION
 
347
            else:
 
348
                base = parents[0]
 
349
        return base, target
 
350
 
 
351
    def _add_mp_records_keys(self, repo_kind, vf, keys):
 
352
        """Add multi-parent diff records to a bundle"""
 
353
        ordered_keys = list(multiparent.topo_iter_keys(vf, keys))
 
354
        mpdiffs = vf.make_mpdiffs(ordered_keys)
 
355
        sha1s = vf.get_sha1s(ordered_keys)
 
356
        parent_map = vf.get_parent_map(ordered_keys)
 
357
        for mpdiff, item_key, in zip(mpdiffs, ordered_keys):
 
358
            sha1 = sha1s[item_key]
 
359
            parents = [key[-1] for key in parent_map[item_key]]
 
360
            text = ''.join(mpdiff.to_patch())
 
361
            # Infer file id records as appropriate.
 
362
            if len(item_key) == 2:
 
363
                file_id = item_key[0]
 
364
            else:
 
365
                file_id = None
 
366
            self.bundle.add_multiparent_record(text, sha1, parents, repo_kind,
 
367
                                               item_key[-1], file_id)
 
368
 
 
369
 
 
370
class BundleInfoV4(object):
 
371
 
 
372
    """Provide (most of) the BundleInfo interface"""
 
373
    def __init__(self, fileobj, serializer):
 
374
        self._fileobj = fileobj
 
375
        self._serializer = serializer
 
376
        self.__real_revisions = None
 
377
        self.__revisions = None
 
378
 
 
379
    def install(self, repository):
 
380
        return self.install_revisions(repository)
 
381
 
 
382
    def install_revisions(self, repository, stream_input=True):
 
383
        """Install this bundle's revisions into the specified repository
 
384
 
 
385
        :param target_repo: The repository to install into
 
386
        :param stream_input: If True, will stream input rather than reading it
 
387
            all into memory at once.  Reading it into memory all at once is
 
388
            (currently) faster.
 
389
        """
 
390
        repository.lock_write()
 
391
        try:
 
392
            ri = RevisionInstaller(self.get_bundle_reader(stream_input),
 
393
                                   self._serializer, repository)
 
394
            return ri.install()
 
395
        finally:
 
396
            repository.unlock()
 
397
 
 
398
    def get_merge_request(self, target_repo):
 
399
        """Provide data for performing a merge
 
400
 
 
401
        Returns suggested base, suggested target, and patch verification status
 
402
        """
 
403
        return None, self.target, 'inapplicable'
 
404
 
 
405
    def get_bundle_reader(self, stream_input=True):
 
406
        """Return a new BundleReader for the associated bundle
 
407
 
 
408
        :param stream_input: If True, the BundleReader stream input rather than
 
409
            reading it all into memory at once.  Reading it into memory all at
 
410
            once is (currently) faster.
 
411
        """
 
412
        self._fileobj.seek(0)
 
413
        return BundleReader(self._fileobj, stream_input)
 
414
 
 
415
    def _get_real_revisions(self):
 
416
        if self.__real_revisions is None:
 
417
            self.__real_revisions = []
 
418
            bundle_reader = self.get_bundle_reader()
 
419
            for bytes, metadata, repo_kind, revision_id, file_id in \
 
420
                bundle_reader.iter_records():
 
421
                if repo_kind == 'info':
 
422
                    serializer =\
 
423
                        self._serializer.get_source_serializer(metadata)
 
424
                if repo_kind == 'revision':
 
425
                    rev = serializer.read_revision_from_string(bytes)
 
426
                    self.__real_revisions.append(rev)
 
427
        return self.__real_revisions
 
428
    real_revisions = property(_get_real_revisions)
 
429
 
 
430
    def _get_revisions(self):
 
431
        if self.__revisions is None:
 
432
            self.__revisions = []
 
433
            for revision in self.real_revisions:
 
434
                self.__revisions.append(
 
435
                    bundle_data.RevisionInfo.from_revision(revision))
 
436
        return self.__revisions
 
437
 
 
438
    revisions = property(_get_revisions)
 
439
 
 
440
    def _get_target(self):
 
441
        return self.revisions[-1].revision_id
 
442
 
 
443
    target = property(_get_target)
 
444
 
 
445
 
 
446
class RevisionInstaller(object):
 
447
    """Installs revisions into a repository"""
 
448
 
 
449
    def __init__(self, container, serializer, repository):
 
450
        self._container = container
 
451
        self._serializer = serializer
 
452
        self._repository = repository
 
453
        self._info = None
 
454
 
 
455
    def install(self):
 
456
        """Perform the installation.
 
457
        
 
458
        Must be called with the Repository locked.
 
459
        """
 
460
        self._repository.start_write_group()
 
461
        try:
 
462
            result = self._install_in_write_group()
 
463
        except:
 
464
            self._repository.abort_write_group()
 
465
            raise
 
466
        self._repository.commit_write_group()
 
467
        return result
 
468
 
 
469
    def _install_in_write_group(self):
 
470
        current_file = None
 
471
        current_versionedfile = None
 
472
        pending_file_records = []
 
473
        inventory_vf = None
 
474
        pending_inventory_records = []
 
475
        added_inv = set()
 
476
        target_revision = None
 
477
        for bytes, metadata, repo_kind, revision_id, file_id in\
 
478
            self._container.iter_records():
 
479
            if repo_kind == 'info':
 
480
                if self._info is not None:
 
481
                    raise AssertionError()
 
482
                self._handle_info(metadata)
 
483
            if (pending_file_records and
 
484
                (repo_kind, file_id) != ('file', current_file)):
 
485
                # Flush the data for a single file - prevents memory
 
486
                # spiking due to buffering all files in memory.
 
487
                self._install_mp_records_keys(self._repository.texts,
 
488
                    pending_file_records)
 
489
                current_file = None
 
490
                del pending_file_records[:]
 
491
            if len(pending_inventory_records) > 0 and repo_kind != 'inventory':
 
492
                self._install_inventory_records(pending_inventory_records)
 
493
                pending_inventory_records = []
 
494
            if repo_kind == 'inventory':
 
495
                pending_inventory_records.append(((revision_id,), metadata, bytes))
 
496
            if repo_kind == 'revision':
 
497
                target_revision = revision_id
 
498
                self._install_revision(revision_id, metadata, bytes)
 
499
            if repo_kind == 'signature':
 
500
                self._install_signature(revision_id, metadata, bytes)
 
501
            if repo_kind == 'file':
 
502
                current_file = file_id
 
503
                pending_file_records.append(((file_id, revision_id), metadata, bytes))
 
504
        self._install_mp_records_keys(self._repository.texts, pending_file_records)
 
505
        return target_revision
 
506
 
 
507
    def _handle_info(self, info):
 
508
        """Extract data from an info record"""
 
509
        self._info = info
 
510
        self._source_serializer = self._serializer.get_source_serializer(info)
 
511
        if (info['supports_rich_root'] == 0 and
 
512
            self._repository.supports_rich_root()):
 
513
            self.update_root = True
 
514
        else:
 
515
            self.update_root = False
 
516
 
 
517
    def _install_mp_records(self, versionedfile, records):
 
518
        if len(records) == 0:
 
519
            return
 
520
        d_func = multiparent.MultiParent.from_patch
 
521
        vf_records = [(r, m['parents'], m['sha1'], d_func(t)) for r, m, t in
 
522
                      records if r not in versionedfile]
 
523
        versionedfile.add_mpdiffs(vf_records)
 
524
 
 
525
    def _install_mp_records_keys(self, versionedfile, records):
 
526
        d_func = multiparent.MultiParent.from_patch
 
527
        vf_records = []
 
528
        for key, meta, text in records:
 
529
            # Adapt to tuple interface: A length two key is a file_id,
 
530
            # revision_id pair, a length 1 key is a
 
531
            # revision/signature/inventory. We need to do this because
 
532
            # the metadata extraction from the bundle has not yet been updated
 
533
            # to use the consistent tuple interface itself.
 
534
            if len(key) == 2:
 
535
                prefix = key[:1]
 
536
            else:
 
537
                prefix = ()
 
538
            parents = [prefix + (parent,) for parent in meta['parents']]
 
539
            vf_records.append((key, parents, meta['sha1'], d_func(text)))
 
540
        versionedfile.add_mpdiffs(vf_records)
 
541
 
 
542
    def _install_inventory_records(self, records):
 
543
        if self._info['serializer'] == self._repository._serializer.format_num:
 
544
            return self._install_mp_records_keys(self._repository.inventories,
 
545
                records)
 
546
        for key, metadata, bytes in records:
 
547
            revision_id = key[-1]
 
548
            parent_ids = metadata['parents']
 
549
            parents = [self._repository.get_inventory(p)
 
550
                       for p in parent_ids]
 
551
            p_texts = [self._source_serializer.write_inventory_to_string(p)
 
552
                       for p in parents]
 
553
            target_lines = multiparent.MultiParent.from_patch(bytes).to_lines(
 
554
                p_texts)
 
555
            sha1 = osutils.sha_strings(target_lines)
 
556
            if sha1 != metadata['sha1']:
 
557
                raise errors.BadBundle("Can't convert to target format")
 
558
            target_inv = self._source_serializer.read_inventory_from_string(
 
559
                ''.join(target_lines))
 
560
            self._handle_root(target_inv, parent_ids)
 
561
            try:
 
562
                self._repository.add_inventory(revision_id, target_inv,
 
563
                                               parent_ids)
 
564
            except errors.UnsupportedInventoryKind:
 
565
                raise errors.IncompatibleRevision(repr(self._repository))
 
566
 
 
567
    def _handle_root(self, target_inv, parent_ids):
 
568
        revision_id = target_inv.revision_id
 
569
        if self.update_root:
 
570
            text_key = (target_inv.root.file_id, revision_id)
 
571
            parent_keys = [(target_inv.root.file_id, parent) for
 
572
                parent in parent_ids]
 
573
            self._repository.texts.add_lines(text_key, parent_keys, [])
 
574
        elif not self._repository.supports_rich_root():
 
575
            if target_inv.root.revision != revision_id:
 
576
                raise errors.IncompatibleRevision(repr(self._repository))
 
577
 
 
578
    def _install_revision(self, revision_id, metadata, text):
 
579
        if self._repository.has_revision(revision_id):
 
580
            return
 
581
        revision = self._source_serializer.read_revision_from_string(text)
 
582
        self._repository.add_revision(revision.revision_id, revision)
 
583
 
 
584
    def _install_signature(self, revision_id, metadata, text):
 
585
        transaction = self._repository.get_transaction()
 
586
        if self._repository.has_signature_for_revision_id(revision_id):
 
587
            return
 
588
        self._repository.add_signature_text(revision_id, text)