/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: Vincent Ladeuil
  • Date: 2007-09-19 06:42:50 UTC
  • mto: (2831.1.1 bzr.integration)
  • mto: This revision was merged to the branch mainline in revision 2832.
  • Revision ID: v.ladeuil+lp@free.fr-20070919064250-zczjpcv2rrlx612x
Review feeback.

* bzrlib/tests/transport_util.py (_change_scheme_in): 
Fix indentation, replace [] with "".

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
        assert content_kind in ('revision', 'file', 'inventory', 'signature',
 
111
                                'info')
 
112
 
 
113
        if content_kind == 'file':
 
114
            assert file_id is not None
 
115
        else:
 
116
            assert file_id is None
 
117
        if content_kind == 'info':
 
118
            assert revision_id is None
 
119
        else:
 
120
            assert revision_id is not None
 
121
        names = [n.replace('/', '//') for n in
 
122
                 (content_kind, revision_id, file_id) if n is not None]
 
123
        return '/'.join(names)
 
124
 
 
125
    def _add_record(self, bytes, metadata, repo_kind, revision_id, file_id):
 
126
        """Add a bundle record to the container.
 
127
 
 
128
        Most bundle records are recorded as header/body pairs, with the
 
129
        body being nameless.  Records with storage_kind 'header' have no
 
130
        body.
 
131
        """
 
132
        name = self.encode_name(repo_kind, revision_id, file_id)
 
133
        encoded_metadata = bencode.bencode(metadata)
 
134
        self._container.add_bytes_record(encoded_metadata, [(name, )])
 
135
        if metadata['storage_kind'] != 'header':
 
136
            self._container.add_bytes_record(bytes, [])
 
137
 
 
138
 
 
139
class BundleReader(object):
 
140
    """Reader for bundle-format files.
 
141
 
 
142
    This serves roughly the same purpose as ContainerReader, but acts as a
 
143
    layer on top of it, providing metadata, a semantic name, and a record
 
144
    body
 
145
    """
 
146
 
 
147
    def __init__(self, fileobj, stream_input=True):
 
148
        """Constructor
 
149
 
 
150
        :param fileobj: a file containing a bzip-encoded container
 
151
        :param stream_input: If True, the BundleReader stream input rather than
 
152
            reading it all into memory at once.  Reading it into memory all at
 
153
            once is (currently) faster.
 
154
        """
 
155
        line = fileobj.readline()
 
156
        if line != '\n':
 
157
            fileobj.readline()
 
158
        self.patch_lines = []
 
159
        if stream_input:
 
160
            source_file = iterablefile.IterableFile(self.iter_decode(fileobj))
 
161
        else:
 
162
            source_file = StringIO(bz2.decompress(fileobj.read()))
 
163
        self._container = pack.ContainerReader(source_file)
 
164
 
 
165
    @staticmethod
 
166
    def iter_decode(fileobj):
 
167
        """Iterate through decoded fragments of the file"""
 
168
        decompressor = bz2.BZ2Decompressor()
 
169
        for line in fileobj:
 
170
            yield decompressor.decompress(line)
 
171
 
 
172
    @staticmethod
 
173
    def decode_name(name):
 
174
        """Decode a name from its container form into a semantic form
 
175
 
 
176
        :retval: content_kind, revision_id, file_id
 
177
        """
 
178
        segments = re.split('(//?)', name)
 
179
        names = ['']
 
180
        for segment in segments:
 
181
            if segment == '//':
 
182
                names[-1] += '/'
 
183
            elif segment == '/':
 
184
                names.append('')
 
185
            else:
 
186
                names[-1] += segment
 
187
        content_kind = names[0]
 
188
        revision_id = None
 
189
        file_id = None
 
190
        if len(names) > 1:
 
191
            revision_id = names[1]
 
192
        if len(names) > 2:
 
193
            file_id = names[2]
 
194
        return content_kind, revision_id, file_id
 
195
 
 
196
    def iter_records(self):
 
197
        """Iterate through bundle records
 
198
 
 
199
        :return: a generator of (bytes, metadata, content_kind, revision_id,
 
200
            file_id)
 
201
        """
 
202
        iterator = self._container.iter_records()
 
203
        for names, meta_bytes in iterator:
 
204
            if len(names) != 1:
 
205
                raise errors.BadBundle('Record has %d names instead of 1'
 
206
                                       % len(names))
 
207
            metadata = bencode.bdecode(meta_bytes(None))
 
208
            if metadata['storage_kind'] == 'header':
 
209
                bytes = None
 
210
            else:
 
211
                _unused, bytes = iterator.next()
 
212
                bytes = bytes(None)
 
213
            yield (bytes, metadata) + self.decode_name(names[0][0])
 
214
 
 
215
 
 
216
class BundleSerializerV4(serializer.BundleSerializer):
 
217
    """Implement the high-level bundle interface"""
 
218
 
 
219
    def write(self, repository, revision_ids, forced_bases, fileobj):
 
220
        """Write a bundle to a file-like object
 
221
 
 
222
        For backwards-compatibility only
 
223
        """
 
224
        write_op = BundleWriteOperation.from_old_args(repository, revision_ids,
 
225
                                                      forced_bases, fileobj)
 
226
        return write_op.do_write()
 
227
 
 
228
    def write_bundle(self, repository, target, base, fileobj):
 
229
        """Write a bundle to a file object
 
230
 
 
231
        :param repository: The repository to retrieve revision data from
 
232
        :param target: The head revision to include ancestors of
 
233
        :param base: The ancestor of the target to stop including acestors
 
234
            at.
 
235
        :param fileobj: The file-like object to write to
 
236
        """
 
237
        write_op =  BundleWriteOperation(base, target, repository, fileobj)
 
238
        return write_op.do_write()
 
239
 
 
240
    def read(self, file):
 
241
        """return a reader object for a given file"""
 
242
        bundle = BundleInfoV4(file, self)
 
243
        return bundle
 
244
 
 
245
    @staticmethod
 
246
    def get_source_serializer(info):
 
247
        """Retrieve the serializer for a given info object"""
 
248
        return xml_serializer.format_registry.get(info['serializer'])
 
249
 
 
250
 
 
251
class BundleWriteOperation(object):
 
252
    """Perform the operation of writing revisions to a bundle"""
 
253
 
 
254
    @classmethod
 
255
    def from_old_args(cls, repository, revision_ids, forced_bases, fileobj):
 
256
        """Create a BundleWriteOperation from old-style arguments"""
 
257
        base, target = cls.get_base_target(revision_ids, forced_bases,
 
258
                                           repository)
 
259
        return BundleWriteOperation(base, target, repository, fileobj,
 
260
                                    revision_ids)
 
261
 
 
262
    def __init__(self, base, target, repository, fileobj, revision_ids=None):
 
263
        self.base = base
 
264
        self.target = target
 
265
        self.repository = repository
 
266
        bundle = BundleWriter(fileobj)
 
267
        self.bundle = bundle
 
268
        self.base_ancestry = set(repository.get_ancestry(base,
 
269
                                                         topo_sorted=False))
 
270
        if revision_ids is not None:
 
271
            self.revision_ids = revision_ids
 
272
        else:
 
273
            revision_ids = set(repository.get_ancestry(target,
 
274
                                                       topo_sorted=False))
 
275
            self.revision_ids = revision_ids.difference(self.base_ancestry)
 
276
 
 
277
    def do_write(self):
 
278
        """Write all data to the bundle"""
 
279
        self.bundle.begin()
 
280
        self.write_info()
 
281
        self.write_files()
 
282
        self.write_revisions()
 
283
        self.bundle.end()
 
284
        return self.revision_ids
 
285
 
 
286
    def write_info(self):
 
287
        """Write format info"""
 
288
        serializer_format = self.repository.get_serializer_format()
 
289
        supports_rich_root = {True: 1, False: 0}[
 
290
            self.repository.supports_rich_root()]
 
291
        self.bundle.add_info_record(serializer=serializer_format,
 
292
                                    supports_rich_root=supports_rich_root)
 
293
 
 
294
    def iter_file_revisions(self):
 
295
        """Iterate through all relevant revisions of all files.
 
296
 
 
297
        This is the correct implementation, but is not compatible with bzr.dev,
 
298
        because certain old revisions were not converted correctly, and have
 
299
        the wrong "revision" marker in inventories.
 
300
        """
 
301
        transaction = self.repository.get_transaction()
 
302
        altered = self.repository.fileids_altered_by_revision_ids(
 
303
            self.revision_ids)
 
304
        for file_id, file_revision_ids in altered.iteritems():
 
305
            vf = self.repository.weave_store.get_weave(file_id, transaction)
 
306
            yield vf, file_id, file_revision_ids
 
307
 
 
308
    def iter_file_revisions_aggressive(self):
 
309
        """Iterate through all relevant revisions of all files.
 
310
 
 
311
        This uses the standard iter_file_revisions to determine what revisions
 
312
        are referred to by inventories, but then uses the versionedfile to
 
313
        determine what the build-dependencies of each required revision.
 
314
 
 
315
        All build dependencies which are not ancestors of the base revision
 
316
        are emitted.
 
317
        """
 
318
        for vf, file_id, file_revision_ids in self.iter_file_revisions():
 
319
            new_revision_ids = set()
 
320
            pending = list(file_revision_ids)
 
321
            while len(pending) > 0:
 
322
                revision_id = pending.pop()
 
323
                if revision_id in new_revision_ids:
 
324
                    continue
 
325
                if revision_id in self.base_ancestry:
 
326
                    continue
 
327
                new_revision_ids.add(revision_id)
 
328
                pending.extend(vf.get_parents(revision_id))
 
329
            yield vf, file_id, new_revision_ids
 
330
 
 
331
    def write_files(self):
 
332
        """Write bundle records for all revisions of all files"""
 
333
        for vf, file_id, revision_ids in self.iter_file_revisions_aggressive():
 
334
            self.add_mp_records('file', file_id, vf, revision_ids)
 
335
 
 
336
    def write_revisions(self):
 
337
        """Write bundle records for all revisions and signatures"""
 
338
        inv_vf = self.repository.get_inventory_weave()
 
339
        revision_order = list(multiparent.topo_iter(inv_vf, self.revision_ids))
 
340
        if self.target is not None and self.target in self.revision_ids:
 
341
            revision_order.remove(self.target)
 
342
            revision_order.append(self.target)
 
343
        self.add_mp_records('inventory', None, inv_vf, revision_order)
 
344
        parents_list = self.repository.get_parents(revision_order)
 
345
        for parents, revision_id in zip(parents_list, revision_order):
 
346
            revision_text = self.repository.get_revision_xml(revision_id)
 
347
            self.bundle.add_fulltext_record(revision_text, parents,
 
348
                                       'revision', revision_id)
 
349
            try:
 
350
                self.bundle.add_fulltext_record(
 
351
                    self.repository.get_signature_text(
 
352
                    revision_id), parents, 'signature', revision_id)
 
353
            except errors.NoSuchRevision:
 
354
                pass
 
355
 
 
356
    @staticmethod
 
357
    def get_base_target(revision_ids, forced_bases, repository):
 
358
        """Determine the base and target from old-style revision ids"""
 
359
        if len(revision_ids) == 0:
 
360
            return None, None
 
361
        target = revision_ids[0]
 
362
        base = forced_bases.get(target)
 
363
        if base is None:
 
364
            parents = repository.get_revision(target).parent_ids
 
365
            if len(parents) == 0:
 
366
                base = _mod_revision.NULL_REVISION
 
367
            else:
 
368
                base = parents[0]
 
369
        return base, target
 
370
 
 
371
    def add_mp_records(self, repo_kind, file_id, vf, revision_ids):
 
372
        """Add multi-parent diff records to a bundle"""
 
373
        revision_ids = list(multiparent.topo_iter(vf, revision_ids))
 
374
        mpdiffs = vf.make_mpdiffs(revision_ids)
 
375
        sha1s = vf.get_sha1s(revision_ids)
 
376
        for mpdiff, revision_id, sha1, in zip(mpdiffs, revision_ids, sha1s):
 
377
            parents = vf.get_parents(revision_id)
 
378
            text = ''.join(mpdiff.to_patch())
 
379
            self.bundle.add_multiparent_record(text, sha1, parents, repo_kind,
 
380
                                               revision_id, file_id)
 
381
 
 
382
 
 
383
class BundleInfoV4(object):
 
384
 
 
385
    """Provide (most of) the BundleInfo interface"""
 
386
    def __init__(self, fileobj, serializer):
 
387
        self._fileobj = fileobj
 
388
        self._serializer = serializer
 
389
        self.__real_revisions = None
 
390
        self.__revisions = None
 
391
 
 
392
    def install(self, repository):
 
393
        return self.install_revisions(repository)
 
394
 
 
395
    def install_revisions(self, repository, stream_input=True):
 
396
        """Install this bundle's revisions into the specified repository
 
397
 
 
398
        :param target_repo: The repository to install into
 
399
        :param stream_input: If True, will stream input rather than reading it
 
400
            all into memory at once.  Reading it into memory all at once is
 
401
            (currently) faster.
 
402
        """
 
403
        repository.lock_write()
 
404
        try:
 
405
            ri = RevisionInstaller(self.get_bundle_reader(stream_input),
 
406
                                   self._serializer, repository)
 
407
            return ri.install()
 
408
        finally:
 
409
            repository.unlock()
 
410
 
 
411
    def get_merge_request(self, target_repo):
 
412
        """Provide data for performing a merge
 
413
 
 
414
        Returns suggested base, suggested target, and patch verification status
 
415
        """
 
416
        return None, self.target, 'inapplicable'
 
417
 
 
418
    def get_bundle_reader(self, stream_input=True):
 
419
        """Return a new BundleReader for the associated bundle
 
420
 
 
421
        :param stream_input: If True, the BundleReader stream input rather than
 
422
            reading it all into memory at once.  Reading it into memory all at
 
423
            once is (currently) faster.
 
424
        """
 
425
        self._fileobj.seek(0)
 
426
        return BundleReader(self._fileobj, stream_input)
 
427
 
 
428
    def _get_real_revisions(self):
 
429
        if self.__real_revisions is None:
 
430
            self.__real_revisions = []
 
431
            bundle_reader = self.get_bundle_reader()
 
432
            for bytes, metadata, repo_kind, revision_id, file_id in \
 
433
                bundle_reader.iter_records():
 
434
                if repo_kind == 'info':
 
435
                    serializer =\
 
436
                        self._serializer.get_source_serializer(metadata)
 
437
                if repo_kind == 'revision':
 
438
                    rev = serializer.read_revision_from_string(bytes)
 
439
                    self.__real_revisions.append(rev)
 
440
        return self.__real_revisions
 
441
    real_revisions = property(_get_real_revisions)
 
442
 
 
443
    def _get_revisions(self):
 
444
        if self.__revisions is None:
 
445
            self.__revisions = []
 
446
            for revision in self.real_revisions:
 
447
                self.__revisions.append(
 
448
                    bundle_data.RevisionInfo.from_revision(revision))
 
449
        return self.__revisions
 
450
 
 
451
    revisions = property(_get_revisions)
 
452
 
 
453
    def _get_target(self):
 
454
        return self.revisions[-1].revision_id
 
455
 
 
456
    target = property(_get_target)
 
457
 
 
458
 
 
459
class RevisionInstaller(object):
 
460
    """Installs revisions into a repository"""
 
461
 
 
462
    def __init__(self, container, serializer, repository):
 
463
        self._container = container
 
464
        self._serializer = serializer
 
465
        self._repository = repository
 
466
        self._info = None
 
467
 
 
468
    def install(self):
 
469
        """Perform the installation"""
 
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
                assert self._info is None
 
481
                self._handle_info(metadata)
 
482
            if (repo_kind, file_id) != ('file', current_file):
 
483
                if len(pending_file_records) > 0:
 
484
                    self._install_mp_records(current_versionedfile,
 
485
                                             pending_file_records)
 
486
                current_file = None
 
487
                current_versionedfile = None
 
488
                pending_file_records = []
 
489
            if len(pending_inventory_records) > 0 and repo_kind != 'inventory':
 
490
                self._install_inventory_records(inventory_vf,
 
491
                                                pending_inventory_records)
 
492
                pending_inventory_records = []
 
493
            if repo_kind == 'inventory':
 
494
                if inventory_vf is None:
 
495
                    inventory_vf = self._repository.get_inventory_weave()
 
496
                if revision_id not in inventory_vf:
 
497
                    pending_inventory_records.append((revision_id, metadata,
 
498
                                                      bytes))
 
499
            if repo_kind == 'revision':
 
500
                target_revision = revision_id
 
501
                self._install_revision(revision_id, metadata, bytes)
 
502
            if repo_kind == 'signature':
 
503
                self._install_signature(revision_id, metadata, bytes)
 
504
            if repo_kind == 'file':
 
505
                current_file = file_id
 
506
                if current_versionedfile is None:
 
507
                    current_versionedfile = \
 
508
                        self._repository.weave_store.get_weave_or_empty(
 
509
                        file_id, self._repository.get_transaction())
 
510
                    pending_file_records = []
 
511
                if revision_id in current_versionedfile:
 
512
                    continue
 
513
                pending_file_records.append((revision_id, metadata, bytes))
 
514
        self._install_mp_records(current_versionedfile, pending_file_records)
 
515
        return target_revision
 
516
 
 
517
    def _handle_info(self, info):
 
518
        """Extract data from an info record"""
 
519
        self._info = info
 
520
        self._source_serializer = self._serializer.get_source_serializer(info)
 
521
        if (info['supports_rich_root'] == 0 and
 
522
            self._repository.supports_rich_root()):
 
523
            self.update_root = True
 
524
        else:
 
525
            self.update_root = False
 
526
 
 
527
    def _install_mp_records(self, versionedfile, records):
 
528
        if len(records) == 0:
 
529
            return
 
530
        d_func = multiparent.MultiParent.from_patch
 
531
        vf_records = [(r, m['parents'], m['sha1'], d_func(t)) for r, m, t in
 
532
                      records if r not in versionedfile]
 
533
        versionedfile.add_mpdiffs(vf_records)
 
534
 
 
535
    def _install_inventory_records(self, vf, records):
 
536
        if self._info['serializer'] == self._repository._serializer.format_num:
 
537
            return self._install_mp_records(vf, records)
 
538
        for revision_id, metadata, bytes in records:
 
539
            parent_ids = metadata['parents']
 
540
            parents = [self._repository.get_inventory(p)
 
541
                       for p in parent_ids]
 
542
            p_texts = [self._source_serializer.write_inventory_to_string(p)
 
543
                       for p in parents]
 
544
            target_lines = multiparent.MultiParent.from_patch(bytes).to_lines(
 
545
                p_texts)
 
546
            sha1 = osutils.sha_strings(target_lines)
 
547
            if sha1 != metadata['sha1']:
 
548
                raise errors.BadBundle("Can't convert to target format")
 
549
            target_inv = self._source_serializer.read_inventory_from_string(
 
550
                ''.join(target_lines))
 
551
            self._handle_root(target_inv, parent_ids)
 
552
            try:
 
553
                self._repository.add_inventory(revision_id, target_inv,
 
554
                                               parent_ids)
 
555
            except errors.UnsupportedInventoryKind:
 
556
                raise errors.IncompatibleRevision(repr(self._repository))
 
557
 
 
558
    def _handle_root(self, target_inv, parent_ids):
 
559
        revision_id = target_inv.revision_id
 
560
        if self.update_root:
 
561
            target_inv.root.revision = revision_id
 
562
            store = self._repository.weave_store
 
563
            transaction = self._repository.get_transaction()
 
564
            vf = store.get_weave_or_empty(target_inv.root.file_id, transaction)
 
565
            vf.add_lines(revision_id, parent_ids, [])
 
566
        elif not self._repository.supports_rich_root():
 
567
            if target_inv.root.revision != revision_id:
 
568
                raise errors.IncompatibleRevision(repr(self._repository))
 
569
 
 
570
 
 
571
    def _install_revision(self, revision_id, metadata, text):
 
572
        if self._repository.has_revision(revision_id):
 
573
            return
 
574
        self._repository._add_revision_text(revision_id, text)
 
575
 
 
576
    def _install_signature(self, revision_id, metadata, text):
 
577
        transaction = self._repository.get_transaction()
 
578
        if self._repository._revision_store.has_signature(revision_id,
 
579
                                                          transaction):
 
580
            return
 
581
        self._repository._revision_store.add_revision_signature_text(
 
582
            revision_id, text, transaction)